Compare commits

...

1479 Commits

Author SHA1 Message Date
Alan Dipert
51fbb5cfac Update NEWS 2018-09-14 14:25:43 -07:00
Alan Dipert
ca2c2b60f2 Grunt 2018-09-14 14:25:09 -07:00
Alan Dipert
d6064636d4 Merge pull request #2182 from rstudio/joe/misc/fontawesome-4-note
Add note that Font Awesome support is for 4.7.0
2018-09-14 13:52:30 -07:00
Joe Cheng
9646c9b0a0 Add note that Font Awesome support is for 4.7.0 2018-09-12 08:57:53 -07:00
Winston Chang
3bdd4af75c Merge pull request #2168 from rstudio/create-scope-dir
Check for existence of bookmark scope directory before creating
2018-08-24 13:43:15 -05:00
Winston Chang
98d4b5e487 Check for existence of bookmark scope directory before creating 2018-08-24 12:43:45 -05:00
Joe Cheng
8b5639bfdb Tweaks to NEWS 2018-08-24 10:41:08 -07:00
Joe Cheng
1c70b8b1bf Merge pull request #2147 from nathancday/master
Fixes #174, allowing specific days of the week to be disabled.
2018-08-24 10:39:12 -07:00
Joe Cheng
b5a7e03879 Merge branch 'master' into master 2018-08-24 10:38:44 -07:00
Winston Chang
32913f9d95 Merge pull request #2160 from rstudio/digest-xxhash
Use xxhash64 instead of sha256 for hash algorithm
2018-08-17 11:59:47 -05:00
Winston Chang
cbabf9a2a3 Use xxhash64 instead of sha256 for hash algorithm 2018-08-16 15:54:54 -05:00
Winston Chang
03e92c3336 Update NEWS 2018-08-10 21:18:05 -05:00
Winston Chang
997c39fdc0 Merge pull request #2125 from rstudio/plot-interact-scaled
Fix plot interaction for scaled plots
2018-08-10 21:13:16 -05:00
Winston Chang
bba2d1ee18 Grunt 2018-08-10 19:42:11 -05:00
Winston Chang
a60301810f Update coordmap tests 2018-08-10 19:42:11 -05:00
Winston Chang
6b261f76b1 Bump version and update NEWS 2018-08-07 15:25:31 -05:00
Winston Chang
3db5f21d90 Update data structure comment 2018-08-07 15:11:43 -05:00
Winston Chang
121bfcb984 Import old brush after image has loaded 2018-08-07 15:11:43 -05:00
Winston Chang
265de66946 Make sure not to have multiple reset event handlers 2018-08-07 14:51:23 -05:00
Winston Chang
79c5c9f95e Add isnan() function for IE 2018-08-07 14:51:23 -05:00
Winston Chang
3354a47e8a Add width/height to coordmap instead of using naturalWidth/Height
This eliminates the need to use an on load callback.
2018-08-07 14:51:23 -05:00
Winston Chang
a1e1416d7a More consistent use of img to css conversion functions 2018-08-07 10:48:42 -05:00
Winston Chang
24b7a9907f renderCachedPlot: add note about interactive plots to help page 2018-08-07 10:48:42 -05:00
Nate
214abd0cd4 moved new dateInput arguments to last and added formatting conditional if datesdisabled is Date object 2018-08-07 10:16:48 -04:00
Joe Cheng
0bb53e8ca5 Inputs in renderUI/uiOutput don't work with bookmarks (#2139)
* hasCurrentRestoreContext returns FALSE from server side

Fixes #2138.

* Add NEWS item for renderUI bookmarking fix
2018-08-06 15:04:16 -07:00
Winston Chang
ec12caaeba Include x and y pixelratio in coordinfo 2018-08-06 12:51:08 -05:00
Winston Chang
5bbf2aa57a Use canonical CSS property name
Firefox doesn't support shorthand properties like "border-left", but instead
requires "border-left-width".
2018-08-06 12:51:08 -05:00
Winston Chang
84ad9997da Reposition div when resized (without new image) 2018-08-06 12:51:08 -05:00
Winston Chang
9f6ce87443 Remove redundant isEquivalent function 2018-08-06 12:51:08 -05:00
Winston Chang
1ff6c382bf Remove unnecessary ggplot2 workaround 2018-08-06 12:51:07 -05:00
Winston Chang
c366c10ae1 Initialize coordmap only after image loads 2018-08-06 12:51:07 -05:00
Winston Chang
950df1e25c Add support for scaled images and brushing 2018-08-06 12:51:07 -05:00
Winston Chang
909bfa8c14 Allow plot interaction to handle scaled images 2018-08-06 12:51:07 -05:00
Winston Chang
598b48d078 DiskCache: use mtime instead of atime, check for time resolution (#2146)
* DiskCache: check for atime support while running

* Use mtime instead of atime

* Remove mtime resolution checks
2018-08-06 10:50:05 -07:00
Nate
4c7b7f236a added datesdisabled parameter to dateInput() allows disabling of specific yyyy-mm-dd format strings 2018-08-04 19:04:40 -04:00
Nate
896c5b41cb Fixes #174, allowing specific days of the week to be disabled. 2018-08-04 15:47:13 -04:00
Winston Chang
205c35d5e5 Re-document with roxygen2 6.1.0 2018-08-03 17:50:47 -05:00
Winston Chang
bf0dd7d725 Merge pull request #1997 from rstudio/plot-cache
Add renderCachedPlot()
2018-08-03 13:30:13 -05:00
Winston Chang
ba2b811172 Fix argument name in documentation 2018-07-31 15:06:59 -05:00
Winston Chang
be347c3ed4 Don't cache plotResult 2018-07-31 12:07:21 -05:00
Joe Cheng
c01abdb6a9 Merge branch 'master' into plot-cache 2018-07-26 15:52:11 -07:00
Winston Chang
95a5a965a5 Documentation fixes 2018-07-20 16:12:21 -05:00
Winston Chang
fc2849a8ff Remove env and quoted arguments 2018-07-20 16:10:15 -05:00
Winston Chang
fcc900f3e0 Simplify resize logic 2018-07-20 14:48:57 -05:00
Winston Chang
9d0bcd5637 Add shiny-scalable class 2018-07-20 14:44:21 -05:00
Winston Chang
6ebbad5273 Safer file removal order 2018-07-19 23:20:51 -05:00
Winston Chang
930459899a Small logic cleanup 2018-07-19 23:20:40 -05:00
Winston Chang
fe730e2d76 Make session cache public, so that user can set it 2018-07-19 21:57:32 -05:00
Winston Chang
e58b2e9a47 Fix app- and session-level cache initialization 2018-07-19 21:57:07 -05:00
Winston Chang
719dbab0c2 DiskCache: make destroy_on_finalize default to FALSE 2018-07-19 14:35:59 -05:00
Winston Chang
86ea023e2e Update caches from code review feedback 2018-07-19 14:33:53 -05:00
Winston Chang
bc0fb3f44c Restructure drawReactive/renderFunc code 2018-07-18 14:52:39 -05:00
Winston Chang
6d37f6b4dd Set max-width and max-height to 100% for cached plots 2018-07-12 10:55:26 -05:00
Winston Chang
958ab85297 Add exec_missing parameter 2018-07-12 10:55:26 -05:00
Winston Chang
a23f973433 Suppress console logging for renderCachedPlot 2018-07-02 17:04:44 -05:00
Winston Chang
c124256bad Improved logging for DiskCache and MemoryCache 2018-07-02 17:04:28 -05:00
Winston Chang
f1b035bcca Update renderCachedPlot documentation and examples 2018-07-02 15:27:51 -05:00
Joe Cheng
81cc7c591e Merge pull request #2080 from schloerke/barret-freeze-thaw
freeze/thaw reactiveValues with namespace support
2018-06-29 15:58:52 -07:00
Winston Chang
a0ca560c3b Doc entries 2018-06-29 13:24:01 -05:00
Winston Chang
d1f20a9c73 Add 'missing' parameter to get() 2018-06-29 13:23:47 -05:00
Barret Schloerke
013059c5b9 merged in master 2018-06-29 11:35:20 -04:00
Winston Chang
fe6ad235ac Add sentinel value cache miss 2018-06-28 23:58:55 -05:00
Winston Chang
67af26ffe6 When MemoryCache is used, we can cache the displayList 2018-06-26 23:11:25 -05:00
Winston Chang
0fce9de04f Change default DiskCache size 2018-06-26 22:59:32 -05:00
Winston Chang
a8b8df21d6 DiskCache: make default finalizer behavior conditional on whether a temp directory is used 2018-06-26 22:57:35 -05:00
Winston Chang
ab2e304f02 DiskCache: Add info about sharing across processes 2018-06-26 22:56:59 -05:00
Winston Chang
574f2c53d4 DiskCache: Make destroy() work robustly with multiple processes 2018-06-26 22:26:27 -05:00
Winston Chang
bc85d812d2 DiskCache: Avoid errors from a race condition 2018-06-26 16:38:43 -05:00
Winston Chang
364990a29f Fix .rds directory search pattern 2018-06-26 16:37:58 -05:00
Winston Chang
9ac9e36873 DiskCache: Check if cache has been destroyed by someone else 2018-06-26 15:48:08 -05:00
Winston Chang
6745e09688 Add support for promisey cache key 2018-06-25 21:13:55 -05:00
Winston Chang
e758927c84 DiskCache: Add warning when caching reference objects 2018-06-25 15:53:47 -05:00
Winston Chang
90fbf7d50f Add comment about pruning 2018-06-25 15:34:43 -05:00
Winston Chang
75f1ee0082 Prune after setting value 2018-06-25 15:30:30 -05:00
Winston Chang
750aaf451a NEWS edits 2018-06-25 14:11:42 -05:00
Winston Chang
b44bfe9109 Grunt 2018-06-25 14:10:22 -05:00
Winston Chang
aa392f8563 Merge pull request #2102 from tmastny/selectize-remote
Resolves #1933: Serve-side selectize extended to all inputs
2018-06-25 14:07:56 -05:00
Winston Chang
ac7228f6c4 Merge branch 'master' into selectize-remote 2018-06-25 14:07:20 -05:00
Winston Chang
dcb12addaa Merge pull request #2108 from tmastny/par-oma
Resolves #1935: Fix coordinate outputs when modifying outer margins
2018-06-25 14:05:39 -05:00
Winston Chang
ad398b5f8a Merge branch 'master' into par-oma 2018-06-25 14:05:30 -05:00
Timothy Mastny
803cb4806e add new item 2018-06-25 14:04:31 -05:00
Timothy Mastny
1a468bbb61 add news items 2018-06-25 14:02:18 -05:00
Winston Chang
c332c051f3 Merge pull request #2099 from tmastny/slider-type
Fix #2019: `updateSliderInput` changes formatting
2018-06-25 14:00:18 -05:00
Timothy Mastny
db48befcb7 removed TODO comments 2018-06-25 13:51:24 -05:00
Winston Chang
b02edb05ac DiskCache: Use temp file when setting value 2018-06-25 11:56:01 -05:00
Winston Chang
d7009fd1c8 DiskCache: Don't call exists() before reading file 2018-06-25 11:55:29 -05:00
Winston Chang
ce3755676c Fix files that were split 2018-06-25 10:45:12 -05:00
Winston Chang
db3c1b728d Merge branches 'plot-cache-split-1' and 'plot-cache-split-2' into plot-cache 2018-06-25 10:42:52 -05:00
Winston Chang
1761de4740 Rename cache.R cache-context.R 2018-06-25 10:42:49 -05:00
Winston Chang
09d496925b Rename cache.R cache-memory.R 2018-06-25 10:41:49 -05:00
Winston Chang
3af5327f1c Rename cache.R cache-disk.R 2018-06-25 10:41:04 -05:00
Winston Chang
06cb14d7ec Rename $has method to $exists 2018-06-22 23:27:49 -05:00
Winston Chang
7be1a9d7fa Add memoryCache and make it the default 2018-06-22 23:08:39 -05:00
Winston Chang
95243fb35c DiskCache: Add LRU eviction policy 2018-06-22 22:44:27 -05:00
Winston Chang
26438a3979 DiskCache cleanup 2018-06-22 19:32:29 -05:00
Winston Chang
28db097a71 Use promise domain for currentOutputName 2018-06-22 19:31:20 -05:00
Winston Chang
76fdd8ae04 Make renderCachedPlot work with async 2018-06-22 16:47:55 -05:00
Timothy Mastny
3a73bfb142 changed output coordinate system to "ndc" to account for margin changes 2018-06-21 17:48:16 -05:00
Timothy Mastny
a24bdabf08 Updates to Winston's feedback: removed for ... of iteration that is not supported in IE. 2018-06-21 16:17:42 -05:00
Winston Chang
8815f293a2 Update R version check information 2018-06-21 15:55:35 -05:00
Winston Chang
9af2775539 Fix absolutePath to correctly handle absolute paths 2018-06-21 15:48:54 -05:00
Winston Chang
ae5deae6e9 Use output name in cache key 2018-06-21 14:55:30 -05:00
Winston Chang
61c2126498 Add diskCache function, and app- and session-level caches 2018-06-21 14:44:09 -05:00
Timothy Mastny
881fe0cfce explicitly set number to null instead of implicitly for better documentation 2018-06-21 14:30:15 -05:00
Timothy Mastny
a999bf389c update NEWS.md 2018-06-21 14:30:15 -05:00
Timothy Mastny
ff3b97b630 refactored data type checking for consistency 2018-06-21 14:30:15 -05:00
Timothy Mastny
639b520d39 updateInputSlider can now change from date to date-time formatting. fixes #2019 2018-06-21 14:30:15 -05:00
Timothy Mastny
19dc29ea17 changes as per Winston's feedback; additional comments, and edge cases, and removed unnecessary JS. 2018-06-21 12:47:14 -05:00
Timothy Mastny
97bebae8d7 fixed default selection for multiple-select 2018-06-20 15:41:35 -05:00
Timothy Mastny
cf534ce6da remove "thiz"s and replace with explicit "selectize" 2018-06-20 15:41:35 -05:00
Timothy Mastny
f25f691a55 fix selectize for default selected value NULL 2018-06-20 15:41:35 -05:00
Timothy Mastny
cbebf8be7b improve performance of R input processing 2018-06-20 15:41:35 -05:00
Timothy Mastny
165ce26b2f Fixes #1933. Fixed JSON encoding of input data, and added more optgroup controls on JS-side 2018-06-20 15:41:35 -05:00
Winston Chang
572c863bff Merge pull request #2106 from schloerke/js_patch
compile with grunt to get version in js code (v1.1.0.9000)
2018-06-20 12:47:43 -05:00
Barret Schloerke
d3c85d67b8 gruntfile should run 'default' task to make sure everything is built like normal 2018-06-20 13:31:10 -04:00
Barret Schloerke
ff3434f77e add a R test to make sure shiny.js {{ VERSION }} was replaced 2018-06-20 12:53:07 -04:00
Barret Schloerke
762528c044 add a grunt test to make sure {{ VERSION }} was replaced 2018-06-20 12:52:46 -04:00
Barret Schloerke
1891af0d4a compile with grunt 2018-06-20 11:39:12 -04:00
Winston Chang
583ad036f7 Streamline renderCachedPlot API 2018-06-19 16:00:49 -05:00
Winston Chang
ac92bf98d4 WIP 2018-06-18 16:25:36 -05:00
Winston Chang
fd90ff7ff7 Use DiskCache class 2018-06-18 16:25:36 -05:00
Winston Chang
d06dbbe5db Change cacheResetEventExpr to cacheResetExpr 2018-06-18 16:25:36 -05:00
Winston Chang
bffc4995d7 Rename normalizePath2 to absolutePath 2018-06-18 16:25:36 -05:00
Winston Chang
4b8b406bed Add sizeGrowthRatio function 2018-06-18 16:25:36 -05:00
Winston Chang
5641153272 renderCachedPlot: pass ... args 2018-06-18 16:25:36 -05:00
Winston Chang
08c6c7781f Rename cacheClearExpr to cacheResetEventExpr 2018-06-18 16:25:36 -05:00
Winston Chang
ad2ad391a7 Isolate user code 2018-06-18 16:25:36 -05:00
Winston Chang
caac88be0d Don't re-run user code when only dimensions change 2018-06-18 16:25:36 -05:00
Winston Chang
10660aa373 Rename cacheInvalidationExpr to cacheClearExpr 2018-06-18 16:25:36 -05:00
Winston Chang
cfaf97aee4 Add entries to staticdocs 2018-06-18 16:25:36 -05:00
Winston Chang
55f14576f0 Remove createCachedPlot function 2018-06-18 16:25:36 -05:00
Winston Chang
4dca94ac99 Code reorganization 2018-06-18 16:25:36 -05:00
Winston Chang
14779d3d27 Only invalidate plot when fitted dimensions change 2018-06-18 16:25:36 -05:00
Winston Chang
66d1e710b5 Allow renderCachedPlot to take a directory for scope 2018-06-18 16:25:36 -05:00
Winston Chang
12ae3c17e9 Allow onStop to take a NULL session 2018-06-18 16:25:35 -05:00
Winston Chang
36e4da0709 Add app/session scoping for renderCachedPlot 2018-06-18 16:25:35 -05:00
Winston Chang
91631cb081 Note bug fix in R 2018-06-18 16:25:35 -05:00
Winston Chang
224f082e1f Implement cache invalidation 2018-06-18 16:25:35 -05:00
Winston Chang
76b239a6ea Convert renderCachedPlot to take expr and cacheKeyExpr 2018-06-18 16:25:35 -05:00
Winston Chang
cb476b510d Initial implementation of renderCachedPlot 2018-06-18 16:25:35 -05:00
Winston Chang
334f233968 Move plot caching code into separate file 2018-06-18 16:25:35 -05:00
Winston Chang
e1f21250b9 Rename plotCache to createCachedPlot 2018-06-18 16:25:35 -05:00
Winston Chang
8d087e4f20 Get output info and auto-size plot cache images 2018-06-18 16:25:35 -05:00
Winston Chang
9e35e8c947 Allow user code to pass width/height/pixelratio 2018-06-18 16:25:35 -05:00
Winston Chang
f98faef024 Document cache scoping and minor code cleanup 2018-06-18 16:25:35 -05:00
Winston Chang
0f9346ead5 Add different scoping levels for plotCache 2018-06-18 16:25:35 -05:00
Winston Chang
fc8118c694 First implementation of plotCache 2018-06-18 16:25:35 -05:00
Joe Cheng
026b7278c1 Merge pull request #2091 from schloerke/barret-varSelectInput
Variable Select Input
2018-06-14 09:39:09 -05:00
Barret Schloerke
375a7e7e5c respect existing class values and add tests 2018-06-11 10:31:49 -05:00
Barret Schloerke
7a1aecb1a4 varSelectInput doc tweaks 2018-06-08 10:59:45 -04:00
Barret Schloerke
b3690e8680 add bullets in details for output return value (update corresponding example) 2018-06-08 10:51:37 -04:00
Barret Schloerke
97d490cfb4 remove rogue staticdocs fn 2018-06-07 16:52:35 -04:00
Barret Schloerke
2081dda6fc merged master -> varSelectInput 2018-06-07 16:46:31 -04:00
Barret Schloerke
ea912fc50c staticdocs for varSelectInput 2018-06-07 16:43:20 -04:00
Barret Schloerke
b655fdf68f added news for varSelectInput 2018-06-07 16:43:05 -04:00
Barret Schloerke
4749f46a4f add shiny.symbol and shiny.symbolList tests 2018-06-07 16:10:47 -04:00
Barret Schloerke
f95bb9c82d compile and document 2018-06-07 16:10:24 -04:00
Barret Schloerke
6529529cdb add new input 'varSelectInput()' and input binding 'shiny.symbol' and 'shiny.symbolList' 2018-06-07 16:10:01 -04:00
Tim Mastny
3a2a3f21d4 Improve optgroup documentation per #1864 (#2084)
* improves optgroup documentation per #1864

* change PR number in NEWS.md

* change addresses to addressed
2018-06-07 12:09:45 -05:00
Tim Mastny
631bc1c481 Addresses #2042: lighten dates outside of range for datepicker (#2087)
* dehighlight dates outside of range for datepicker

* override color in shiny.css instead of package css

* refactored css styling to use specific references instead of !important
2018-06-07 11:57:27 -05:00
Barret Schloerke
597af36759 added news item 2018-05-31 10:31:35 -04:00
Barret Schloerke
691062f687 white space 2018-05-31 10:30:19 -04:00
Barret Schloerke
6651c4ea48 when freezing a reactivevalues key, use the name space similar to $.reactivevalues 2018-05-31 10:23:30 -04:00
Barret Schloerke
116559e5a0 use utils namespace 2018-05-31 10:21:55 -04:00
Barret Schloerke
7818e8ed64 white space 2018-05-31 10:21:42 -04:00
Winston Chang
2880391620 runApp: add support for IPv6 addresses 2018-05-25 16:19:51 -04:00
Joe Cheng
f742605a1b Bump version to *.9000. Back to work! 2018-05-17 17:20:17 -07:00
Winston Chang
2afff67e89 Bump version to 1.1.0 2018-05-16 15:18:59 -07:00
Winston Chang
fe7bd53250 Bump httpuv version and add NEWS note 2018-05-16 15:18:59 -07:00
Joe Cheng
6df3509869 Merge pull request #2062 from rstudio/joe/bugfix/windows-rounding
Fix #2061: Tests failing on Windows due to rounding errors
2018-05-16 15:17:04 -07:00
Joe Cheng
062dc771aa Fix #2061: Tests failing on Windows due to rounding errors 2018-05-16 14:44:34 -07:00
Joe Cheng
d4688db31c Update NEWS 2018-05-03 14:08:52 -07:00
Joe Cheng
c49a289619 Fix #2033: Rstudio Viewer window not closed on shiny::stopApp() 2018-05-03 14:06:34 -07:00
Joe Cheng
9c3a0c86ca Take dependency on later >=0.7.2 2018-05-01 20:37:25 -05:00
Winston Chang
01b24e984c Merge pull request #2038 from rstudio/joe/bugfix/cycle-start-bugs
Fix #2037: With enableBookmarking="url", clientData is not available …
2018-04-25 13:34:34 -05:00
Joe Cheng
9dd4302fe9 Fix #2037: With enableBookmarking="url", clientData is not available when observers are first run
Also fixed reactiveTimer firing even while async tasks are active
2018-04-25 10:54:00 -07:00
Winston Chang
c2f03aa833 Merge pull request #2036 from rstudio/joe/misc/renderui-experimental
Remove "experimental feature" tag from renderUI
2018-04-24 12:42:04 -05:00
Barbara Borges Ribeiro
2260459422 brought observeEvent/eventReactive documentation up to date 2018-04-24 17:44:54 +01:00
Winston Chang
e838cc3fe9 Re-document 2018-04-24 10:24:52 -05:00
Winston Chang
74457b95e9 NEWS edits 2018-04-24 10:17:03 -05:00
Joe Cheng
d5754515a6 Remove "experimental feature" tag from renderUI 2018-04-23 17:05:08 -07:00
Joe Cheng
4ed13c04f5 Merge pull request #2032 from rstudio/fix-flushed-callbacks
Set default reactive domain when executing flushedCallbacks. Fixes #1975
2018-04-21 10:02:10 -07:00
Winston Chang
5a5294cc44 Set default reactive domain when executing flushedCallbacks. Fixes #1975 2018-04-21 11:46:50 -05:00
Joe Cheng
3a5d48ae7c Remove outdated Remotes
Live code review by @wch
2018-04-19 14:59:10 -07:00
Joe Cheng
ffe883ab72 Merge pull request #2022 from rstudio/joe/bugfix/timer-leak
Fix #2021: Memory leak with reactiveTimer and invalidateLater
2018-04-19 14:16:35 -07:00
Joe Cheng
31c4e0fdfe Add test to demonstrate vectorized unscheduling 2018-04-19 12:52:30 -07:00
Joe Cheng
66f970e0bd Merge pull request #2026 from rstudio/fix-ggplot2
Fix plot coordmap for devel version of ggplot2. Closes #2016
2018-04-19 12:46:38 -07:00
Winston Chang
07b223dcb0 Fix plot coordmap for devel version of ggplot2. Closes #2016 2018-04-19 11:40:26 -05:00
Joe Cheng
f1e27b6ffb Fix #1922: Warning: partial match of 'y' to 'yintercept' 2018-04-19 10:00:24 -05:00
Joe Cheng
389463aea5 Merge branch 'joe/feature/undedupe-inputs' 2018-04-18 19:33:37 -07:00
Joe Cheng
b11ab9a31c Update NEWS 2018-04-18 12:53:31 -07:00
Joe Cheng
5fe85b07b7 Merge remote-tracking branch 'andrewsali/master' 2018-04-18 12:52:40 -07:00
Joe Cheng
3c7b1e7d21 Update NEWS 2018-04-18 12:32:16 -07:00
Joe Cheng
c556cf1e69 Fix #2021: Memory leak with reactiveTimer and invalidateLater 2018-04-18 12:30:14 -07:00
Joe Cheng
722e5fb5f7 Modify internal JS to use Shiny.setInputValue, {priority: "event"} 2018-04-18 12:05:18 -07:00
Joe Cheng
e90cc591b7 Update NEWS 2018-04-18 11:57:55 -07:00
Joe Cheng
c555725201 Change {immediate: ...} to {priority: "deferred|immediate|event"}
This was the product of a long discussion between @wch, @alandipert, @bborgesr
and myself. The conflation of immediate (no throttle/debounce) and non-dedupe
in a single "immediate" flag was deemed unacceptable, because UI controls often
want immediacy but also dedupe. Introducing a second "dedupe" flag would work
but {immediate: false, dedupe: false} doesn't make much sense, and dedupe not
only implies that InputNoResendDecorator should behave differently but also
InputBatchSender (i.e. no deduplication AND no coalescing).

We decided to remove the "immediate" boolean option and replace it with a
string option that would have three possibilities at this time. The only con
to this approach is if anyone is calling onInputChange with immediate:true
today, and I can't imagine anyone is. The immediate flag only has any effect
if the input id that's being set has been put in debounce/throttle mode, and
I don't even think that is documented today, and I'm not even sure it's
possible to do it from custom JS (that's not part of a custom input binding).
2018-04-17 16:39:05 -07:00
Joe Cheng
cef1f3c7ee withReactiveDomain now acts as a promise domain
Without this change, async handlers won't return any
value for getDefaultReactiveDomain().

    library(shiny)
    library(promises)

    ui <- fluidPage(
      p("This app tests if async handlers have reactive domains. You'll get a yes/no answer below."),
      h3(
        "Does it work?",
        textOutput("answer", inline = TRUE)
      )
    )

    server <- function(input, output, session) {
      output$answer <- renderText({
        promise_resolve(TRUE) %...>% {
          if (!is.null(getDefaultReactiveDomain()))
            "Yes!"
          else
            "No :("
        }
      })
    }

    shinyApp(ui, server)
2018-04-16 20:51:46 -05:00
Joe Cheng
e5d1fa1ea4 Fix #2008: Allow eventReactive and observeEvent eventExprs to be async (#2014)
* Fix #2008: Allow eventReactive and observeEvent eventExprs to be async

This makes it possible to monitor e.g. async reactives.

In the process of fixing this, also discovered that observers don't
filter out shiny.silent.error (i.e. req(FALSE)) when they come back
from async operations. For example, this will kill the current
Shiny session instead of being ignored:

  observe({
    promise_resolve(TRUE) %...>%
      {req(FALSE)}
  })

This issue is also fixed in this commit.

* Enable deep stack trace by default, now that it's fast
2018-04-16 20:50:28 -05:00
Joe Cheng
3ccf2937b4 Fix #928: allow inputs to trigger reactive flow even if the value of input hasn't changed
We already had an `immediate` input option, which was used to override client side rate
limiting mechanisms (debounce/throttle). This commit extends the semantics of that option
to also mean that duplicate values should not be ignored on the client side.

Previous to this commit, circumventing the client side dedupe logic was not enough. The
server side ReactiveValues object was also subject to deduping. With this commit, the
low-level ReactiveValues class's constructor now has a `dedupe` option, which defaults
to TRUE; the ReactiveValues used for a session's input has it turned to FALSE. I figure
if I had to work this hard to get the client to stop sending duplicates, and the input
values are only expected to ever be updated by the client, then there's really no reason
for server side deduping to be performed for this particular ReactiveValues object.

It would make sense as a future feature to also make deduping optional for user-created
reactiveValues and reactiveVal objects.
2018-04-16 18:37:47 -07:00
Joe Cheng
b7b696630f Fix #2003: Long stack traces are truncated 2018-04-16 17:16:12 -05:00
Joe Cheng
84aba546bc Fix #2000: Implicit calls to xxxOutput not working inside modules (#2010)
* Fix #2000: Implicit calls to xxxOutput not working inside modules

* Add comment, update NEWS

* Credits in NEWS
2018-04-16 16:57:13 -05:00
Winston Chang
741236df56 Merge pull request #2011 from rstudio/joe/feature/output-null
Fix #1989: Allow outputs to be removed by assigning NULL to them
2018-04-12 16:29:48 -05:00
Winston Chang
e3584f0a61 Merge pull request #2013 from rstudio/joe/bugfix/render-plot-args
renderPlot's ... args were being dropped
2018-04-12 16:27:46 -05:00
Joe Cheng
432482c5a7 renderPlot's ... args were being dropped 2018-04-12 11:57:32 -07:00
Joe Cheng
323ad46bba Implement #1989: Allow outputs to be removed by assigning NULL to them 2018-04-11 18:40:32 -07:00
Alan Dipert
ace0fe1802 Merge pull request #2005 from rstudio/alan/bugfix/dndfix
Fix dragging and dropping in the presence of jQuery 3.0
2018-04-11 11:53:42 -07:00
Alan Dipert
36f244fece Merge branch 'master' into alan/bugfix/dndfix 2018-04-11 11:52:23 -07:00
Joe Cheng
99e5ef99ec Move some bullets around 2018-04-10 10:08:15 -07:00
Alan Dipert
d6d3ed5bbc NEWS 2018-04-10 08:40:40 -07:00
Alan Dipert
49d09ecf30 Grunt 2018-04-10 08:30:46 -07:00
Alan Dipert
c529a03096 DnD: Fix in the presence of jQuery 3.0 (removed .size()) 2018-04-10 08:29:03 -07:00
Andras Sali
101d9aa0fa Move trigger after value change 2018-04-07 16:51:03 +02:00
Andras Sali
b4864e1180 Trigger shiny:value even if same data is received. Fixes #1978 2018-04-07 16:46:34 +02:00
Winston Chang
cba7304ab9 Merge pull request #1996 from rstudio/fix-selectize
fix selectize capitalization (regression introduced by #1861)
2018-04-05 11:58:20 -05:00
Barbara Borges Ribeiro
2d058b0519 move attribute setting to after choices is reassigned (this was getting lost after the changes in #1861). 2018-04-04 19:55:15 +01:00
Barbara Borges Ribeiro
eed9231884 fix selectize capitalization (regression introduced by #1861) 2018-04-04 14:00:44 +01:00
Joe Cheng
5c84eaf2a5 Merge pull request #1990 from rstudio/joe/feature/better-stacks
Better stack traces
2018-03-27 19:31:51 -07:00
Joe Cheng
2ef7226be0 Use seq_along instead of 1:length(x)
It behaves when length(x) == 0
2018-03-27 18:30:24 -07:00
Joe Cheng
e5d1c61cdf Merge branch 'master' into joe/feature/better-stacks 2018-03-27 18:04:05 -07:00
Joe Cheng
e635055ab8 Update NEWS 2018-03-27 18:02:41 -07:00
Joe Cheng
d8d4e3b262 Don't error when entire stack trace is stripped/pruned 2018-03-27 16:35:45 -07:00
Joe Cheng
8f29543479 Use qualified name for utils::tail (R CMD check NOTE) 2018-03-27 16:19:28 -07:00
Joe Cheng
c11a8ea24b Fix tests 2018-03-27 15:35:29 -07:00
Joe Cheng
86646d7faa Make srcref offsetting optional 2018-03-27 15:35:06 -07:00
Joe Cheng
6e44915e08 Merge pull request #1984 from rstudio/joe/feature/faster-deep-stacks
Lazily format stack traces
2018-03-27 10:31:00 -07:00
Joe Cheng
f8b99cf4e9 Add deprecation docs 2018-03-26 11:38:12 -07:00
Joe Cheng
0e7d6ff192 Refactoring and deprecation in conditions.R
- Refactor printError so a working printStackTrace falls out
- Deprecate extractStackTrace and formatStackTrace, see if anyone uses them
2018-03-26 11:35:50 -07:00
Barbara Borges Ribeiro
66501dac97 Add new autoclose = TRUE param to dateInput() and dateRangeInput (#1987)
* Add new `autoclose = TRUE` param to both dateInput() and dateRangeInput()

* added NEWS item
2018-03-23 09:40:19 -07:00
Joe Cheng
195907b2ec printError implements lobstr::cst analysis and deep-stack-aware frame suppression 2018-03-22 12:22:46 -07:00
Joe Cheng
be11b44864 First steps to improved stack traces
- Adds functions we will need for tracking ..stacktraceon/off..
  across deep stacks
- Adds functions we will need for pruning according to lobstr::cst
  logic

These functions are not yet integrated, that will occur in a
separate commit.
2018-03-20 16:45:47 -07:00
Joe Cheng
bc7cd21c13 Update NEWS.md 2018-03-20 16:39:52 -07:00
Carson Sievert
0555cbdd28 relay offsetWidth/offsetHeight of htmlwidgets to clientData (#1981)
Addresses #1980
2018-03-20 16:37:43 -07:00
Joe Cheng
97498451bb Lazily format stack traces
With deep stack traces enabled, whenever then() is called, we need
to grab the current stack, just in case a downstream callback throws
an error and we need to form a deep stack trace.

Previously, we were calling formatStackTrace at the time that we
grab the current stack (i.e. no error has happened yet) because I
wasn't sure whether holding a reference to sys.calls() for a long
time was a good idea from a garbage collection perspective; would it
prevent the stack frame environments from being collected? But the
answer is no, sys.calls() is just calls, which can be confirmed with
.Internal(inspect(sys.calls()).

By deferring the formatStackTrace call to when we actually need to
print the stack trace, we save ourselves a ton of work--it turns out
it's quite expensive to format the stack traces, much more expensive
than sys.calls() alone.
2018-03-20 12:35:17 -07:00
Joe Cheng
2e0d9b5475 Bump httpuv dependency version 2018-03-18 19:07:43 -07:00
Barbara Borges Ribeiro
62395f3103 Improve error handling when addResourcePath() fails (especially for runtime: shiny_prerendered documents) (#1968)
* A copy of yihui's PR for rmarkdown (https://github.com/rstudio/rmarkdown/pull/1171/) to avoid to error "Error in normalizePath: path[1]="": No such file or directory" when running any tutorial

* first try

* limited the scope of the `tryCatch` wrapper to the one important line that needed it; added news item
2018-03-16 15:36:12 -07:00
Winston Chang
6b31cd6aee Merge pull request #1965 from rstudio/joe/bugfix/plot-dim-error
Fix #1964: renderPlot cache breaks when width/height throw
2018-03-01 11:12:56 -06:00
Joe Cheng
e67a8ba369 Fix #1964: renderPlot cache breaks when width/height throw
Fixed by moving the isolate(getDims()) call into the (effectively)
try/catch that does a non-isolated getDims() if an error occurs
2018-02-28 15:40:55 -08:00
Joe Cheng
133d301925 Merge pull request #1961 from rstudio/fix-date-sliders-bookmarking
Fix URL-encoded bookmarking with date/date-time sliders
2018-02-28 14:26:50 -08:00
Joe Cheng
17c40a5d1d Merge pull request #1960 from rstudio/slider-formatting
Don't show commas after decimal mark in sliderInput
2018-02-28 13:01:20 -08:00
Winston Chang
042211e5f6 Grunt 2018-02-28 14:34:09 -06:00
Winston Chang
d12830d700 sliderInput: don't show commas after decimal 2018-02-28 14:33:42 -06:00
Winston Chang
b411c70280 Fix URL-encoded bookmarking with date/date-time sliders 2018-02-27 20:42:11 -06:00
Winston Chang
2bc22cc7d5 Merge pull request #1955 from rstudio/update-slider
Update ion.rangeSlider to 2.2.0
2018-02-27 13:21:09 -06:00
Joe Cheng
b4c189c89b Merge pull request #1956 from rstudio/fix-slider-rounding
Avoid rounding errors in sliderInput
2018-02-27 10:55:11 -08:00
Winston Chang
fe3f351a2d Avoid rounding errors from pretty(). Fixes #1006 2018-02-27 10:50:36 -06:00
Winston Chang
076be9cba7 Remove unused keyboard_step parameter
keyboard_step was removed in ion.rangeSlider 2.2.0.
2018-02-26 15:57:30 -06:00
Winston Chang
f28dcd85fb Update to ion.rangeSlider 2.2.0 2018-02-26 15:57:30 -06:00
Joe Cheng
8e0f17c9d7 Merge pull request #1954 from rstudio/fix-bookmark-restore
Look for restore context associated with session
2018-02-26 09:25:33 -08:00
Winston Chang
d73817a0db Look for restore context associated with session. Fixes #1948 2018-02-26 10:55:38 -06:00
Joe Cheng
11874db825 Remove background-thread branch from httpuv remote 2018-02-16 15:41:32 -08:00
Joe Cheng
5d5a43ce90 Merge pull request #1932 from rstudio/async
Async
2018-02-16 07:41:36 -08:00
Winston Chang
75e548caab For installation of Rtools on Appveyor 2018-02-16 09:15:40 -06:00
Joe Cheng
c901e7ba06 Update TODO-promises.md 2018-02-13 15:23:06 -08:00
Joe Cheng
b1dc3dfca1 Restore label to plotObj reactive 2018-02-09 11:12:46 -08:00
Joe Cheng
ce4ed20c69 Fix remaining failing tests 2018-02-09 11:12:46 -08:00
Joe Cheng
d44df7f860 Stack traces were being lost inside hybrid_chain 2018-02-09 11:12:46 -08:00
Joe Cheng
54353e0e1f Fix coordmap unit tests 2018-02-09 11:12:46 -08:00
Joe Cheng
1c042b6efb Add to .Rbuildignore 2018-02-09 11:12:46 -08:00
Joe Cheng
b8df1f29c4 Remove unused function 2018-02-09 11:12:46 -08:00
Joe Cheng
18252f5b03 Use later >= 0.7.1 2018-02-09 11:12:46 -08:00
Joe Cheng
881370f284 Remove extraneous comments 2018-02-09 11:12:46 -08:00
Joe Cheng
35d1747bc3 Don't allow invalidation from a child process 2018-02-09 11:12:46 -08:00
Joe Cheng
91ac89a54e Update TODOs 2018-02-09 11:12:46 -08:00
Joe Cheng
3c694d9bd9 More robust process identification (thanks @HenrikBengtsson) 2018-02-09 11:12:46 -08:00
Joe Cheng
6a78e9df77 Detect (probably inadvertent) attempts to inherit reactive contexts across processes
Example (we want this to fail):

library(shiny)
library(future)
plan(multicore)

r <- reactiveVal(TRUE)
isolate({
  f <- future({
    r()
  })
  value(f)
})
2018-02-09 11:12:45 -08:00
Joe Cheng
078c6eb30a Add TODO 2018-02-09 11:12:45 -08:00
Joe Cheng
d35c6002a6 Respect pixelratio (retina) when redrawing cached images 2018-02-09 11:12:45 -08:00
Joe Cheng
f23fc3beaa Plots were not respecting pixel ratio (retina) 2018-02-09 11:12:45 -08:00
Joe Cheng
5a352e5ace Update TODOs 2018-02-09 11:12:45 -08:00
Joe Cheng
27cae0065e Fix bug where req(cancelOutput=TRUE) would leave things grey
This was introduced by some changes to shinyapp.js that were necessary
before async outputs and sync outputs were held/flushed together. Now
that async/sync outputs are held/flushed together, these changes are
not necessary and removing them fixes the problem.

The test app is in shiny-examples/205-async-req. I also moved a test
app from manualtests/async/download.R to shiny-examples/204-async-download.
2018-02-09 11:12:45 -08:00
Joe Cheng
50be2993fa Add TODO 2018-02-09 11:12:45 -08:00
Joe Cheng
d9ea15e9bc Update TODOs 2018-02-09 11:12:45 -08:00
Joe Cheng
03b1d45d7e Make deep stack traces opt-in; fix imports 2018-02-09 11:12:45 -08:00
Joe Cheng
e48d6878c4 Update TODOs 2018-02-09 11:12:45 -08:00
Joe Cheng
1a3b255848 Update TODOs 2018-02-09 11:12:45 -08:00
Joe Cheng
f00aa94d7e Suspend session during async download operation 2018-02-09 11:12:45 -08:00
Joe Cheng
f7980b19f4 Update TODOs 2018-02-09 11:12:45 -08:00
Joe Cheng
6a1f9677a5 Support async downloadHandler content functions
If a downloadHandler content function returns a promise (or future)
then Shiny will wait for the promise to resolve before serving up
the file download.
2018-02-09 11:12:45 -08:00
Joe Cheng
e844bb36a5 Update TODOs 2018-02-09 11:12:45 -08:00
Joe Cheng
ae364adfc2 wip2 2018-02-09 11:12:45 -08:00
Joe Cheng
c14a382b90 wip 2018-02-09 11:12:45 -08:00
Joe Cheng
da9c2beaaf Update TODO 2018-02-09 11:12:45 -08:00
Joe Cheng
a4a56476db Update TODO 2018-02-09 11:12:45 -08:00
Joe Cheng
39d3784b9b async support for renderDataTable 2018-02-09 11:12:45 -08:00
Joe Cheng
7d29df58f1 Support same-tick execution for synchronous outputs 2018-02-09 11:12:45 -08:00
Joe Cheng
05aa413683 promises::finally() was missing namespace prefix 2018-02-09 11:12:45 -08:00
Joe Cheng
132f90f45b Support promise domain wrapSync; fix renderPrint visibility
Also introduce promise_chain and hybrid_chain, for assembling chains of
operations without involving magrittr-style operators
2018-02-09 11:12:45 -08:00
Joe Cheng
4526fd1917 Update TODO 2018-02-09 11:12:45 -08:00
Joe Cheng
2602dc15b0 Changes to flush cycle to support async
- Moved (in|de)crementBusyCount calls out of Context and into Observer
- decrementBusyCount is (effectively) deferred for async observers until
  the async operation is complete
- invalidateLater didn't force(session), almost certainly was buggy
- invalidateLater, reactiveTimer, and manageInputs all now use a new
  session$cycleStartAction, which delays their effect until observers
  (including async ones) are done executing
2018-02-09 11:12:45 -08:00
Joe Cheng
2314f63424 Fix broken .shiny__stdout mechanism 2018-02-09 11:12:45 -08:00
Joe Cheng
c2410600ee Refactor list of TODOs 2018-02-09 11:12:45 -08:00
Joe Cheng
f7e4702685 Restore writing of _n_flushReact and _x_flushReact to stdout 2018-02-09 11:12:45 -08:00
Joe Cheng
71682512c4 Refactor flush cycle 2018-02-09 11:12:45 -08:00
Joe Cheng
20b82fbf77 Cleanup R CMD check 2018-02-09 11:12:45 -08:00
Joe Cheng
631f09847d Update TODOs 2018-02-09 11:12:45 -08:00
Joe Cheng
671585f68a Update TODOs 2018-02-09 11:12:45 -08:00
Joe Cheng
5feed888bb Add promises to remotes 2018-02-09 11:12:45 -08:00
Joe Cheng
47bef0f1b0 Remove extraneous debugging code 2018-02-09 11:12:45 -08:00
Joe Cheng
c1dc662a40 "promise" package was renamed to "promises" 2018-02-09 11:12:45 -08:00
Joe Cheng
16e1721fe8 Add TODO comment 2018-02-09 11:12:45 -08:00
Joe Cheng
f406e13600 Add TODO 2018-02-09 11:12:45 -08:00
Joe Cheng
9063133a7b Fix various rendering bugs 2018-02-09 11:12:45 -08:00
Joe Cheng
3fbb436187 Add call_async temporarily; this should probably live somewhere else 2018-02-09 11:12:45 -08:00
Joe Cheng
7c845d070b Sync to changes in promise. run_now aggressively in serviceApp. 2018-02-09 11:12:45 -08:00
Joe Cheng
5e905aa73e Implement execOnResize 2018-02-09 11:12:45 -08:00
Joe Cheng
e15654f265 Minor renderPlot cleanup 2018-02-09 11:12:45 -08:00
Joe Cheng
369c067efc Update TODO 2018-02-09 11:12:45 -08:00
Joe Cheng
c037e69793 Fix ggplot brushing 2018-02-09 11:12:45 -08:00
Joe Cheng
8c935ff44e Use proper promise:: prefix 2018-02-09 11:12:45 -08:00
Joe Cheng
74bf8b0554 renderPlots works!!! Testing needed. 2018-02-09 11:12:45 -08:00
Joe Cheng
6345972efe Adapt promise domains to handle multiple arguments 2018-02-09 11:11:35 -08:00
Joe Cheng
16242e87a1 Some steps toward renderPlot working. Move to promise package instead of system2.5. 2018-02-09 11:11:34 -08:00
Joe Cheng
8155320ba5 wip 2018-02-09 11:09:28 -08:00
Winston Chang
39a7f63972 Update NEWS 2018-01-29 13:45:23 -06:00
Dmitriy Selivanov
7b72209277 fixes #1859 (#1861)
* style & formatting. fixes #1859

* prepare `choices` in `updateSelectizeInput()` as per discussion in #1861

* remove duplicated block in selectizeJSON, simply `lab` assignement logic
2018-01-29 13:41:30 -06:00
Joe Cheng
cad20a0bfe Merge pull request #1856 from rstudio/wch-do-call
Quote arguments to do.call() for nicer stack traces
2017-10-31 11:28:47 -07:00
Winston Chang
ba8d79f202 Fix version text substitution 2017-10-17 10:51:19 -05:00
Winston Chang
176fe699b9 Point to RStudio Community website instead of shiny-discuss 2017-09-27 15:54:05 -05:00
Winston Chang
213ee7be13 Quote arguments to do.call() for nicer stack traces. Closes #1851 2017-09-26 14:36:19 -05:00
Joe Cheng
48fd869c71 Merge pull request #1848 from rstudio/barbara/fix/icon
Revert back the relative position of the icon and title in tabPanel's and navbarMenu's
2017-09-12 07:34:31 -07:00
Barbara Borges Ribeiro
53e47484e2 reverted the relative positioning of the icon and the title text in navbarMenus and tabPanels back to what it was before Shiny 1.0.5 (fixes #1840) 2017-09-12 12:16:06 +01:00
Winston Chang
dc18b20e5a Don't copy httpuv::decodeURIComponent at build time 2017-09-07 21:31:32 -05:00
Barbara Borges Ribeiro
b4c5debbdf Merge pull request #1844 from rstudio/barbara/fix/reactlog
Changed script tags in reactlog from HTTP to HTTPS
2017-09-07 01:43:46 +01:00
Barbara Borges Ribeiro
771d3d52b9 Changed script tags in reactlog from HTTP to HTTPS in order to avoid mixed content blocking by most browsers (thanks @jekriske-lilly) 2017-09-07 01:34:17 +01:00
Joe Cheng
2a53ac093d Merge pull request #1830 from rstudio/wch-compare-version
Add Shiny.compareVersion() function
2017-09-05 11:37:17 -07:00
Winston Chang
4fa2af72cc Avoid port 6697. Closes #1784 2017-08-28 16:40:51 -05:00
Winston Chang
e512d3cd61 Grunt 2017-08-25 14:46:19 -05:00
Winston Chang
16b7ee3985 Add Shiny.compareVersion() function 2017-08-25 14:46:06 -05:00
Winston Chang
4f3d26c31b Add Shiny.version to Javascript (#1826)
* Add Shiny.version to Javascript

* Grunt
2017-08-23 15:52:16 -05:00
Winston Chang
587bf94d69 Merge tag 'v1.0.5'
Shiny 1.0.5 on CRAN
2017-08-23 15:27:56 -05:00
Winston Chang
635ad77e0d Bump version to 1.0.5 2017-08-23 13:11:59 -05:00
Winston Chang
33258da6c3 Bump version to 1.0.5.9000 2017-08-23 13:07:15 -05:00
Joe Cheng
c2b3c3379d Fix #1824: HEAD request on static files causes app to stop (#1825)
* Fix #1824: HEAD request on static files causes app to stop

The problem was that for HEAD requests specifically, we implement
an explicit Content-Length header (normally we let httpuv figure
out the Content-Length based on the content, but for HEAD we don't
return any content but still want to include the Content-Length).

The Content-Length header was only implemented correctly for string
values, not for raw vectors or file-by-path. This change implements
the value correctly for all currently valid httpuv content.

* Update NEWS

* Code review feedback
2017-08-23 13:01:22 -05:00
Winston Chang
e30fac02ed Add safe wrapper for fromJSON 2017-08-21 19:55:48 -05:00
Winston Chang
e74592a654 Escape a few more characters for conditionalPanel expressions 2017-08-21 14:25:20 -05:00
Joe Cheng
ebd47aa73b Merge pull request #1820 from rstudio/wch-fix-conditionalpanel
Escape newline characters in conditionalPanel expression
2017-08-18 20:49:48 -04:00
Winston Chang
e2d19cbaba Grunt 2017-08-18 17:24:06 -05:00
Winston Chang
1f864a846f Escape newline chars in conditionalPanel expr. Fixes #1818 2017-08-18 17:24:06 -05:00
Winston Chang
fc32c2c944 Clarify that choices must be strings 2017-08-18 11:48:41 -05:00
Winston Chang
279e37f1cb Bump version to 1.0.4.9000 2017-08-18 11:47:19 -05:00
Winston Chang
3f9176176e Merge tag 'v1.0.4'
Shiny 1.0.4 on CRAN
2017-08-14 12:01:26 -05:00
Winston Chang
b3201ccafd Add functions to news item 2017-08-10 14:29:06 -05:00
Winston Chang
2a01a620a9 Add NEWS summary 2017-08-10 12:03:22 -05:00
Winston Chang
6f43cf7b82 NEWS cleanup 2017-08-09 18:49:34 -05:00
Winston Chang
1c6250f9c2 Bump version to 1.0.4 2017-08-09 18:49:34 -05:00
Barbara Borges Ribeiro
650075a9ab Fix appendTab for empty tabsetPanels (#1813)
* fix appendTab for empty tabsetPanels; use spread operator to avoid having to resort to apply; upgrade grunt.

* revert back to `Math.max.apply(null, existingTabIds) + 1;` there's no browser compatibility issues there
2017-08-09 18:45:25 -05:00
Winston Chang
668ee6f24a Add references to issues 2017-08-08 11:16:57 -05:00
Alan Dipert
c456ec2c4c drag/drop-able fileInputs (#1782)
* fileInput WIP: Show dropzones when file dragged over window

- Still need to validate dataTransfer contents

* WIP: Basic functionality working

* wip

* Grunt

* WIP state machine

* WIP generalize FSM to data+multimethod

* WIP multimethod

* WIP draghover

* wip multimethod

* WIP, such refactor

* WIP: rm multimethod

* WIP

* WIP resurrect multimethod

* WIP move draghover functions into input object

* WIP colors: use more muted, bootstrap-esque glows

* Grunt

* WIP: use whenAny, more descriptive args in default test/dispatch fns

* WIP more whenAny

* Grunt

* WIP dont use for...of, requires polyfill

* Grunt

* multimethod improvements, documentation. `equal` function.

* multimethod: simplified equal, removed need for forward decl. docs.

* dox

* multimethod improvements, docs

* minor

* IE 10+ drag/drop, first cut

* Grunt

* use functions not arrows for faux instance methods

* Grunt

* fix uploadDropped call

* Grunt

* cleanup drop handler, fix entry to invalid state via doc drop handler

* Grunt

* IE workaround #293932

* Grunt

* yeeeeeeeeeeessss IE WORKSSSSS

* Cleanup; support activeClass/overClass

* everything basically works everywhere \o/

* revert ability to specify classes, hardcode in JS

* MM fixes

* minor fixes

* Grunt

* DnD: Support dragging directly over zones
- Happens when source window occludes browser window

* woo

* Note Safari bug, use draghover for zones

* merge

* Grunt

* news

* include CSS
2017-08-08 11:12:21 -05:00
Joe Cheng
3b0c390a9e Merge pull request #1794 from rstudio/barbara/tabs
Dynamic tabs
2017-08-04 11:31:50 -07:00
Barbara Borges Ribeiro
b02eb11345 do inputId <- session$ns(inputId) in user facing functions for module functionality (rather than overriding the same functions in makeScope) 2017-08-04 18:02:43 +01:00
Barbara Borges Ribeiro
ed3ba303bc Joe's feedback 2017-08-04 17:56:58 +01:00
Barbara Borges Ribeiro
ee5da1410e make hide and remove work well when we want to hide/remove a tab inside a navbarMenu (or the whole menu) and it is selected (before this commit, it wasn't navigating to the first tab like it is supposed to) 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
494627c6e1 make this PR work for modules 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
82ac112dec added select argument to showTab function 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
40cfff33ff for dynamic tabs, send message on session$onFlush (instead of session$onFlushed) 2017-08-04 15:10:08 +01:00
Joe Cheng
c1c5873912 Abandon nearest neighbor tab-showing logic. Just grab the first tab. 2017-08-04 15:10:08 +01:00
Joe Cheng
c090efd562 Fix bug where last tab being removed, didn't update tabset input value 2017-08-04 15:10:08 +01:00
Joe Cheng
91dbb0e77b htmlDependencies are properly loaded with dynamic tabs 2017-08-04 15:10:08 +01:00
Joe Cheng
dde7b144f0 Add select=FALSE argument to insert/append/prependTab 2017-08-04 15:10:08 +01:00
Joe Cheng
f1873a014c Make tab prepend/append just edge cases of insert 2017-08-04 15:10:08 +01:00
Joe Cheng
48b8923b67 Properly escape jQuery selector strings 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
6f9f3fea83 implement navigation after hiding/removing selected tab 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
10f3320165 more JS code refactoring; improved documentation 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
d57aa33b40 insertion fully implemented 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
0e7c78bae3 refactored code and made insertion of navbarMenus possible 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
e6602786ec updated docs 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
31bbb3894c remove extra line 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
8bbf576807 typo: tag -> tab (makes a big difference!) 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
1ecc9b9d0e Fixed documentation problems and JS code logic 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
3adbebc3d9 document similar things together; add prependTab and appendTab 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
a4c086f51b now working for navbarPage and navlistPanel 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
0ecdcec698 clean up JS code (1 line only) 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
ae7f026d46 added NEWS and fixed typo 2017-08-04 15:10:08 +01:00
Barbara Borges Ribeiro
2813e0b706 update examples 2017-08-04 15:09:00 +01:00
Barbara Borges Ribeiro
a409562d00 delete extra brackets 2017-08-04 15:09:00 +01:00
Barbara Borges Ribeiro
b6b6661ea1 implement showTab and removeTab 2017-08-04 15:09:00 +01:00
Barbara Borges Ribeiro
fb7b6f667c implement removeTab 2017-08-04 15:09:00 +01:00
Barbara Borges Ribeiro
b94efe81e4 finish insertTab 2017-08-04 15:09:00 +01:00
Barbara Borges Ribeiro
72a1b3d2a0 add functions to index.r 2017-08-04 15:09:00 +01:00
Barbara Borges Ribeiro
20bff18bd4 changes 2017-08-04 15:09:00 +01:00
Winston Chang
ba5c5ef4fb Move isRunning function to better location 2017-07-27 14:56:01 -05:00
Barbara Borges Ribeiro
aff3ac0bb3 Add onStop function (#1770)
* NEWS item

* added `onStop` arg to `shinyApp()` (and renamed our internal `onEnd` - which is what was calling `on.exit()` already - to `onStop` as well)

* added onStop() function

* add entry for documentation

* make it work for all possible app structures (interactive, saved as app.R, saved as ui.R and server.R)

* fix #1772: make sure `onStart` works in all scenarios

* update NEWS

* improved wording

* more wording

* and more wording

* don't stop execution if a `onStop` callback function results in an error

* remove "(all sessions have been disconnected)" because it's misleading

* add @seealso documentation

* shamefully forgot to Cmd Shift D

* change code place

* Code review feedback

* onStop: use session argument instead of scope
2017-07-27 14:54:55 -05:00
Winston Chang
2c350daf01 Merge pull request #1802 from rstudio/bugfix/rstudio-debugger
work around RStudio debugger issue (closes #1474)
2017-07-27 13:14:01 -05:00
Winston Chang
cb7627c736 Update NEWS and add comment 2017-07-27 13:13:31 -05:00
Kevin Ushey
f731a5cae4 work around RStudio debugger issue (closes #1474) 2017-07-27 10:37:56 -07:00
Winston Chang
07cb7c9305 Add 'setSerializer' function (#1792) 2017-07-18 17:01:06 -05:00
Winston Chang
86e9cc4896 Add preprocessor for fileInputs that strips local path (#1789)
* Add preprocessor for fileInputs that strips local path

* Update NEWS

* Rename snapshotPreprocess to snapshotPreprocessOutput

* Add snapshotPreprocessInput function

* Remove unnecessary NEWS item

* Update NEWS

* Add getSnapshotPreprocessInput

* Add staticdocs entry for snapshotPreprocessInput

* Add private methods to get snapshotPreprocess functions

* Bump version to 1.0.3.9002
2017-07-13 16:07:16 -05:00
Joe Cheng
12c9405257 Merge pull request #1790 from rstudio/wch-warn-level
Don't reduce warn level when running app
2017-07-12 22:41:21 -07:00
Winston Chang
4708b44c59 Don't reduce warn level when running app. Fixes #1680 2017-07-12 19:29:49 -05:00
Winston Chang
4cb428bb92 Add a function to test if an app is running (#1785)
Squashed commit of the following:

commit 8667bed8962069a5cab8691f981e2b7ba9d449c3
Author: Winston Chang <winston@stdout.org>
Date:   Tue Jul 11 14:36:11 2017 -0500

    Edits

commit c4e8549ca5
Author: Konrad Rudolph <konrad.rudolph@gmail.com>
Date:   Fri Jul 7 17:57:33 2017 +0100

    Describe changes

commit 7b05c2e60f
Author: Konrad Rudolph <konrad.rudolph@gmail.com>
Date:   Fri Jul 7 17:54:40 2017 +0100

    Add new function to doc index

commit eb93ebfad8
Author: Konrad Rudolph <konrad.rudolph@gmail.com>
Date:   Fri Jul 7 17:54:30 2017 +0100

    Add documentatio for new function

commit 1a6c8a4d72
Author: Konrad Rudolph <konrad.rudolph@gmail.com>
Date:   Fri Jul 7 17:53:13 2017 +0100

    Add a function to test whether the app is running
2017-07-11 14:36:59 -05:00
Mine Cetinkaya-Rundel
d7391b19bc Convert examples to single file apps (#1685)
* - Convert all example apps to single file app.R file
- Make relevant updates to Readmes to match up with app.R structure
- Add color to plots (RStudio blue)
- In 04_mpg example: Show outliers by default, as opposed to hide, since this is more routine
- In 06_tabsets and 08_html examples: Don't name random data vector "data"
- Add extensive comments to app.R files and use consistent formatting of comments across examples
- In 09_upload example: Use req() to check for NULL entry

* add news entry summarizing changes

* use true RStudio blue, #75AADB

* Conver shinyApp calls at the end to drop argument name in examples 3-11, except for the custom HTML example. Kept them in for examples 1&2 for completeness in first exporuse to function.

* Pull news items that got added before this PR was merged

* Update comment for shinyApp function -- it creates an app object, doesn't run the app
2017-07-11 14:20:01 -05:00
Joe Cheng
db9e56d1ca Merge pull request #1768 from rstudio/wch-fix-with-private-seed
Fix withPrivateSeed
2017-07-11 12:17:02 -07:00
Winston Chang
e527af10f4 New version of httpuv is on CRAN 2017-07-11 13:45:45 -05:00
Joe Cheng
74c7be0a6d Merge pull request #1786 from rstudio/joe/bugfix/fileinput-content-type
Use a more suitable content type for file uploads
2017-07-10 15:39:13 -07:00
Joe Cheng
2d40e7b51a Use a more suitable content type for file uploads
application/x-www-form-urlencoded;charset=UTF-8 is the default, which shinyapps.io
cares about for some reason and tries to parse the data as such. By setting the
content type to the more accurate application/octet-stream, no middleware should
be tempted to futz with the contents.
2017-07-10 15:33:42 -07:00
Winston Chang
ea407fb2ea Don't include xtable comment in renderTable by default 2017-06-27 15:05:31 -05:00
Winston Chang
fca5b0529a Remove reinitalizeSeed
This function is no longer needed because the minimum R vesion supported by Shiny is 3.0.2.
2017-06-27 10:30:50 -05:00
Winston Chang
65fd1dd2d8 Remove branch name for httpuv remote 2017-06-26 22:40:37 -05:00
Winston Chang
0a7ede3818 Add tests for random streams 2017-06-26 21:59:52 -05:00
Winston Chang
24e84f3866 Prevent private random stream from leaking out. Fixes #1763 2017-06-26 21:59:51 -05:00
Winston Chang
c1c8e46c09 Refactor withPrivateSeed 2017-06-26 21:59:51 -05:00
Winston Chang
8591e4f301 Add working app for conditionalPanel example 2017-06-23 10:14:32 -05:00
Alan Dipert
10db7ad89c Support modules in conditionalPanel (#1735) 2017-06-23 10:12:15 -05:00
Joe Cheng
4ca4f442b9 Required R version is 3.0.2 due to sourcetools 2017-06-22 19:42:49 -05:00
Winston Chang
6d5ecbc9c4 Fix indentation 2017-06-22 13:18:05 -05:00
Winston Chang
ea685a5686 Don't send local package path to client when using htmlwidgets (#1756)
* Don't send local package path to client when using htmlwidgets. Fixes #1755

* Add scrubFile option
2017-06-22 13:16:19 -05:00
Winston Chang
376d3b6e91 Merge pull request #1760 from rstudio/wch-snapshot-preprocess
Add snapshotPreprocess function
2017-06-22 13:01:09 -05:00
Winston Chang
df7397af1f Bump version 2017-06-21 14:27:03 -05:00
Winston Chang
9ba9345b04 Add snapshotPreprocess function 2017-06-21 14:27:03 -05:00
Barbara Borges Ribeiro
9fc5758ae0 Triggers a new shiny:outputinvalidated event (#1758)
* trigger a new `shiny:invalidated` event when an output gets invalidated, at the same time that the `recalculating` CSS class is added (fixes #1688)

* add attribution to @andrewsali

* change event name from 'shiny:invalidated' to 'shiny:outputinvalidated'

* add binding and name to the new event 'shiny:outputinvalidated'
2017-06-21 12:28:51 -05:00
Winston Chang
25298a6182 In test mode, send message to client even when no outputs change (#1747)
* In test mode, send message to client even when no outputs change

* Update NEWS.md
2017-06-20 13:50:28 -05:00
Winston Chang
246da1bff6 Grunt 2017-06-16 13:48:38 -05:00
Carl Ganz
8b5d12b958 Add placeholder parameter to updateTextInput (#1742)
* add placeholder parameter

* add js placeholder code

* roxygenize

* grunt

* fix updateCheckBoxInput not to use placeholder

* simply roxygen

* add NEWS

* revert grunt
2017-06-15 22:00:39 -05:00
Alan Dipert
3817370d4e fileInput JS: Allow uploading the same file. (#1719)
* tools README: notes about entr + grunt

* fileInput JS: Allow uploading the same file. Fixes #1508

* Grunt

* Added note to NEWS.

* tools README: add Linux section, fix formatting
2017-06-15 15:09:37 -05:00
Alan Dipert
c29846a9da fileInput: Preserve extension of files uploaded from IE9 (#1717)
* fileInput: IE addendum to #1706

- Attempt to preserve the extension of files uploaded from IE9.

* maybeMoveIEUpload: Fix if spacing
2017-06-15 13:47:21 -05:00
Winston Chang
2158f906a7 Merge pull request #1736 from rstudio/wch-fix-unbind
Fix condition for calling exports.unbindAll()
2017-06-15 13:05:16 -05:00
Winston Chang
008dd280d6 Grunt 2017-06-08 17:03:21 -07:00
Winston Chang
fb99db011c Fix condition for calling exports.unbindAll(). (Correction to #1449) 2017-06-08 17:02:56 -07:00
Joe Cheng
c0fbd9cb3c Merge pull request #1732 from rstudio/barbara/mods
Fixed #1546: make it possible to write into a module's session$userData non-hackily
2017-06-07 22:45:47 -07:00
Barbara Borges Ribeiro
fb79b18002 More descriptive NEWS item and added an explanatory comment to the code 2017-06-07 13:28:51 -07:00
Barbara Borges Ribeiro
3841f22108 Fixed #1546: make it possible (without any hacks) to write arbitrary data into a module's session$userData 2017-06-07 12:11:05 -07:00
Winston Chang
379d523ac5 Add better error messages for errors parsing and evaluating JS code (#1727)
* Add better error messages for errors parsing and evaluating JS code

* Grunt
2017-06-02 14:31:06 -05:00
Winston Chang
07ec7f8c13 Update Rproj for new version of RStudio IDE 2017-06-02 13:31:33 -05:00
Alan Dipert
d0f29cc7a2 fileInput: If possible, retain uploaded file extensions on the server. (#1706) 2017-05-26 11:16:02 -05:00
Joe Cheng
0e23a487f7 Merge pull request #1713 from rstudio/jjallaire-contributing-links
Update links in CONTRIBUTING.md
2017-05-23 14:05:43 -07:00
JJ Allaire
ac10f7c426 Update links in CONTRIBUTING.md
Update the links to contributor agreements to reflect new versions that use my current email rather than rstudio.org based one.

I've made the same change in the rstudio and rmarkdown repos, we should make it in other repos that have a contributor agreement as well.
2017-05-23 16:38:02 -04:00
Joe Cheng
852c00009e Merge pull request #1712 from rstudio/wch-fix-reactiveval
Give each ReactiveVal separate dependents
2017-05-22 11:15:35 -07:00
Winston Chang
b365798e66 Add tests for ReactiveVal independence 2017-05-22 10:35:01 -05:00
Winston Chang
66a6097a49 Give each ReactiveVal separate dependents. Fixes #1710 2017-05-22 10:34:27 -05:00
Winston Chang
0e529d3d92 Fix partial arg match. Closes #1701 2017-05-10 10:08:05 -05:00
Winston Chang
06c75dd656 Bump version to 1.0.3.9000 2017-04-28 09:45:17 -05:00
Winston Chang
69c32d4d90 Bump version to 1.0.3 2017-04-25 15:33:10 -05:00
Winston Chang
36ffebd975 Workaround for NOTE about objects in yet-unreleased version of ggplot2 2017-04-25 15:33:10 -05:00
Winston Chang
deb56539fb Better reactivePoll example. Closes #1678 2017-04-25 10:48:29 -05:00
Winston Chang
af8d099b9f Don't call body(NULL). Fixes #1676 2017-04-24 13:42:22 -05:00
Winston Chang
eed869d321 Make fileInput progress bar change color on error (#1673)
* Make fileInput progress bar change color on error. Fixes #1672

* Grunt

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

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

* new entry

* update NEWS

* correct version number in NEWS.md

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

* use vapply

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

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

* Update NEWS

* Use vectorized ns()

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

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

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

* Add opts argument to setInput methods

* Extract input values without opts

* Consistent interface for setting initial values

* Update NEWS

* Add binding and el when fileInputBinding triggers shiny:inputchanged

* Revert "Consistent interface for setting initial values"

This reverts commit 12c0b6e72a.

* Move InputDeferDecorater function

The new placement properly reflects the decorator stack

* Fix indentation

* bindInputs: make sure value is set immediately

* Only use opts where necessary in input decorators

* Properly send initial values

* Move initial value of .clientdata_allowDataUriScheme to better place

* Fix indentation

* Add InputValidateDecorator

* Better variable name

* Add function for default input options

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

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

* Simplify code when slider separator is ""

* Add links to NEWS
2017-01-17 12:00:47 -05:00
Winston Chang
61831f530f Merge pull request #1525 from rstudio/wch/ggplot-coord-fixed
Add plot interaction support for coord_fixed
2017-01-16 13:01:01 -05:00
Winston Chang
6065db1d24 Update NEWS 2017-01-16 13:00:41 -05:00
Winston Chang
270b8415e8 Add plot interaction support for coord_fixed. Closes #1121 2017-01-16 12:58:58 -05:00
Winston Chang
1987331a70 Bump version to 1.0.0.9000 2017-01-16 12:55:36 -05:00
Winston Chang
ab85216b96 Merge tag 'v1.0.0' 2017-01-13 13:36:23 -05:00
Winston Chang
b5cb78c77e Update URL 2017-01-10 10:13:06 -06:00
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
Joe Cheng
e133290c57 Fix #1399: Duplicate binding error with insertUI and nested uiOutput (#1402)
* Fix #1399: Duplicate binding error with insertUI and nested uiOutput

* Update NEWS.md
2016-10-18 20:22:02 -05:00
shrektan
1429b0677e fix a typo: option() -> options() 2016-10-18 14:56:38 -05:00
Barbara Borges Ribeiro
d03ee36647 Fixes #1427: add event delegation so that modals do not close by mistake (#1430)
* Fixes #1427: add event delegation so that modal does not close when an element inside it is triggered as hidden

* use `this === e.target` instead

* added NEWS item

* `e.target` must be equal to `$(#shiny-modal)`, not `this`
2016-10-18 14:54:27 -05:00
Winston Chang
6e5880c642 Bump version and update NEWS 2016-10-18 13:51:43 -05:00
Winston Chang
fa93cffafb Add entry to staticdocs 2016-10-18 13:51:43 -05:00
Winston Chang
ce9af0fb57 Export function that applies input handlers 2016-10-18 13:51:43 -05:00
Winston Chang
95700d8d51 Fix dategrange comment 2016-10-17 13:37:25 -05:00
Winston Chang
fb15e98519 Merge pull request #1429 from rstudio/slider-setvalue
Make sliderInputBinding.setValue update value immediately
2016-10-17 12:29:14 -05:00
Winston Chang
3054cb7971 Update NEWS 2016-10-17 12:28:36 -05:00
Winston Chang
f84587cf5a Grunt 2016-10-17 12:22:21 -05:00
Winston Chang
538f38f314 sliderInputBinding: setValue changes value immediately 2016-10-17 12:22:21 -05:00
Winston Chang
06578349c7 Document InputBinding.subscribe's callback argument 2016-10-17 12:18:12 -05:00
Winston Chang
a807476171 sliderInputBinding: rename 'updating' to 'immediate' 2016-10-17 12:13:15 -05:00
Winston Chang
7aacf9ca89 Use Yarn instead of npm (#1416) 2016-10-12 12:51:05 -05:00
Winston Chang
50dae5fb83 Remove unneeded npm package 2016-10-11 13:04:38 -05:00
Winston Chang
0853c425fe Bump version to 0.14.1.9000 in DESCRIPTION 2016-10-11 12:59:06 -05:00
Barbara Borges Ribeiro
edcc676693 add "fade" arg to modalDialog() (#1414)
* add "fade" arg to modalDialog() that can be set to FALSE to remove default modal animation

* added documentation

* reflow comments

* news item
2016-10-10 15:03:25 -05:00
Winston Chang
c8a742a121 Bump version and update NEWS 2016-10-05 09:32:36 -05:00
Winston Chang
ee14a7e15f Merge tag 'v0.14.1'
Shiny 0.14.1 on CRAN
2016-10-05 09:29:07 -05:00
Winston Chang
e1eaccf409 Fix tests for compiled code on R-devel. Closes #1404 2016-10-03 16:23:11 -05:00
Winston Chang
d2aae52868 Update NEWS 2016-09-30 15:39:31 -05:00
Winston Chang
9158fb4745 Bump version to 0.14.1 2016-09-30 15:39:31 -05:00
Winston Chang
0ff5ef5337 Remove file 2016-09-30 15:39:13 -05:00
Joe Cheng
1ace145f85 Merge pull request #1392 from rstudio/ggplot-fix
Add plot interaction support for ggplot>2.1.0
2016-09-30 12:40:30 -07:00
Winston Chang
565eb4b450 Merge pull request #1397 from rstudio/barbara/bugfix/radio
Maintain names of factors when updating radio buttons' choices
2016-09-29 14:18:24 -05:00
Barbara Borges Ribeiro
f39861c43f more comments and NEWS 2016-09-29 19:48:56 +01:00
Barbara Borges Ribeiro
72838c248f news entry 2016-09-29 19:16:46 +01:00
Barbara Borges Ribeiro
9be8765ccf more tests 2016-09-29 19:14:18 +01:00
Barbara Borges Ribeiro
48732c4393 deleted commented out lines 2016-09-29 19:14:18 +01:00
Barbara Borges Ribeiro
5bf0b7c920 a better fix 2016-09-29 19:14:18 +01:00
Barbara Borges Ribeiro
51a4580d0f maintain names of factors when updating radio buttons' choices 2016-09-29 19:14:18 +01:00
Winston Chang
266e611afa Update NEWS 2016-09-27 23:07:23 -05:00
Winston Chang
22598b693c Add more plot interaction tests 2016-09-27 23:07:23 -05:00
Winston Chang
008fe38f10 Add support for coord_flip 2016-09-27 23:07:17 -05:00
Winston Chang
24e8123240 Add plot interaction support for ggplot>2.1.0 2016-09-27 16:02:28 -05:00
Simon Müller
6054f03c0d Update update-input.R 2016-09-21 22:53:41 +02:00
Winston Chang
476f6d83e2 Remove no-longer-necessary CSS class 2016-09-19 10:22:40 -05:00
Winston Chang
ec57109f39 Merge pull request #1374 from rstudio/datepicker-noconflict
Fix datepicker conflicts
2016-09-19 09:30:22 -05:00
Winston Chang
d73488f887 Grunt 2016-09-19 09:27:28 -05:00
Winston Chang
3201380c29 Set value after min in max when updating dates 2016-09-19 09:27:28 -05:00
Winston Chang
1f04b39ae3 Rename bootstrapDP to bsDatepicker 2016-09-16 23:39:26 -05:00
Winston Chang
9e2b47027c Update NEWS 2016-09-16 23:39:26 -05:00
Winston Chang
662149a98a Update to jQuery UI 1.12.1 2016-09-16 23:39:26 -05:00
Winston Chang
fafa31589d Update NEWS 2016-09-16 23:39:26 -05:00
Winston Chang
43a5940b9e Updates to dateRangeInputBinding for new datepicker API 2016-09-16 23:39:26 -05:00
Winston Chang
33908624fd Fix off-by-one error for datepicker's setStartDate and setEndDate 2016-09-16 23:39:26 -05:00
Winston Chang
ffef8a341f Add workaround for bootstrap datepicker bug with setStartDate and setEndDate 2016-09-16 23:39:26 -05:00
Winston Chang
a48c5df844 Don't try to set min/max date when undefined 2016-09-16 23:39:26 -05:00
Winston Chang
37b6a668ab Fix off-by-one dates 2016-09-16 23:39:26 -05:00
Winston Chang
2a9a7cc897 Enable noconflict for Bootstrap Datepicker. Closes #1346 2016-09-16 23:39:26 -05:00
Winston Chang
c62e6b5734 Update to Bootstrap Datepicker 1.6.4. Closes #1218 2016-09-16 23:39:26 -05:00
Winston Chang
6ec1d0b935 grunt clean && grunt 2016-09-16 23:37:31 -05:00
Winston Chang
6c5769fdd8 Add missing files to Grunt clean 2016-09-16 23:36:57 -05:00
Winston Chang
09acc5920c Allow using no separator for sliderInput numbers. Fixes #1369 2016-09-15 12:03:25 -05:00
Winston Chang
9613c58bf8 Merge pull request #1370 from rstudio/fix-bookmark-fileinput
Fix bookmark fileinput
2016-09-13 15:38:52 -05:00
Winston Chang
147f9ac64b Remove sourcetools workaround for Travis
This is no longer necessary because the new version of sourcetools on CRAN
does not need C++11
2016-09-13 12:52:15 -05:00
Winston Chang
cc1e8961a1 Update NEWS 2016-09-13 11:24:42 -05:00
Winston Chang
3b1b2f401d Use createUniqueId for consistency 2016-09-13 11:14:29 -05:00
Winston Chang
58a87b9b61 Copy restored file to temp directory 2016-09-13 11:13:21 -05:00
Winston Chang
f09475a6b5 Better check for unsafe paths in restored file inputs 2016-09-13 11:12:16 -05:00
Winston Chang
750422d858 Mark restored file inputs with correct serializer. Closes #1368 2016-09-13 10:49:08 -05:00
Winston Chang
03d911d335 Use cache for AppVeyor 2016-09-13 10:47:00 -05:00
Winston Chang
2747c11a46 Bump version to 0.14.0.9000 2016-09-13 10:46:15 -05:00
Winston Chang
5a9fe2637f Update shiny.rstudio.com URLs in NEWS 2016-09-12 09:36:32 -05:00
Winston Chang
a5787f9988 Bump version to 0.14 2016-09-08 15:03:19 -05:00
Winston Chang
85e22bb515 Ignore revdep 2016-09-08 15:03:19 -05:00
Barbara Borges Ribeiro
5e1e90ad80 fix validation bug 2016-09-07 19:22:02 +01:00
Winston Chang
fe85421c7e Fixes for R CMD check 2016-09-06 14:43:57 -05:00
Winston Chang
38af6ce279 Merge pull request #1362 from rstudio/barbara/update-old-release-news
Barbara/update old release news
2016-09-06 11:05:24 -05:00
Barbara Borges Ribeiro
fe92f16da4 updated 0.12 section 2016-09-05 20:55:38 +01:00
Barbara Borges Ribeiro
edc4b562f2 removed unnecessary line breaks now that we switched to .md 2016-09-05 20:42:20 +01:00
Barbara Borges Ribeiro
7b6a91064c updated 0.11 section 2016-09-05 19:52:28 +01:00
Winston Chang
a32414c6fc NEWS edits 2016-09-02 21:09:39 -05:00
Winston Chang
259b4e29de NEWS improvements (#1354) 2016-09-02 16:28:08 -05:00
Winston Chang
e56f80b546 Check whether hosting environment supports bookmarking (#1360)
* Check whether hosting environment supports bookmarking

* Show notification on startup if hosting environment doesn't support saved-to-server bookmarking
2016-09-02 14:19:41 -07:00
Barbara Borges Ribeiro
1ff52c5290 Merge branch 'master' of https://github.com/rstudio/shiny 2016-09-02 00:42:26 +01:00
Barbara Borges Ribeiro
70bd249f43 make explicit link to external package 2016-09-02 00:42:14 +01:00
Winston Chang
f2f7e43579 Convert validate example to single-file app. Closes #1345 (#1347) 2016-09-01 13:08:03 -07:00
Winston Chang
c36d60fcd4 Fix reactive highlighting in showcase mode. Closes #1350 (#1351) 2016-09-01 13:07:39 -07:00
Barbara Borges Ribeiro
0950f307d1 Merge pull request #1344 from rstudio/joe/feature/root-scope
Let modules get at the root scope (undocumented for now)
2016-09-01 04:58:17 +01:00
Joe Cheng
a9b7e4a85e Let modules get at the root scope (undocumented for now) 2016-08-31 14:44:45 -07:00
Winston Chang
912a886539 updateQueryString: add note about not working in IE9. Closes #1336 (#1339) 2016-08-31 12:17:49 -07:00
Winston Chang
f7484f49e5 Fix file uploads in IE9. Closes #1332 (#1342)
* Fix file uploads in IE9. Closes #1332

* Grunt
2016-08-31 12:17:24 -07:00
Joe Cheng
9f68be1925 Merge pull request #1341 from rstudio/ie9-fix-remove-modal
Fix modal removal for IE 9
2016-08-31 12:02:43 -07:00
Joe Cheng
ef298f8d7a Merge pull request #1337 from rstudio/fix-replay-plot
Make sure displaylist is on for recording/replaying plots
2016-08-31 12:01:32 -07:00
Winston Chang
c038f0e6ee Grunt 2016-08-31 11:36:16 -05:00
Winston Chang
3c53a93447 Wrap DOM object in jQuery. Fixes #1335 2016-08-31 11:35:48 -05:00
Winston Chang
7e86e65cce Make sure displaylist is on for recording/replaying plots 2016-08-30 20:09:45 -05:00
Winston Chang
ad171d6cbb Better checks in modal example 2016-08-30 17:02:07 -05:00
Joe Cheng
76ffc20836 Merge pull request #1329 from rstudio/progress-compatibility
Add old-style rendering option to progress bars
2016-08-30 14:50:32 -07:00
Winston Chang
c4cc5b6dfc Fix renderPlot's execOnResize logic
This was found in #1331, but the real problem with that issue is that
the mclust::mclust2Dplot function has changed since we wrote the example
app.
2016-08-30 16:37:50 -05:00
Winston Chang
878c9210d3 Add shinyOption for progress.style 2016-08-30 16:15:55 -05:00
Winston Chang
35c982b367 Grunt 2016-08-29 19:48:56 -05:00
Winston Chang
9c4ff080af Update NEWS 2016-08-29 19:47:12 -05:00
Winston Chang
d32ca64a03 Add old-style rendering as an option for progress 2016-08-29 16:05:43 -05:00
Winston Chang
53b89390be Rename shiny-progress CSS class to shiny-progress-notification 2016-08-29 15:19:45 -05:00
Winston Chang
a8e09d7fe6 Add sections to NEWS and add some new items 2016-08-26 12:26:30 -05:00
Winston Chang
0c7cf20e7e Switch NEWS to Markdown 2016-08-26 11:43:38 -05:00
Winston Chang
6ebcee33c5 Add some issue numbers to NEWS 2016-08-26 11:39:20 -05:00
Winston Chang
c73544fb59 Fix misplaced NEWS items 2016-08-26 11:29:10 -05:00
Winston Chang
37c1f93bcb Merge pull request #1320 from rstudio/joe/docs/sendBinaryMessage
NEWS, doc update for sendBinaryMessage
2016-08-25 20:25:51 -05:00
Joe Cheng
95aa2e10fc NEWS, doc update for sendBinaryMessage 2016-08-25 15:48:53 -07:00
Joe Cheng
279e6e3925 Merge remote-tracking branch 'origin/daef/feature/binary-messages' 2016-08-25 13:08:29 -07:00
Joe Cheng
8a661d5ee4 Code review feedback 2016-08-25 13:04:58 -07:00
Joe Cheng
67fcb40455 Merge pull request #1313 from rstudio/bookmarking-arg
Add enableBookmarking argument to shinyApp()
2016-08-25 13:00:04 -07:00
Winston Chang
641524c80e More docs for enableBookmarking 2016-08-24 17:00:59 -05:00
Winston Chang
55802354d4 Add enableBookmarking arg to shinyApp() 2016-08-24 14:47:56 -05:00
Winston Chang
75f4f5c0bd Merge pull request #1317 from MangoTheCat/fix/updatedaterangeinteraction
Fix updateDateRangeInput interaction from #1299 & #1315
2016-08-24 13:43:55 -05:00
Gábor Csárdi
382e9dee55 Grunt 2016-08-23 22:52:41 +01:00
Gábor Csárdi
6861d4029e Fix updateDateRangeInput interaction from #1299 & #1315 2016-08-23 22:51:18 +01:00
Joe Cheng
370ef16854 Slight tweaks
- Rename arguments to type/message
- Check tag length after converting to bytes
2016-08-23 14:08:04 -07:00
Gábor Csárdi
9dbe434792 Fix updateDateRangeInput when only one of start/end updated (#1315) 2016-08-23 16:07:53 -05:00
Joe Cheng
21a0e95623 Merge commit 'd1353e8eaebc3f878fe9074919948d662caf8a89' 2016-08-23 13:58:40 -07:00
Winston Chang
57c6307479 Merge pull request #1314 from rstudio/joe/bugfix/downloadhandler-no-data
Fix #1122: Do something sensible when downloadHandler doesn't create a file
2016-08-23 15:50:39 -05:00
Winston Chang
01d8b1f468 Merge pull request #1299 from MangoTheCat/fix/resetdateinput
Allow updateDateInput to set input fields to empty
2016-08-23 15:48:25 -05:00
Joe Cheng
ef6b82a0a3 Fix #1122: Do something sensible when downloadHandler doesn't create a
file
2016-08-23 13:48:24 -07:00
Gábor Csárdi
19b7d1a7c5 Rebuild minified files 2016-08-23 21:21:35 +01:00
Gábor Csárdi
097d901191 Updates can clear date and date range inputs 2016-08-23 21:21:10 +01:00
Winston Chang
a1b5846b29 Remove extra comma 2016-08-23 10:54:37 -05:00
Winston Chang
dbdb353e69 Add updateQueryString example 2016-08-23 10:30:12 -05:00
Winston Chang
4456eac1fd Make staticdocs tests work in R CMD check 2016-08-22 21:04:19 -05:00
Winston Chang
ba3f8f432e Update staticdocs index. Closes #1293 2016-08-22 20:38:16 -05:00
Winston Chang
bab539f52c Make it possible to have multiple bookmark buttons (#1310) 2016-08-19 21:13:27 -07:00
Winston Chang
42dbb128be Fix handling of NULLs in updateDateInput 2016-08-19 14:36:29 -05:00
Winston Chang
5e4a6cb15f Fix priority of onRestore observer 2016-08-19 14:12:57 -05:00
Winston Chang
73e45ce911 Clarify observer priority documentation 2016-08-19 14:10:24 -05:00
Winston Chang
1e40043456 Add travis fix for R-devel (thanks to @jimhester) 2016-08-19 12:12:42 -05:00
Winston Chang
7f3b952ec7 Documentation updates 2016-08-19 11:53:31 -05:00
Winston Chang
82887dc1c1 Do Travis checks on multiple R versions 2016-08-19 10:16:54 -05:00
Winston Chang
71380ab37a Replace stopWithCondition with reactiveStop 2016-08-18 16:54:42 -05:00
Winston Chang
5d00804758 Add size argument to modalDialog (#1308) 2016-08-18 14:43:57 -07:00
Winston Chang
84364c65b0 Bookmark/restore actionButtons (#1304) 2016-08-18 14:42:19 -07:00
Winston Chang
1b59b705ae Fix bookmark URL when there are no input values. Fixes #1306 2016-08-17 14:00:14 -05:00
Winston Chang
bc90fe6f99 Don't error when creating tabsets without id
The bug was introduced by #1296.
2016-08-16 20:02:33 -05:00
Winston Chang
c8d6a0833e Merge pull request #1301 from daattali/patch-2
fix typo in NEWS
2016-08-16 13:57:28 -05:00
Winston Chang
d8fc7d27ec Add Dismiss button to URL modal 2016-08-16 13:00:42 -05:00
Winston Chang
1e44b19ff0 Fix typo in bookmarkButton documentation 2016-08-16 12:28:37 -05:00
Winston Chang
cc8b2cd20e Document that enableBookamrking can be called in onStart function 2016-08-16 12:22:19 -05:00
Dean Attali
057b1e294c fix typo in NEWS 2016-08-13 01:54:43 -07:00
Winston Chang
0653e790c7 Merge pull request #1298 from rstudio/handle-malformed-dates
Don't crash on malformed date input values. Closes #803
2016-08-12 21:29:00 -05:00
Winston Chang
6d72bbcb76 Don't crash on malformed date input values. Closes #803 2016-08-12 21:28:43 -05:00
Winston Chang
59e6f08455 Merge branch 'textarea' 2016-08-12 21:26:55 -05:00
Winston Chang
8fdccf50a8 Update NEWS 2016-08-12 21:26:19 -05:00
Winston Chang
1c7e11c5d9 Textarea refinements 2016-08-12 21:26:18 -05:00
Winston Chang
1756fbbb23 Merge branch 'fix-update-date' 2016-08-12 21:23:42 -05:00
Winston Chang
7bb939ab7f Restore bookmarked tabs. Fixes #1282 (#1296) 2016-08-12 18:53:44 -07:00
Nuno Agostinho
4fa0abbd5a Add arguments height, rows and cols 2016-08-12 17:19:23 -05:00
Nuno Agostinho
1e5f0266ef Fix missing parenthesis 2016-08-12 17:19:23 -05:00
Nuno Agostinho
3dee62105e Fix value assignment of text area input 2016-08-12 17:19:23 -05:00
Nuno Agostinho
680b2323d5 Update documentation relative to textarea input 2016-08-12 17:19:23 -05:00
Nuno Agostinho
562b4dad4d Add textarea input 2016-08-12 17:19:23 -05:00
Joe Cheng
079a82dfe4 executeElapsed gets stuck returning TRUE even when nothing was executed
Fixes #1278
2016-08-12 17:19:23 -05:00
Winston Chang
16f7eb43b0 Merge branch 'joe/bugfix/too-often-flush' 2016-08-12 15:24:25 -05:00
Winston Chang
4b0ed3f224 updateDateInput: better handling of malformed dates. Closes #1179 2016-08-12 14:43:44 -05:00
Winston Chang
1d453b694d Add documentation about using selected=character(0). Closes #1182 2016-08-12 14:22:54 -05:00
Barbara Borges Ribeiro
751e8c189e fixes #1093 (#1291)
* fixes #1093

* check if NULL
2016-08-11 14:29:24 -07:00
Winston Chang
183e9a3d0b Bookmarking fixes (#1281)
* Rename invalidateReactiveValue to freezeReactiveValue

* Make onFlush and onFlushed use 'once' argument

* session$flushOutput: schedule another flush if needed

* Catch all errors before they propagate to websocket

* Restore original logic for progressKeys
2016-08-11 11:58:07 -07:00
Winston Chang
5f0f4dd485 Merge pull request #1285 from rstudio/joe/bugfix/update-radio-buttons-modules
Fix #1144: RadioButtons and checkBoxGroup do not work in modules when they are updated
2016-08-11 13:17:15 -05:00
Barbara Borges Ribeiro
20f05662aa fixes #1289 2016-08-11 15:57:31 +01:00
Joe Cheng
963471b43f Fix #1144: RadioButtons and checkBoxGroup do not work in modules when they are updated
The unqualified input ID was being used to generate name attributes
on radio and checkboxes.
2016-08-10 15:52:01 -07:00
Joe Cheng
fdb52e0243 executeElapsed gets stuck returning TRUE even when nothing was executed
Fixes #1278
2016-08-10 15:10:31 -07:00
Winston Chang
d1e4483f98 Upgrade to Font-Awesome 4.6.3. Closes #1274 2016-08-10 15:40:10 -05:00
Winston Chang
b194ada316 Upgrade to Bootstrap 3.3.7 2016-08-10 15:34:51 -05:00
Winston Chang
3a25a2dcbf Upgrade to jQuery 1.12.4. Closes #1251 2016-08-10 15:24:08 -05:00
Barbara Borges Ribeiro
85e4497fbe fixes #1270; improves docs; export isTruthy() 2016-08-10 20:45:40 +01:00
Barbara Borges Ribeiro
0bfa5e7ea6 allow sticky nodes in reactlog if users drags them (#1283) 2016-08-10 20:04:37 +01:00
Barbara Borges Ribeiro
013599890f Solve updateSelectInput bug in IE 11.0 (#1277) 2016-08-09 19:08:30 +01:00
Winston Chang
519e552405 Unset shinyOptions when set to NULL 2016-08-08 13:03:07 -05:00
Winston Chang
32a4ec49f7 Bump version and update NEWS 2016-08-05 16:19:57 -05:00
Winston Chang
3223332906 Merge pull request #1209 from wch/bookmarkable-state
Bookmarkable state
2016-08-05 16:15:12 -05:00
Winston Chang
f78bd08440 Correctly handle bookmark options for global.R and app.R 2016-08-05 16:08:49 -05:00
Winston Chang
99b5f92d7a Add showBookmarkUrlModal 2016-08-03 16:07:49 -05:00
Barbara Borges Ribeiro
1cc5e84104 Barbara/error handling/port as socket (#1263)
* catch if `port` is being used incorrectly (see issue #971)

* better error msg
2016-08-03 15:50:30 -05:00
Winston Chang
8346f5ab08 Fix enableBookmarking references 2016-08-03 14:41:37 -05:00
Winston Chang
dc60a39ba9 Fix tests to be order-insensitive 2016-08-02 13:47:42 -05:00
Winston Chang
16f7872553 Grunt 2016-08-02 11:18:25 -05:00
Winston Chang
75e7c4b2ca More informative error messages 2016-08-02 11:10:17 -05:00
Barbara Borges Ribeiro
c5d5ddd7d2 Merge pull request #1267 from rstudio/barbara/remove-example-extra-line
had forgotten to run devtools::document for #1266...
2016-08-02 04:16:22 +01:00
Barbara Borges Ribeiro
98a81e3708 had forgotten to run devtools::document 2016-08-02 04:13:35 +01:00
Barbara Borges Ribeiro
ecdbdb944a Merge pull request #1266 from rstudio/barbara/remove-example-extra-line
Barbara/remove example extra line
2016-08-02 03:51:41 +01:00
Barbara Borges Ribeiro
8a2846461b removed extra line at end of example section 2016-08-02 03:40:56 +01:00
Winston Chang
6bfb9a2f57 More details in enableBookmarking man page 2016-08-01 20:46:43 -05:00
Winston Chang
85dfb2d4eb urlModal: always show Ctrl/Cmd-C copy message 2016-08-01 20:00:05 -05:00
Barbara Borges Ribeiro
8f1d0c2b8f Barbara/appveyor (#1265) 2016-08-02 01:26:48 +01:00
Winston Chang
829494c03e Make bookmark button work when there's a submitButton 2016-08-01 16:13:43 -05:00
Winston Chang
f89fade28d Rename updateLocationBar to updateQueryString 2016-08-01 16:13:25 -05:00
Winston Chang
9081b1dadd Simplify list2env 2016-08-01 14:22:23 -05:00
Winston Chang
84dac544af Make sure state ID doesn't have invalid chars 2016-08-01 14:16:50 -05:00
Winston Chang
49f1ac333d Always get appDir when shinyApp object is created 2016-08-01 14:01:16 -05:00
Winston Chang
a2b761094b Fix bookmark values tests 2016-07-29 16:07:18 -05:00
Winston Chang
54bd3d480f Fixes for R CMD check 2016-07-29 15:59:56 -05:00
Winston Chang
b281f8fa32 Merge branch 'master' into bookmarkable-state 2016-07-29 15:47:31 -05:00
Winston Chang
a3732f845e Update bookmarking documentation 2016-07-27 16:17:21 -05:00
Winston Chang
38f6d0a020 When restoring modules, only provide state$dir if present 2016-07-26 16:24:36 -05:00
Winston Chang
a0e5da758a Ensure that state$values can't be replaced 2016-07-26 16:24:02 -05:00
Winston Chang
dcbe10c1c7 Keep state$values as an environment for save and restore 2016-07-26 14:33:04 -05:00
Joe Cheng
1f823d2a1b Fix #931: Observer memory leak (#1256)
* Fix #931: Observer memory leak

Observers were being prevented from being garbage collected by
their own onReactiveDomainEnded() event handlers. This commit
fixes that by making sure that those event handlers are only
registered when autoDestroy=TRUE, and that they are unregistered
both on destruction and when autoDestroy is changed.

* Remove extraneous self$ prefixes

* Add comment explaining autoDestroyHandle
2016-07-26 11:56:47 -07:00
Winston Chang
12d0a9e11a For session_proxy, add [[ and disallow $<- and [[<- (#1262) 2016-07-26 11:21:38 -07:00
Winston Chang
326b294c83 Change progress indicators to use notification API (#1160) 2016-07-26 11:16:08 -07:00
Winston Chang
50f213ae71 Fixes for R CMD check 2016-07-26 10:32:13 -05:00
Winston Chang
91d4fd8849 Fix behavior when onBookmark() is called multiple times in a module 2016-07-25 22:29:55 -05:00
Winston Chang
3f1985a9dc Allow modules to exclude their inputs 2016-07-25 22:10:38 -05:00
Winston Chang
573a71f09d New version of reactiveValuesToList 2016-07-25 16:32:01 -05:00
Winston Chang
57900fa287 Move methods out of ShinySaveState 2016-07-25 16:31:47 -05:00
Winston Chang
fa721d9614 More informative comments 2016-07-25 16:31:47 -05:00
Winston Chang
e64bbacf68 Define filterNamespace and unNamespace locally 2016-07-25 16:31:47 -05:00
Winston Chang
6ee2edc757 Add namespace support to reactiveValuesToList 2016-07-25 13:58:21 -05:00
Winston Chang
9ec4faf0d0 Allow modules to call onBookmark, onRestore, onRestored 2016-07-25 10:52:41 -05:00
Winston Chang
5be3ba2ffa Use Callbacks objects for bookmarking callbacks 2016-07-21 15:28:43 -05:00
Winston Chang
a5ee96656b Add onFlush, onFlushed, and onSessionEnded functions 2016-07-21 14:33:43 -05:00
Winston Chang
2db71d0323 Re-document 2016-07-21 14:33:42 -05:00
Winston Chang
dcf321047f When bookmarkStore="disable", don't restore 2016-07-21 13:55:45 -05:00
Winston Chang
4982110be9 configureBookmarking affect next shinyApp object creation 2016-07-21 12:49:36 -05:00
Winston Chang
28547e90d1 Fix memory leak (#1254)
* When observer is destroyed, invalidate context. Closes #1253

* Update NEWS
2016-07-20 21:01:29 -07:00
Joe Cheng
f70187597f Merge pull request #1222 from rstudio/joe/bugfix/chrome-unsafe-ports
Don't use randomly-chosen port numbers that Chrome deems unsafe
2016-07-20 13:36:05 -07:00
Barbara Borges Ribeiro
333e454e78 Merge pull request #1252 from rstudio/barbara/bugfix/where
give `where` arg a default value ("replace")
2016-07-20 19:47:00 +01:00
Barbara Borges Ribeiro
61cfd11644 give where arg a default value ("replace") 2016-07-20 19:05:20 +01:00
Winston Chang
1c970c8176 Remove configureBookmarking; add onBookmark, onBookmarked, onRestore, onRestored 2016-07-19 14:31:57 -05:00
Winston Chang
1fea54ca5a Merge pull request #1249 from daattali/patch-1
fix typo
2016-07-19 13:31:12 -05:00
Dean Attali
faccc42b22 fix typo 2016-07-17 02:42:41 -04:00
Winston Chang
460f4769a5 Add _inputs_ for url-encoded state 2016-07-12 12:30:37 -05:00
Winston Chang
f465643b75 Rename 'type' argument to 'store' 2016-07-11 23:00:19 -05:00
Winston Chang
a8afd71f96 Rename shiny_persist to shiny_bookmarks 2016-07-08 16:36:58 -05:00
Winston Chang
2274d60207 Change function(req) to function(request) 2016-07-08 16:23:52 -05:00
Winston Chang
57159bccfd Show copy instructions in bookmark dialog 2016-07-08 16:22:09 -05:00
Winston Chang
2888124752 Capture and log errors in initializing RestoreContext 2016-07-08 15:22:58 -05:00
Winston Chang
408e751dcf Capture and log errors in bookmarking/restoring 2016-07-08 15:22:21 -05:00
Winston Chang
af5dcc38a4 Add onRestored argument 2016-07-07 12:44:10 -05:00
Winston Chang
81434640d6 Use name 'bookmark' in more places 2016-07-07 12:04:53 -05:00
Barbara Borges Ribeiro
a45b58d956 Merge pull request #1239 from rstudio/joe/bugfix/insert-ui-initialize
Fix insertUI bug. Closes #1220, #1231, #1232
2016-07-06 22:04:00 +01:00
Barbara Borges Ribeiro
2c5e9a5e76 moved multiple to 4th argument 2016-07-06 21:44:52 +01:00
Barbara Borges Ribeiro
9fb847b179 Merge branch 'master' into joe/bugfix/insert-ui-initialize 2016-07-06 21:37:58 +01:00
Barbara Borges Ribeiro
b8341b2ba8 Merge pull request #1238 from rstudio/feature/pool-scheduler
Better pool support (minor change)
2016-07-06 21:35:21 +01:00
Barbara Borges Ribeiro
26d6e4da2c allows callback to be garbage collected 2016-07-06 21:29:12 +01:00
Barbara Borges Ribeiro
b16ed602d5 rebased with master
Merge branch 'joe/bugfix/insert-ui-initialize' of https://github.com/rstudio/shiny into joe/bugfix/insert-ui-initialize

# Conflicts:
#	inst/www/shared/shiny.js.map
#	inst/www/shared/shiny.min.js
#	inst/www/shared/shiny.min.js.map
2016-07-06 18:04:17 +01:00
Barbara Borges Ribeiro
24aab4d5d3 Merge branch 'master' of https://github.com/rstudio/shiny 2016-07-06 14:18:49 +01:00
Barbara Borges Ribeiro
2eb69d421a fix to outputOptions 2016-07-06 14:18:38 +01:00
Winston Chang
cb52706f2f Add bookmarking example with arbitrary values 2016-07-05 16:35:00 -05:00
Winston Chang
f44d232e8b Add check that UI is a function when restoring state 2016-07-05 16:28:09 -05:00
Winston Chang
a0ac79b9dd Documentation updates 2016-07-05 15:30:42 -05:00
Joe Cheng
177a2a8a1e Input not being initialized with insertUI if beforeStart/afterEnd 2016-07-05 11:06:25 -07:00
Barbara Borges Ribeiro
8b21a87175 Update NEWS 2016-06-30 17:51:09 +01:00
Barbara Borges Ribeiro
6d2dd8e315 Merge pull request #1226 from rstudio/feature/pool-scheduler
Support pool package
2016-06-30 17:49:52 +01:00
Joe Cheng
8b3aff599b Don't use randomly-chosen port numbers that Chrome deems unsafe
Still OK to use these ports if the user asks for them explicitly
2016-06-25 22:37:22 -07:00
Joe Cheng
deb9b74f27 Do equivalent of "mkdir -p" when making state dir 2016-06-25 14:25:22 -07:00
Winston Chang
591de3cbe8 Don't restore state if in a subapp 2016-06-20 16:00:25 -05:00
Winston Chang
f7151e2132 Change '_state_id' to '__state_id__' 2016-06-20 15:12:10 -05:00
Winston Chang
44521be6dd Gracefully handle errors in restoring state 2016-06-20 12:54:04 -05:00
Winston Chang
30416cdbb5 Grunt 2016-06-16 12:44:28 -05:00
Winston Chang
d04da2d256 Add asList method 2016-06-16 12:44:09 -05:00
Winston Chang
231d8a1949 Move loading and decoding of query string into RestoreContext 2016-06-16 10:39:45 -05:00
Winston Chang
3207bec805 Add ShinyRestoreContext class 2016-06-15 15:08:33 -05:00
Winston Chang
425a71e382 Replace bookmarkConfig with bookmarkObserver 2016-06-15 14:55:29 -05:00
Winston Chang
daa12ab2ec Revise how onSave is called; move persist() and encode() into ShinyState object 2016-06-15 14:55:06 -05:00
Joe Cheng
4c652389c5 Support pool package 2016-06-14 12:58:50 -07:00
Winston Chang
f69d88a656 Refinements to save button 2016-06-14 13:23:58 -05:00
Winston Chang
098cbc1456 Better splitting of state query string 2016-06-13 23:19:35 -05:00
Winston Chang
5f2da953a9 Add invalidateReactiveValue function 2016-06-13 22:12:28 -05:00
Winston Chang
638d999fcc Replace updateQueryString with updateLocationBar 2016-06-13 16:47:24 -05:00
Winston Chang
fa80fd64da Make 'restorable' opt-out instead of opt-in 2016-06-13 14:24:21 -05:00
Winston Chang
e4dad82dde Rename 'save' to 'persist' 2016-06-13 12:38:43 -05:00
Winston Chang
d65ff924c8 Add bookmarkButton 2016-06-10 12:47:31 -05:00
Winston Chang
96e9661aaa Fix reactive dependencies when restoring values 2016-06-10 10:55:18 -05:00
Winston Chang
8829d2ebd4 Properly mark actionButtons and passwordInputs as unserializable 2016-06-10 10:44:51 -05:00
Winston Chang
c019280d8a Call onRestore only if it exists 2016-06-10 10:38:04 -05:00
Winston Chang
8d3e5fc160 Refinements 2016-06-10 10:11:55 -05:00
Winston Chang
d3f1312c0b Remove 'enable' argument 2016-06-09 14:55:33 -05:00
Winston Chang
c58f48a1e4 Add support for bookmarking arbitrary values 2016-06-09 14:45:55 -05:00
Winston Chang
979e93509e parseQueryString: ignore extra ampersands 2016-06-09 13:01:36 -05:00
Winston Chang
135c3709b4 Prepare things for separate values 2016-06-09 11:57:45 -05:00
Winston Chang
08400d3f18 Add configureBookmarking function 2016-06-08 12:56:03 -05:00
Winston Chang
bf52075d1b Merge pull request #1197 from rstudio/joe/feature/resetBrush
Add ability to reset brush with session$resetBrush/Shiny.resetBrush
2016-06-03 13:04:01 -05:00
Winston Chang
56befda288 Remove outdated example 2016-06-03 12:48:34 -05:00
Winston Chang
3d68f1dc62 Remove bookmarkOutput; add saveStateModal and encodeStateModal 2016-06-03 12:48:33 -05:00
Winston Chang
970036ce1a Remove clipboard.js 2016-06-03 12:48:33 -05:00
Winston Chang
62108f28f4 Fix argument defaults 2016-06-02 12:52:19 -05:00
Winston Chang
66bbb072c3 Remove createBookmark function 2016-06-02 12:48:17 -05:00
Winston Chang
6c52c26a62 Make names consistent 2016-06-02 11:47:56 -05:00
Winston Chang
d52943d1bf Remove unused code path 2016-06-01 21:41:35 -05:00
Winston Chang
7cb1bbe3d6 Use new ID each time state is saved 2016-06-01 18:12:07 -05:00
Winston Chang
2548c46b8b Check for '..' in restored file input path 2016-06-01 18:07:33 -05:00
Winston Chang
dd5118116b Use wrapper functions for saving/restoring state 2016-06-01 17:10:56 -05:00
Winston Chang
77a9b66028 Merge pull request #1201 from rstudio/bugfix/avoid-radix-sort-overflow
avoid overflow in R 3.3.0 radix sort
2016-05-27 21:39:15 -05:00
Kevin Ushey
e813dab81c avoid overflow in R 3.3.0 radix sort 2016-05-27 14:48:44 -07:00
Winston Chang
6696880178 Add ability to save and restore fileInputs. Also improve fileInput appearance 2016-05-27 14:42:00 -05:00
Winston Chang
8e5952d9ae Add serializers 2016-05-26 12:43:01 -05:00
Joe Cheng
360c1d5953 Add ability to reset brush with session$resetBrush/Shiny.resetBrush 2016-05-25 15:37:27 -07:00
Winston Chang
a7aa6ced19 Save each state in a subdirectory 2016-05-20 14:52:52 -05:00
Winston Chang
97eea669d4 Better error handling when saving/restoring state 2016-05-20 14:17:23 -05:00
Winston Chang
c84777928e Use same state ID throughout a session 2016-05-20 14:10:26 -05:00
Winston Chang
490064a953 Remove unneeded randomID function 2016-05-20 14:10:26 -05:00
Winston Chang
d5975195b3 Initial version of saving state 2016-05-20 14:10:26 -05:00
Winston Chang
9588c36abb Merge branch 'joe/feature/insert-UI' 2016-05-18 15:53:50 -05:00
Barbara Borges Ribeiro
f9200ac135 small fixes; documentation; got rid of unnecassary things 2016-05-18 12:35:25 +01:00
Winston Chang
fffb9606ec Merge pull request #1185 from rstudio/barbara/showcase-update
Barbara/showcase update
2016-05-16 10:04:24 -05:00
Winston Chang
781e15cb84 Restore values only if 'restorable' option is set 2016-05-13 21:06:06 -05:00
Winston Chang
9742001a71 Add shiny options 2016-05-13 20:37:58 -05:00
Barbara Borges Ribeiro
e92eee5ffc removed constraint that forced elements inserted with insertUI to be wrapped in a div/span 2016-05-13 15:28:02 -05:00
Barbara Borges Ribeiro
293c1d471c tiny fix 2016-05-13 14:34:07 -05:00
Barbara Borges Ribeiro
384240b6a4 added NEWS item for IncludeWWW 2016-05-13 14:26:45 -05:00
Winston Chang
6fd626a3ec Disable seralizing of passwords and actionButtons 2016-05-12 17:03:25 -05:00
Winston Chang
bb4ce2f978 Don't clear bookmark DOM elements on error 2016-05-12 15:27:17 -05:00
Barbara Borges Ribeiro
2269e05058 code highliting; dropdown menu for the www files 2016-05-12 15:12:04 -05:00
Winston Chang
ca2a07b816 Add ability to invalidate a reactive value 2016-05-12 10:21:29 -05:00
Winston Chang
38c7bb35e0 Code cleanup 2016-05-12 10:06:44 -05:00
Winston Chang
4f6408f3e1 Add optional update button for bookmarkOutput 2016-05-12 10:06:44 -05:00
Winston Chang
7910d9fde4 Add argument to exclude values from bookmarking 2016-05-12 10:06:43 -05:00
Winston Chang
0258d7e24f Make sure bookmark output is not a text input 2016-05-12 10:06:43 -05:00
Winston Chang
85556ed532 Don't error when no restore context available 2016-05-12 10:06:43 -05:00
Winston Chang
cecb04b097 Make restore context available from server code 2016-05-12 10:06:43 -05:00
Winston Chang
8a7c5c18d0 Add tooltip on copy 2016-05-12 10:06:43 -05:00
Winston Chang
14a1a3f574 Rename functions 2016-05-12 10:06:43 -05:00
Winston Chang
c19f2a7499 Add license info for clipboard.js 2016-05-12 10:05:37 -05:00
Winston Chang
df95be5455 Add bookmarkOutput 2016-05-12 10:05:37 -05:00
Winston Chang
00bef13f1c Add ability for inputs to restore bookmarked values 2016-05-12 10:05:37 -05:00
Winston Chang
a6a35905a7 Clearer variable names 2016-05-12 10:05:37 -05:00
Winston Chang
93f28ef55c Preserve type of bookmarked data 2016-05-12 10:05:36 -05:00
Joe Cheng
bbcb9573cd Add example 2016-05-12 10:05:36 -05:00
Winston Chang
43cc6e19d4 Fixes 2016-05-12 10:05:36 -05:00
Joe Cheng
f4a44664c7 Bookmarkable state wip 2016-05-12 10:05:36 -05:00
Barbara Borges Ribeiro
dd7a3269ad added wwwFiles boolean option to DESCRIPTION file 2016-05-07 17:21:59 +01:00
Winston Chang
157d1b20c5 Fixes for R CMD check 2016-05-06 15:14:09 -05:00
Winston Chang
85fe0c00c2 Fix tests 2016-05-06 15:10:45 -05:00
Winston Chang
91092b8a96 Fix function labels for profiling 2016-05-06 15:03:00 -05:00
Barbara Borges Ribeiro
1ed237cfcc init commit 2016-05-05 16:11:43 +01:00
Barbara Borges Ribeiro
c7044498d5 added NEWS item for insertUI / removeUI 2016-05-05 14:17:45 +01:00
Joe Cheng
1d2a2fbcae Merge remote-tracking branch 'origin/joe/feature/insert-UI' 2016-05-04 11:34:54 -07:00
Barbara Borges Ribeiro
9b015e8cae documentation update 2016-05-03 13:57:10 +01:00
Barbara Borges Ribeiro
0a8c26fff4 call sendImageSize from unbindOutputs 2016-05-02 20:02:41 +01:00
Barbara Borges Ribeiro
506de72666 fixed typos; included argument defaults; removed 'shown', 'hidden' triggers following chat with Winston 2016-05-02 18:27:15 +01:00
Barbara Borges Ribeiro
a5b4156b56 moved insertAdjacentElement to the right place 2016-05-02 15:19:08 +01:00
Barbara Borges Ribeiro
da4b42cb1d ran grunt 2016-05-02 14:50:46 +01:00
Barbara Borges Ribeiro
53790f8247 various updates 2016-05-02 14:48:05 +01:00
Barbara Borges Ribeiro
69780d4727 added sendImage and sendOUtputHIddenState 2016-04-29 06:33:06 +01:00
Barbara Borges Ribeiro
aa2b644684 updated documentation; added ... argument to onFlush() and onFlushed() in order to be able to pass in arguments to the func 2016-04-29 05:27:26 +01:00
Barbara Borges Ribeiro
a12e8875a6 changed everything from sendCustomMessage to session$sendMessage 2016-04-29 05:16:22 +01:00
Barbara Borges Ribeiro
9e91b265ce sendInsertUI now uses sendMessage instead of sendCustomMessage 2016-04-29 04:46:09 +01:00
Barbara Borges Ribeiro
8c12e3ab90 added insertAdjacentElement for compatibility with Firefox 2016-04-28 12:03:15 +01:00
Winston Chang
7e303b4fc0 Merge pull request #1157 from rstudio/modal
Add modal dialogs
2016-04-27 15:30:02 -05:00
Winston Chang
40e0fcff30 Change modal example 2016-04-27 15:29:18 -05:00
Winston Chang
3c9e74b23e Re-document 2016-04-26 15:30:03 -05:00
Barbara Borges Ribeiro
6b001eb7c3 updated insertUI; added removeUI 2016-04-25 23:03:18 +01:00
Winston Chang
f81621aa66 Merge pull request #1158 from rstudio/example-cleanup
Clean up examples
2016-04-22 13:05:36 -05:00
Winston Chang
08c7484087 Rename argument 2016-04-21 15:22:43 -05:00
Barbara Borges Ribeiro
a8c68f3e30 updated shiny-options text 2016-04-18 17:28:45 +01:00
Barbara Borges Ribeiro
0e6698d760 updated NEWS 2016-04-18 02:12:47 +01:00
Barbara Borges Ribeiro
f3d4f9ff23 Merge pull request #1156 from rstudio/barbara/error-hiding
Barbara/error hiding
2016-04-18 01:56:26 +01:00
Barbara Borges Ribeiro
d711f17081 changed sanitization default to FALSE (on local development) 2016-04-18 01:44:43 +01:00
Barbara Borges Ribeiro
d35eba45c5 tiny fix 2016-04-18 01:34:50 +01:00
Barbara Borges Ribeiro
cd53e79b19 removed classError argument to safeError function 2016-04-18 01:27:37 +01:00
Barbara Borges Ribeiro
3db7029534 Merge branch 'master' of https://github.com/rstudio/shiny 2016-04-15 16:50:19 +01:00
Barbara Borges Ribeiro
ad1e52bf19 got rid of warning that popped up when renderFunc took no arguments; there really isn't a good reason to require this (not at this point at least) 2016-04-15 16:49:52 +01:00
Barbara Borges Ribeiro
e08791a284 update to safeError 2016-04-14 18:20:49 +01:00
Barbara Borges Ribeiro
8d1deeb568 undo last commit to be able to merge automatically 2016-04-14 18:02:16 +01:00
Barbara Borges Ribeiro
375c7789a2 updated NEWS 2016-04-14 17:52:10 +01:00
Barbara Borges Ribeiro
ec8a81aedb Merge pull request #1163 from bborgesr/barbara/fix-tabsetpanel
deprecated position arg to tabsetPanel; updated NEWS; cc @jcheng5 @wch
2016-04-14 17:49:51 +01:00
Barbara Borges Ribeiro
033d513aee added version to shinyDeprecated call; updated NEWS 2016-04-14 17:43:24 +01:00
Barbara Borges Ribeiro
fb3e4e4881 Changed customStop to stop(safeError). Refactored some middleware.R code. Fixed downloadHandler's bug of not responding to safeError. 2016-04-14 17:31:34 +01:00
Joe Cheng
8a30c006e7 Prototype insertUI functionality 2016-04-13 16:12:30 -07:00
Barbara Borges Ribeiro
3f76679673 another update to NEWS 2016-04-07 22:22:51 +01:00
Barbara Borges Ribeiro
1cee5d4b41 deprecated position arg to tabsetPanel; updated NEWS 2016-04-07 22:15:15 +01:00
Barbara Borges Ribeiro
3107eec697 removed unnecessary line 2016-04-07 02:01:56 +01:00
Barbara Borges Ribeiro
477d46316e updated customStop() documentation example to match Winston's pattern 2016-04-06 14:08:11 +01:00
Winston Chang
3133693a0e Update NEWS 2016-04-05 20:58:48 -05:00
Winston Chang
bc7d701298 Make examples runnable with shinyApp() 2016-04-05 20:53:59 -05:00
Winston Chang
5d6d75b4f3 Remove shinyUI() and shinyServer() from examples 2016-04-05 15:23:23 -05:00
Winston Chang
73d48a7b37 Grunt 2016-04-05 13:19:27 -05:00
Winston Chang
ed7b9a9989 Modal dialog refinements 2016-04-05 13:18:57 -05:00
Winston Chang
e1a955752f Add modal dialogs 2016-04-05 13:18:56 -05:00
Winston Chang
0bdc8f0b2b Update package.json 2016-04-05 09:45:06 -05:00
Barbara Borges Ribeiro
a692b3ced8 implemented error hiding for ui.R and downloadHandler() cases 2016-04-05 15:28:05 +01:00
Winston Chang
2f5b93861d Merge pull request #1152 from daattali/master
add placeholder option to passwordInput()
2016-04-04 11:23:44 -05:00
Joe Cheng
110183585c Merge pull request #1143 from rstudio/joe/feature/output-arg-passthrough
Joe/feature/output arg passthrough
2016-04-03 08:10:44 -07:00
Barbara Borges Ribeiro
7eb29586a7 a few minor tweaks 2016-04-03 15:24:17 +01:00
Barbara Borges Ribeiro
401065a23e a lot of not very productive experimentation 2016-04-03 15:00:59 +01:00
Dean Attali
4e5e0fb0ce add placeholder option to passwordInput() 2016-04-02 18:44:10 -07:00
Barbara Borges Ribeiro
d41a06611e fixed documentation 2016-04-01 23:57:20 +01:00
Barbara Borges Ribeiro
26c3c27726 a few tweaks to customStop() 2016-04-01 22:47:58 +01:00
Barbara Borges Ribeiro
19ab63e041 a little code refactoring and added a customStop() function 2016-04-01 02:45:41 +01:00
Barbara Borges Ribeiro
5dafdab3d7 made the tracker construct - now an R6 class - easier to understand (more obvious); fixed the shinysession and name issues related to the renderFunc's 2016-04-01 00:44:14 +01:00
Barbara Borges Ribeiro
afbb17d428 errors are now sanitized in the app by default (must use options(shiny.sanitize.errors = FALSE) to override this behavior) 2016-03-30 07:29:58 +01:00
Winston Chang
8a721fbd25 Bump version to 0.13.2.9001 and update NEWS
Shiny 0.13.2 was released from another branch so its changes to NEWS were
incorporated here
2016-03-29 22:46:46 -05:00
Winston Chang
5d91a409e7 Merge pull request #1150 from rstudio/navbarpage-selected
Allow setting selected item in navbarPage. Closes #970
2016-03-29 22:35:18 -05:00
Winston Chang
8470f7caf8 Allow setting selected item in navbarPage. Closes #970 2016-03-29 22:32:00 -05:00
Winston Chang
67e279928e Merge pull request #1147 from rstudio/navbar-horizontal-divider
navbarMenu horizontal dividers
2016-03-29 22:13:24 -05:00
Winston Chang
77ac3a62b7 Check that tab arguments are unnamed 2016-03-29 12:56:43 -05:00
Winston Chang
12eaa3a162 Reconnection refinements 2016-03-29 12:41:46 -05:00
Joe Cheng
bbd5dd7b4f Merge pull request #1132 from rstudio/joe/reactive-graph-data
Update reactive graph sample data to include time
2016-03-29 09:54:15 -07:00
Joe Cheng
38fcd6e267 Merge pull request #1074 from rstudio/reconnect
Reconnect
2016-03-29 09:53:20 -07:00
Winston Chang
fd7f683eaa Remove bootstrapDependency function. Closes #1069 2016-03-29 11:12:05 -05:00
Winston Chang
e15f9acd91 Grunt 2016-03-28 21:30:27 -05:00
Winston Chang
7cb0882c73 Add "force" option to allowReconnect 2016-03-28 21:29:23 -05:00
Winston Chang
486d4d1c88 Add 'action' parameter to notifications 2016-03-28 15:15:07 -05:00
Winston Chang
ded8b13e96 Reconnect UI refinements 2016-03-28 14:42:44 -05:00
Barbara Borges Ribeiro
c7eb7ba861 passed error through if handler accepts it 2016-03-28 20:31:39 +01:00
Barbara Borges Ribeiro
4920bff8fd tiny documentation update 2016-03-28 18:24:04 +01:00
Barbara Borges Ribeiro
d78edf5dda removed func arg from render functions; fixed issue introduced by rebase a few commits ago 2016-03-28 18:19:37 +01:00
Winston Chang
7510c02d83 Update ion.RangeSlider to 2.1.2 2016-03-28 11:17:07 -05:00
Barbara Borges Ribeiro
2d7b729473 got rid of unnecessary lines 2016-03-28 15:46:55 +01:00
Barbara Borges Ribeiro
0495fe2d71 updated renderFunc's to include a shinysession arg 2016-03-28 15:31:33 +01:00
Barbara Borges Ribeiro
d7da5df734 updated integration etsts 2016-03-28 13:18:22 +01:00
Barbara Borges Ribeiro
4462b6bd39 changed a warning to an error, following the "fail fast" principle 2016-03-28 13:00:11 +01:00
Barbara Borges Ribeiro
80e1edeeb2 a better way to check which context the render function is being called from 2016-03-28 12:54:54 +01:00
Winston Chang
11af421f10 Use notification API for reconnection interface 2016-03-25 16:39:58 -05:00
Winston Chang
686ff235e7 New reconnect UI 2016-03-25 15:43:08 -05:00
Winston Chang
31f76a6d4d Add back gray-out on disconnect 2016-03-25 15:40:46 -05:00
Winston Chang
50078078e0 Export show/hideReconnectDialog functions 2016-03-25 15:40:45 -05:00
Winston Chang
be85e1e2f7 Add onConnected and onDisconnected 2016-03-25 15:40:45 -05:00
Winston Chang
9ad1574292 Allow Shiny Server to properly override methods 2016-03-25 15:40:45 -05:00
Winston Chang
4b71825707 Increase reconnect delay time with subsequent attempts 2016-03-25 15:40:45 -05:00
Winston Chang
fb1fd88947 Tweaks to disconnection/reconnection UI 2016-03-25 15:40:45 -05:00
Winston Chang
dca527d8b6 Allow app to control reconnection behavior 2016-03-25 15:40:45 -05:00
Winston Chang
3452a445fe Show box when trying to reconnect 2016-03-25 15:40:45 -05:00
Winston Chang
a06e9d2bef Implement auto-reconnect 2016-03-25 15:39:31 -05:00
Winston Chang
7a3961a280 Add support for menu section headers 2016-03-25 09:29:42 -05:00
Winston Chang
54729d8fb4 Update NEWS 2016-03-24 20:11:44 -05:00
Winston Chang
c2e17ee182 Add support for horizontal dividers in navbarMenu 2016-03-24 20:11:07 -05:00
Barbara Borges Ribeiro
bc0064d4b9 harcoded colors used for the color-coding of the time labels (creditted colorbrewer) 2016-03-25 00:51:19 +00:00
Barbara Borges Ribeiro
03685dbb61 added check for valid arguments if passed via outputArgs 2016-03-25 00:22:48 +00:00
Barbara Borges Ribeiro
26fcba8ed5 really not a solution... 2016-03-24 22:42:57 +00:00
Barbara Borges Ribeiro
bc15b65538 added outputArgs to all other renderXXX functions following the pattern used for renderPlot 2016-03-24 22:42:57 +00:00
Joe Cheng
e9ab34a9c1 Provide xxxOutput args via renderXXX passthrough
This will allow you to customize outputs when used in an R Markdown
document
2016-03-24 22:21:48 +00:00
Winston Chang
0bf512ebdd Grunt 2016-03-24 17:11:12 -05:00
Winston Chang
7646fbeaa0 Bump version to 0.13.1.9002 2016-03-24 17:11:02 -05:00
Winston Chang
84b4766013 Merge pull request #1141 from rstudio/notifications
Notification interface
2016-03-24 17:09:31 -05:00
Winston Chang
3a48734b2f Re-document 2016-03-24 14:07:37 -05:00
Winston Chang
36ae332959 Remove some public methods for notifications 2016-03-24 14:01:28 -05:00
Winston Chang
3e0d8da9d6 Add exports.renderContent to modularize content rendering in JS 2016-03-23 15:46:44 -05:00
Winston Chang
2fcb4dbe50 Modularize dependency handling in R 2016-03-23 15:46:03 -05:00
Winston Chang
09c93bfb39 Escape ID 2016-03-23 14:34:20 -05:00
Winston Chang
34068b1598 Rename 'style' to 'type' 2016-03-23 14:34:20 -05:00
Barbara Borges Ribeiro
a67da1c99a added color scale for time labels 2016-03-23 16:26:33 +00:00
Winston Chang
0d6754761d Add style argument 2016-03-22 15:36:21 -05:00
Winston Chang
898f7b66cf Rename argument from 'html' to 'ui' 2016-03-22 14:47:07 -05:00
Winston Chang
c18f3e86f0 Add note about IDs 2016-03-22 13:46:21 -05:00
Barbara Borges Ribeiro
de51922f10 Trigger 2016-03-22 17:02:38 +00:00
Winston Chang
be0cb18bfc Merge pull request #1138 from rstudio/joe/bugfix/htmltemplate-doc-update
Update htmlTemplate docs for htmltools 0.3.5
2016-03-22 11:13:31 -05:00
Winston Chang
39fd1db3c0 Bump htmltools required version to 0.3.5 2016-03-22 11:07:15 -05:00
Winston Chang
b4565e7354 Fix coordmap tests 2016-03-22 11:05:59 -05:00
Winston Chang
e28cada4dd Handle tag inputs and escape HTML text 2016-03-21 20:10:17 -05:00
Joe Cheng
6daac65968 Add missing entries to staticdocs index 2016-03-21 17:00:17 -07:00
Joe Cheng
1ecc49c450 Update htmlTemplate docs for htmltools 0.3.5 2016-03-21 16:53:46 -07:00
Barbara Borges Ribeiro
f96e7d9aaa stored the timeElapsed float on the node instead of the fully formatted string; made sure we're not showing any time elapsed info while the node is active (it could be confusing) 2016-03-21 23:25:17 +00:00
Barbara Borges Ribeiro
c637bba867 changed time label color; updated default argument to renderReactLog 2016-03-21 22:19:45 +00:00
Barbara Borges Ribeiro
bdc6554ca8 added time argument 2016-03-21 21:53:34 +00:00
Winston Chang
ecb59e9c31 Add R notification functions 2016-03-21 16:47:55 -05:00
Winston Chang
1b39184e98 Add randomID function 2016-03-21 16:43:35 -05:00
Barbara Borges Ribeiro
2a35ba64f7 fixed y positioning 2016-03-21 21:15:32 +00:00
Barbara Borges Ribeiro
3a5123627d updated multilineTextNode 2016-03-21 21:04:27 +00:00
Barbara Borges Ribeiro
a18eeecd59 separated text label and time label 2016-03-21 20:59:32 +00:00
Winston Chang
85e3f04738 Restyle notifications and add close button 2016-03-21 13:59:12 -05:00
Barbara Borges Ribeiro
cc59864377 experimenting 2016-03-21 18:49:47 +00:00
Barbara Borges Ribeiro
5b10cbf2e2 added 'time elapsed' to nodes' labels 2016-03-21 14:30:42 +00:00
Barbara Borges Ribeiro
fc6b83bb5d Merge pull request #1136 from rstudio/barbara/renderTable-fixes
tiny update following the bigger renderTable() PR
2016-03-18 21:20:29 +00:00
Barbara Borges Ribeiro
bc509f55d9 added NEWS item documenting the change to renderTable() and fixed tiny bug (stop() message was spanning two lines with only one string) 2016-03-18 21:15:35 +00:00
Winston Chang
f81301ece6 Simplify notification API 2016-03-18 15:53:40 -05:00
Winston Chang
382e5c1f43 Rename Shiny.Notification to Shiny.notifications 2016-03-18 14:59:43 -05:00
Winston Chang
0243f74dcd Add delay before removal 2016-03-18 14:45:21 -05:00
Winston Chang
58737ef454 Add notification JS API 2016-03-18 14:45:21 -05:00
Winston Chang
940cea82ca Merge pull request #1133 from rstudio/es6
Add tooling for ES6
2016-03-18 14:44:53 -05:00
Winston Chang
5683e36733 Add estraverse-fb npm dependency 2016-03-18 14:42:03 -05:00
Winston Chang
f5137b7935 Grunt 2016-03-18 14:16:47 -05:00
Winston Chang
0c2af42c69 Make ESLint gave warnings instead of errors 2016-03-18 14:15:36 -05:00
Winston Chang
760dc5d0c6 Add babel polyfill 2016-03-18 14:15:36 -05:00
Winston Chang
5331aa08a7 Fixes for eslint 2016-03-18 14:15:36 -05:00
Winston Chang
375d7cc7b1 Update eslint rules 2016-03-18 14:15:36 -05:00
Winston Chang
a05f3dd640 Update npm packages 2016-03-18 14:15:36 -05:00
Winston Chang
b91c1b44ba Switch from jshint to eslint 2016-03-18 14:15:36 -05:00
Winston Chang
6efb01a397 Use Babel for ES6->ES5 transpilation 2016-03-18 14:15:36 -05:00
Barbara Borges Ribeiro
1843eca6c0 Merge pull request #1134 from bborgesr/updateActionButton
Verify button icon format and created updateActionButton()
2016-03-18 19:01:50 +00:00
Barbara Borges Ribeiro
506e3e8a48 added another check in the JS to make sure that we're finding the correct icon 2016-03-18 18:55:38 +00:00
Barbara Borges Ribeiro
0e5a3cc5aa throw error instead of warning in validateIcon(); updated documentation 2016-03-18 15:32:06 +00:00
Barbara Borges Ribeiro
d2dd76e13d fixed typo 2016-03-17 21:36:51 +00:00
Barbara Borges Ribeiro
470b82fd64 compiled documentation 2016-03-17 21:20:42 +00:00
Barbara Borges Ribeiro
e04dd3a4b1 check icon validity more robustly; set icon=character(0) as the way to get rid of a previous icon; updated documentaion and NEWS 2016-03-17 21:15:49 +00:00
Barbara Borges Ribeiro
2d39e06c97 find i-tag elements with *any* class (to circumvent the issue of selecting italicized text) 2016-03-17 14:55:57 +00:00
Barbara Borges Ribeiro
e1fc74bdc1 updates to input_binding_actionbutton.js and got rid of isIcon function (substituted by simple check instead) 2016-03-17 14:47:48 +00:00
Barbara Borges Ribeiro
3ab5d7f861 verify that button icons are in the right format (not necessarily valid though) and added updateActionButton() 2016-03-15 22:19:21 +00:00
Winston Chang
d63dd6086a Merge pull request #1129 from bborgesr/newRenderTable
Improved renderTable()
2016-03-15 12:43:02 -05:00
Barbara Borges Ribeiro
a8d9895a9b updated documentation 2016-03-15 16:58:42 +00:00
Barbara Borges Ribeiro
f8a7257af3 improved defaultAlignment function and changed names of spacing value options 2016-03-15 16:47:35 +00:00
Barbara Borges Ribeiro
4703028988 actually with multiple tables, their ids would all be identical (bad), so switched back to using classes to gain specificity 2016-03-15 00:55:48 +00:00
Barbara Borges Ribeiro
87523cdbd5 created table id to add css specificity 2016-03-15 00:38:18 +00:00
Barbara Borges Ribeiro
d9567ed035 check valid spacing 2016-03-15 00:01:52 +00:00
Barbara Borges Ribeiro
0ab277662a updated documentation 2016-03-14 23:00:15 +00:00
Barbara Borges Ribeiro
2eeb94e39c changed bordered to spacing with four possible values, rather than only two 2016-03-14 22:48:55 +00:00
Joe Cheng
4b441d10b3 Update reactive graph sample data to include time 2016-03-14 15:37:53 -07:00
Barbara Borges Ribeiro
37a1d3d61e improved defaultAlignment function 2016-03-14 20:24:41 +00:00
Barbara Borges Ribeiro
3839338c15 mostly spacing 2016-03-14 20:13:08 +00:00
Barbara Borges Ribeiro
bdee5790e6 added alignment default character ("?") 2016-03-14 18:39:51 +00:00
Barbara Borges Ribeiro
d0dab25dae tried fix 2016-03-14 16:16:22 +00:00
Barbara Borges Ribeiro
b14b7b00c2 actually padding is necessary for headers too 2016-03-14 14:57:38 +00:00
Barbara Borges Ribeiro
248bfcccda padding on all cells 2016-03-14 14:54:31 +00:00
Barbara Borges Ribeiro
9b5833205b made the check for empty data frame more robust 2016-03-12 18:24:19 +00:00
Barbara Borges Ribeiro
07f8589090 coerce the input to a data frame (important if the input was a matrix for example, as some parts of the code might not apply) 2016-03-12 18:12:50 +00:00
Barbara Borges Ribeiro
f77f83dfeb fixed a tiny bug introduced by the previous commit 2016-03-12 18:03:59 +00:00
Barbara Borges Ribeiro
e3d3d916ba improved regex for substitution (less fragile, less hack-ish) 2016-03-12 17:59:45 +00:00
Barbara Borges Ribeiro
cccf219cd2 simplified alignment vector and got rid of an unnecessary variable 2016-03-12 17:55:05 +00:00
Barbara Borges Ribeiro
0896b2f7b8 initialize header_alignments in a clearer way 2016-03-12 17:36:01 +00:00
Barbara Borges Ribeiro
cc406262ac added spaces after commas in a couple of places where they were missing 2016-03-12 17:23:43 +00:00
Barbara Borges Ribeiro
0f20063eb8 added "$" to regex to make sure we're subbing "</table>" only at the end of the input 2016-03-12 17:04:29 +00:00
Barbara Borges Ribeiro
5f32b165f2 updated createWrapper() per Joe's suggestion, added spaces between the "=" 2016-03-12 16:46:59 +00:00
Winston Chang
3cadd1789b Merge pull request #1130 from dmpe/master
update bootstrap to 3.3.6
2016-03-11 16:03:24 -06:00
dmpe
e486778b36 note to news file and upgrade number in R file 2016-03-11 19:21:25 +01:00
Barbara Borges Ribeiro
7fe6453bbb vectorized form to add format args to classNames 2016-03-11 16:26:07 +00:00
Barbara Borges Ribeiro
9f88d2b6d6 made isNumber() 1000x more elegant 2016-03-11 16:19:29 +00:00
Barbara Borges Ribeiro
8f9d52699d return NULL instead of the empty string if no data is provided 2016-03-11 16:00:00 +00:00
Barbara Borges Ribeiro
0a774a8c55 "the" changed to "of" 2016-03-11 15:52:05 +00:00
Barbara Borges Ribeiro
d4ced34a11 2nd update to width documentation (copied straight from plotOutput() ) 2016-03-11 15:36:14 +00:00
Barbara Borges Ribeiro
85a762a0b9 updated width documentation 2016-03-11 15:34:34 +00:00
dmpe
b255fecc6e update bootstrap to 3.3.6
see https://github.com/rstudio/shiny/issues/1056
2016-03-11 13:23:38 +01:00
Barbara Borges Ribeiro
734d2e2594 latex bug in documentation (but shouldn't this be allowed?) 2016-03-11 10:58:18 +00:00
Barbara Borges Ribeiro
2e292b4636 commenting and documenting 2016-03-11 10:44:02 +00:00
Barbara Borges Ribeiro
f0bc7356ac made sure theader is only present the argument colnames is set to TRUE 2016-03-10 23:34:53 +00:00
Winston Chang
1bcb6ab931 Add note about grunt clean 2016-03-10 11:08:15 -06:00
Barbara Borges Ribeiro
ef65937662 replaced format argument with 4 flags (striped, bordered, hover, condensed) and made headers look like bootstrap's 2016-03-09 22:54:11 +00:00
Barbara Borges Ribeiro
3369b8b5b2 finally got headers to align nicely with columns 2016-03-09 01:44:23 +00:00
Winston Chang
28db561cd9 Bump version to 0.13.1.9001 and update NEWS 2016-03-08 17:03:55 -06:00
Winston Chang
0622326e1b Merge pull request #1126 from rstudio/commas
Add code diagnostics for missing/extra commas, and for unmatched }, ), and ]
2016-03-08 17:01:36 -06:00
Winston Chang
c6e2593e4e Streamline diagnoseCode 2016-03-08 16:54:28 -06:00
Barbara Borges Ribeiro
d0e3279a67 aesthethics 2016-03-08 17:00:42 +00:00
Winston Chang
aee5bda9ec Add workaround for quartz res bug
The quartz device is hard-coded to use 72 ppi in some places, and this
causes problems with grid unit calculations when a value other than 72
is used.
2016-03-08 09:54:01 -06:00
Barbara Borges Ribeiro
979b4a8861 used row.names() function instead of rownames() to avoid naming conflicts 2016-03-07 12:56:00 +00:00
Barbara Borges Ribeiro
c10cd4b474 removed unnecessary css, and garbage collection for renderBootstrapTable 2016-03-07 12:33:43 +00:00
Barbara Borges Ribeiro
4aa1d19845 replaced renderTable with renderBootstrapTable (but kept name renderTable) and ensured backward compatibility 2016-03-07 12:29:43 +00:00
Barbara Borges Ribeiro
7ff51d89fc check if rownames are numbers or strings 2016-03-07 12:21:39 +00:00
Winston Chang
ea9d94e42f Add code diagnostics (missing/extra commas) 2016-03-04 15:11:34 -06:00
Barbara Borges Ribeiro
a9ba0fdb0b added arguments, minimal functional code 2016-03-04 15:13:07 +00:00
Barbara Borges Ribeiro
af19c3331c added more function arguments 2016-03-04 00:01:09 +00:00
Winston Chang
5e98b930ee Move tests from inst/ to tests/ 2016-03-03 15:00:51 -06:00
Barbara Borges Ribeiro
057d160392 changes to make function compatible with table demo app 2016-03-03 15:54:59 +00:00
Barbara Borges Ribeiro
6b2899c219 fixed small width bug 2016-03-02 15:22:16 +00:00
Barbara Borges Ribeiro
85290e687c added customizable width 2016-03-02 15:05:50 +00:00
Barbara Borges Ribeiro
d778e81f42 aesthetic changes 2016-03-02 14:03:56 +00:00
Barbara Borges Ribeiro
2bfad21604 added renderBootstrapTable 2016-03-01 15:55:58 +00:00
Winston Chang
373e0d3a9f Fix NEWS after weird merge 2016-02-22 11:37:30 -06:00
Joe Cheng
5e83403d0c Update NEWS 2016-02-22 09:30:22 -08:00
Winston Chang
cbe76aab83 Merge pull request #1117 from rstudio/joe/feature/abort-output
Add ability to abort the processing of outputs
2016-02-22 11:20:17 -06:00
Joe Cheng
26de088520 s/abortOutput/cancelOutput/; add req option 2016-02-22 09:12:30 -08:00
Winston Chang
98430edb17 Merge branch 'replay-plot' 2016-02-19 13:57:38 -06:00
Winston Chang
48c6784e51 Change 'replay' option to 'execOnResize' 2016-02-19 13:52:36 -06:00
Winston Chang
dc0f5af3ef Rename 'render' to 'plotObj' 2016-02-19 13:42:54 -06:00
Winston Chang
af85e6f2a6 Merge pull request #1116 from yihui/warn-non-UTF8
Closes #810: check if the input file is encoded in UTF-8 and warn if not
2016-02-19 10:00:49 -06:00
Joe Cheng
4e91af4d64 Add ability to abort the processing of outputs
abortOutput() leaves the state of the output unchanged,
unlike req(), validate(), or stop().
2016-02-19 00:33:10 -08:00
Yihui Xie
faf87a5dee Closes #810: check if the input file is encoded in UTF-8 and warn if not
The validUTF8() function is still in R-devel, and they probably will never export it, so let's use iconv(x, from = 'UTF-8', to = 'UTF-8') to test if x is encoded in UTF-8

also closes #1113
2016-02-19 00:02:25 -06:00
Winston Chang
517c5d356f Merge tag 'v0.13.1'
Manually bumped version to 0.13.1.9000.
2016-02-18 12:45:00 -06:00
Winston Chang
931be22247 Bump version to 0.13.1 2016-02-17 12:03:55 -06:00
Joe Cheng
8697360eb7 Really fix docs. 2016-02-17 11:46:02 -06:00
Joe Cheng
e3a867132a Use parent.frame() instead of sys.parent() 2016-02-17 11:45:48 -06:00
Joe Cheng
c96debadc5 Fix docs/check 2016-02-17 11:45:16 -06:00
Joe Cheng
02520d4f54 Pass tests. reactive(function() { ... }) is NO LONGER supported. 2016-02-17 11:45:10 -06:00
Joe Cheng
5070b63d5b Partial fix of debugger breakage
There are two problems I'm trying to solve here.

1) Somewhere along the way, exprToFunction gained a hardcoded
   assumption that two stack frames up is a variable "expr",
   meaning anything that called installExprFunction had to have
   the first argument be exactly "expr". I think I got this
   fixed, now the only assumption made by both installExprFunc
   and exprToFunc is if they are called with quoted = FALSE,
   then the caller is merely passing through code that originated
   exactly one more level up the stack frame. If the code is
   less than one level up, i.e. an end user is directly passing
   code into installExprFunction or exprToFunction, then it won't
   work; and if the code is more than one level up (someone is
   passing code into function A which passes through to function
   B which calls installExprFunction, with quoted = FALSE) then
   it also won't work.

2) registerDebugHook calls were broken in various places by the
   name/envir registered with the hook being different than the
   name/envir through which the function was actually called.
   This generally seems fixable by moving the registerDebugHook
   call closer to the name/envir that will ultimately be called
   (e.g. call registerDebugHook directly from wrapFunctionLabel).

There still seems to be a problem here in that breakpoints in
RStudio are hit but then the IDE automatically runs "n" multiple
times. Also the unit tests don't currently pass, I haven't
investigated that yet.
2016-02-17 11:45:01 -06:00
Joe Cheng
eaa722b10d Fix flexCol on RStudio Desktop for Win/Linux
RStudio Desktop requires the older -webkit vendor-prefixed
flex box properties. I missed the one for flex-direction.
2016-02-17 11:32:47 -06:00
Winston Chang
1bc3c90286 Update NEWS 2016-02-16 14:07:49 -06:00
Winston Chang
afd00edee3 Add replay option 2016-02-16 14:07:49 -06:00
Winston Chang
b712398208 If plot code errors, re-execute on resize 2016-02-16 13:39:11 -06:00
Winston Chang
7586e91b4f Fix coordmap tests 2016-02-15 16:01:34 -06:00
Winston Chang
9eba82c107 Fix vars 2016-02-15 16:01:15 -06:00
Winston Chang
ccdc219a09 More cleanup 2016-02-15 15:39:05 -06:00
Yihui Xie
60d01e76e9 Merge pull request #1109 from vnijs/master
Closes #692
2016-02-13 00:38:06 -06:00
mostly-harmless
b5cfd4152e Fix for https://github.com/rstudio/shiny/issues/692 2016-02-12 22:15:34 -08:00
Winston Chang
32c4c8ae32 Code cleanup 2016-02-12 15:59:10 -06:00
Winston Chang
bd4c506d22 Implement replayPlot when width/height changes 2016-02-12 13:26:41 -06:00
Winston Chang
476dd7cd56 Collect needed data structures 2016-02-11 14:45:43 -06:00
Winston Chang
8176f84715 Restructure ggplot2 coordmap extraction 2016-02-11 14:25:20 -06:00
Winston Chang
6bd33721d8 Separate rendering code into a reactive 2016-02-11 12:27:16 -06:00
Winston Chang
c9d9671288 More restructuring 2016-02-11 12:25:38 -06:00
Winston Chang
2a821edf5f Small restructure of renderPlot 2016-02-11 12:25:38 -06:00
Winston Chang
68b85bdc87 Merge branch 'fix-plot-flicker2' 2016-02-11 12:20:45 -06:00
Winston Chang
83cf5907c3 Merge pull request #1072 from rstudio/rm-do-call
Remove unneeded do.call
2016-02-11 10:24:47 -06:00
Winston Chang
c912b6547c Merge pull request #1106 from yihui/travis-cache
Set sudo to false explicitly to enable caching
2016-02-11 10:23:08 -06:00
Yihui Xie
bf04b74f87 Set sudo to false explicitly to enable caching 2016-02-10 13:34:24 -06:00
Joe Cheng
9d1e008990 Merge pull request #1099 from rstudio/joe/bugfix/debug-fix
Partial fix of debugger breakage
2016-02-10 08:15:59 -08:00
Joe Cheng
d9e5285a3b Really fix docs. 2016-02-09 16:29:15 -08:00
Joe Cheng
84937b7a0b Use parent.frame() instead of sys.parent() 2016-02-09 16:29:15 -08:00
Joe Cheng
924b3e16cf Fix docs/check 2016-02-09 16:29:15 -08:00
Joe Cheng
2a8cf01410 Pass tests. reactive(function() { ... }) is NO LONGER supported. 2016-02-09 16:29:14 -08:00
Joe Cheng
a3a5cfee6c Partial fix of debugger breakage
There are two problems I'm trying to solve here.

1) Somewhere along the way, exprToFunction gained a hardcoded
   assumption that two stack frames up is a variable "expr",
   meaning anything that called installExprFunction had to have
   the first argument be exactly "expr". I think I got this
   fixed, now the only assumption made by both installExprFunc
   and exprToFunc is if they are called with quoted = FALSE,
   then the caller is merely passing through code that originated
   exactly one more level up the stack frame. If the code is
   less than one level up, i.e. an end user is directly passing
   code into installExprFunction or exprToFunction, then it won't
   work; and if the code is more than one level up (someone is
   passing code into function A which passes through to function
   B which calls installExprFunction, with quoted = FALSE) then
   it also won't work.

2) registerDebugHook calls were broken in various places by the
   name/envir registered with the hook being different than the
   name/envir through which the function was actually called.
   This generally seems fixable by moving the registerDebugHook
   call closer to the name/envir that will ultimately be called
   (e.g. call registerDebugHook directly from wrapFunctionLabel).

There still seems to be a problem here in that breakpoints in
RStudio are hit but then the IDE automatically runs "n" multiple
times. Also the unit tests don't currently pass, I haven't
investigated that yet.
2016-02-09 16:29:14 -08:00
Joe Cheng
2c04441591 Merge pull request #1105 from rstudio/travis-update
Take advantage of new travis features
2016-02-09 16:28:33 -08:00
Winston Chang
a4eab8e216 Grunt 2016-02-09 16:03:59 -06:00
Winston Chang
189f9589d4 Unset attributes in img that aren't present in new data 2016-02-09 16:02:39 -06:00
Hadley Wickham
880721e0d0 Take advantage of new travis features
This will cache package install between checks, which should make it run quite a bit faster - this is what @jimhester's has been working on
2016-02-09 14:02:33 -06:00
Winston Chang
6ab65e2031 Fix plot flickering on Safari and Firefox. Closes #776
Previously, a new img tag was added when a new plot was sent, but now it uses
the same img tag and changes the src attribute.
2016-02-09 11:08:25 -06:00
Winston Chang
e871934cfd Fix package name for Travis 2016-02-06 14:46:51 -06:00
Winston Chang
686390c1f2 Merge pull request #1096 from yihui/bugfix/datatables-warning
Fixes #561: make sure DataTables always gets a correct number of columns of data
2016-02-04 10:12:56 -06:00
Yihui Xie
a8b9fb1708 use the CRAN version of htmltools 2016-02-03 14:02:24 -06:00
Yihui Xie
55d3764169 Fixes #561: should discard the query when the number of columns in the request is different with the number of columns of the actual data 2016-02-03 10:54:37 -06:00
Winston Chang
cb5bc3d631 Merge pull request #1088 from rstudio/joe/bugfix/flexcol
Fix flexCol on RStudio Desktop for Win/Linux
2016-01-20 12:12:51 -06:00
Joe Cheng
543e66eb00 Fix flexCol on RStudio Desktop for Win/Linux
RStudio Desktop requires the older -webkit vendor-prefixed
flex box properties. I missed the one for flex-direction.
2016-01-20 09:20:22 -08:00
Winston Chang
b658983fb8 Remove JavaScript events vignette
This vignette has been migrated to a Shiny Dev Center article.
2016-01-15 15:30:24 -06:00
Winston Chang
cfb3e42337 Merge pull request #1080 from rstudio/internal-messages
Don't use sendCustomMessage for messages internal to Shiny
2016-01-15 12:19:23 -06:00
Winston Chang
36815b5e43 Concat and minify shiny.js 2016-01-15 11:54:10 -06:00
Winston Chang
897e077aca Convert internal use of sendCustomMessage to sendMessage 2016-01-15 11:53:51 -06:00
Winston Chang
f395960ffa Add session$sendMessage wrapper function 2016-01-15 11:46:23 -06:00
Winston Chang
fb301717f5 Bump version to 0.13.0.9000 2016-01-14 10:49:08 -06:00
Winston Chang
46da93519f Bump version to 0.13.0 2016-01-12 13:33:14 -06:00
Winston Chang
ce0f2c51a9 Use explicit namespaces 2016-01-12 13:33:14 -06:00
Winston Chang
04b4b8da4f Use --run-donttest when checking package 2016-01-12 13:16:18 -06:00
Winston Chang
877d7451dd Merge pull request #1073 from rstudio/joe/staticdocs-tweaks
Tweaks for improved staticdocs rendering
2016-01-12 13:00:07 -06:00
Joe Cheng
7e6a68a2b1 CRAN maintainers prefer \donttest over \dontrun 2016-01-12 10:10:16 -08:00
Joe Cheng
caca515ba0 Tweaks for improved staticdocs rendering
- \donttest -> \dontrun, otherwise staticdocs hangs
- Put NS before ns.sep so usage entries are in a better order
2016-01-11 12:20:19 -08:00
Winston Chang
d548b78dee Remove unneeded do.call
It's OK to remove this do.call now that we are using R6 instead of Ref Classes.
2016-01-08 16:19:08 -06:00
Joe Cheng
f2410abc48 Merge remote-tracking branch 'origin/image-attr'
Conflicts:
	inst/www/shared/shiny.js
	inst/www/shared/shiny.js.map
	inst/www/shared/shiny.min.js
	inst/www/shared/shiny.min.js.map
2016-01-08 10:55:21 -08:00
Joe Cheng
483a7d34c5 Merge pull request #1071 from rstudio/fix-hidden-slider
Fix hidden slider
2016-01-08 10:53:00 -08:00
Winston Chang
e872411285 Build and minify shiny.js 2016-01-08 10:00:22 -06:00
Winston Chang
fc7e6bf542 Revert "Simpler fix for updating hidden sliders (#1010)"
This reverts commit 4e1caee7da.
See https://github.com/rstudio/shiny/issues/1010#issuecomment-169971201
2016-01-08 10:00:05 -06:00
Winston Chang
16d42b6421 Add bootstrapLib to staticdocs index 2016-01-07 13:51:56 -06:00
Winston Chang
2f25d25eec Use setAttribute() function to set image attributes. Fixes #936 2016-01-06 15:03:18 -06:00
Winston Chang
be1081a4b9 Add note about printing ggplot2 graphics 2016-01-06 14:11:16 -06:00
Joe Cheng
1608b652d7 Fix unnecessary ::: 2016-01-05 14:02:16 -08:00
Winston Chang
5dd19a878c Bump version to 0.2.12.9009 2016-01-05 15:59:19 -06:00
Winston Chang
3314f4b5b8 Update NEWS 2016-01-05 15:59:11 -06:00
Winston Chang
5977e0fe89 Update NEWS 2016-01-05 15:30:59 -06:00
Winston Chang
f477dcba4a Merge pull request #1068 from rstudio/template-bootstrap
Pull out bootstrapLib into separate function
2016-01-05 15:30:32 -06:00
Winston Chang
6c5f0c5379 Pull out bootstrapLib into separate function 2016-01-05 14:57:53 -06:00
Winston Chang
257eb1bed0 Merge pull request #1067 from rstudio/joe/bugfix/module-progress
Fix progress for Shiny modules
2016-01-05 11:59:18 -06:00
Joe Cheng
9c4d142c2d Fix progress for Shiny modules
Too-specific class check was being used, interfered with the
"duck typed" session-like objects we use for modules.
2016-01-05 09:41:52 -08:00
Winston Chang
8e89a1f154 Add README file with info about jQuery UI 2016-01-05 11:03:34 -06:00
Winston Chang
b0952c0374 Use jQuery UI build without datepicker. Fixes #1042 2016-01-05 11:03:07 -06:00
Winston Chang
ac95dcb3f2 Fix head content location 2016-01-04 10:52:26 -06:00
Joe Cheng
ce4043f038 Update metadata 2015-12-31 11:07:35 -08:00
Joe Cheng
0d26857e31 Merge pull request #1031 from rstudio/template
Use templates from htmltools
2015-12-31 11:03:14 -08:00
Joe Cheng
85bea95f6b Merge pull request #1029 from rstudio/joe/feature/knit_print.reactive
Add knit_print.reactive
2015-12-30 14:31:26 -08:00
Winston Chang
10a46c507f Merge pull request #1059 from rstudio/joe/bugfix/gadgets
Fix stopApp bug, and fix flex box in RStudio
2015-12-30 09:38:24 -06:00
Joe Cheng
d35d76e1d0 Use flex box layout vendor prefixes
This fixes fillRow/fillCol for RStudio on Windows and Linux
2015-12-29 15:59:04 -08:00
Joe Cheng
aaa05b22df Return stopApp value without visibility info 2015-12-29 15:57:23 -08:00
Winston Chang
c5fa30f0de Make encoding tests work cleanly on different platforms 2015-12-29 15:41:49 -06:00
Joe Cheng
43fe1a9a0e Merge pull request #1057 from rstudio/joe/feature/gadget-cancel
runGadget automatically handles cancel button
2015-12-28 14:18:17 -08:00
Joe Cheng
aa296fcb69 Don't show call when user cancels gadget 2015-12-28 13:56:01 -08:00
Joe Cheng
b9c7023489 Refactor server function arg matching 2015-12-28 12:43:06 -08:00
Joe Cheng
efcd286039 Code review feedback 2015-12-28 11:56:13 -08:00
Joe Cheng
98014f9495 Code review feedback 2015-12-28 11:53:52 -08:00
Joe Cheng
2702a18ea2 Add stopApp test 2015-12-28 11:18:15 -08:00
Joe Cheng
4a8da3e1e2 runGadget handles cancel automatically (by default) 2015-12-27 23:46:47 -08:00
Joe Cheng
cfe38c00f3 Proper visibility and error handling from stopApp 2015-12-27 23:35:09 -08:00
Joe Cheng
af0463ed46 Update metadata 2015-12-23 17:47:24 -08:00
Joe Cheng
c02f4691e0 Merge pull request #1054 from rstudio/joe/feature/runGadget
Migrate runGadget and viewer functions from shinygadgets
2015-12-23 17:45:12 -08:00
Joe Cheng
5d89393fff Merge pull request #1039 from rstudio/joe/feature/fillPage
Add fillPage, flexRow, flexCol
2015-12-23 17:44:56 -08:00
Joe Cheng
e7ce28204b Remove dependency on newer htmltools
The css function is in htmltools 0.2.11, which isn't on CRAN
yet. Due to a perfect storm of release scheduling we need to
get fillPage onto shiny master before we have a chance to
put htmltools on CRAN.
2015-12-23 17:32:46 -08:00
Joe Cheng
8fc4a75e8c Specify remote repo for htmltools
Makes devtools install easier
2015-12-23 17:31:11 -08:00
Joe Cheng
26c89a09e8 Update staticdocs index 2015-12-23 17:31:11 -08:00
Joe Cheng
25a1493520 Add details to fillRow doc about containers 2015-12-23 17:31:10 -08:00
Joe Cheng
b18722f776 flex:none is more appropriate than flex:initial for weight=NA 2015-12-23 17:31:10 -08:00
Joe Cheng
3f3fd9ae21 Install htmltools from github until 0.2.11 goes to cran 2015-12-23 17:31:10 -08:00
Joe Cheng
94ea3c7dab Require htmltools 0.2.11 2015-12-23 17:31:10 -08:00
Joe Cheng
6c2fea7926 Rename flexRow/flexCol to fillRow/fillCol, other tweaks
I decided against the name flexRow/flexCol as the "flex" prefix
is too general for these implementations, which are mostly just
useful for filling the space with the children. Flex box has a
lot more features than that, such as centering, wrapping,
justifying, etc., but I don't currently know how to design an
API that presents the full power of flex box without also
presenting the full complexity of it as well.

This commit also includes some tweaks to the impl of flexfill
to fix the behavior of children with 100% size along the main
axis, and also introduces support for NA flex values, which
sizes the flex item according to its contents.
2015-12-23 17:31:10 -08:00
Joe Cheng
e08fd47b0e Add fillPage, flexRow, flexCol 2015-12-23 17:31:05 -08:00
Joe Cheng
0fd76e8768 Migrate runGadget and viewer functions from shinygadgets 2015-12-23 15:33:30 -08:00
Joe Cheng
72aaf3055a Update NEWS 2015-12-23 11:46:03 -08:00
Winston Chang
94a943a68c Merge pull request #1052 from rstudio/joe/font-awesome-4.5.0
Upgrade Font Awesome to 4.5.0
2015-12-23 13:08:27 -06:00
Joe Cheng
e867dcfdb1 Upgrade Font Awesome to 4.5.0 2015-12-23 11:03:33 -08:00
Joe Cheng
9a22a89b06 Add docs 2015-12-23 09:42:29 -08:00
Joe Cheng
791e8200bc Install htmltools from github until 0.2.11 goes to cran 2015-12-22 14:06:51 -08:00
Joe Cheng
d96217d49a Merge pull request #1049 from rstudio/joe/bugfix/http-iframe-stampede
Defer subapp iframe loading
2015-12-22 12:26:49 -08:00
Joe Cheng
6bfd65aa19 Guard against window.Shiny not being defined 2015-12-22 11:53:36 -08:00
Joe Cheng
2da9bc07ac Defer subapp iframe loading until main app has loaded
Fixes #1047 defer loading of iframes
2015-12-22 11:14:12 -08:00
Joe Cheng
6d7a562b7a Fix staticdocs
Without this fix, current version of staticdocs won't pull plotOutput
into the index page.
2015-12-21 11:57:01 -08:00
Joe Cheng
0aa1dfb8e1 Merge pull request #1032 from yihui/yihui/encoding-tests
Add some tests for character encodings
2015-12-17 13:36:26 -08:00
Winston Chang
e9e7dc298f Merge pull request #1037 from rstudio/joe/feature/req
Add `req` function for validating required inputs/values
2015-12-17 14:39:40 -06:00
Joe Cheng
ed3b71e396 Remove unnecessary environment() call 2015-12-17 11:58:17 -08:00
Joe Cheng
3450a037a9 Further simplification and more tests for req() 2015-12-17 11:29:54 -08:00
Joe Cheng
f57626d256 req() simplification 2015-12-17 11:16:09 -08:00
Joe Cheng
c1c3fa4d3a Fix a couple of req edge cases 2015-12-17 11:13:33 -08:00
Joe Cheng
300433f7de req() now short-circuits on falsy values 2015-12-17 10:53:13 -08:00
Joe Cheng
eee6f4ed81 Add req function for validating required inputs/values 2015-12-16 10:04:05 -08:00
Joe Cheng
2eb29bd8aa Merge pull request #1034 from rstudio/fix-hidden-slider
Simpler fix for updating hidden sliders (#1010)
2015-12-15 15:57:11 -08:00
Joe Cheng
3a0ce86f51 Remove unnecessary check (feedback from @wch) 2015-12-15 15:36:43 -08:00
Joe Cheng
6041b8cbb2 Roxygenize 2015-12-14 18:00:29 -08:00
Joe Cheng
3ba8fcb7b8 Merge pull request #1036 from rstudio/joe/bugfix/validation-error-behavior
Fix validation error handling
2015-12-14 17:16:04 -08:00
Joe Cheng
f74d9c93a2 Merge pull request #1035 from rstudio/joe/bugfix/multiline-label
Fix warnings when renderXXX is called with explicit namespace
2015-12-14 17:15:50 -08:00
Joe Cheng
739c162281 Fix warnings when renderXXX is called with explicit namespace
Calling shiny::renderText(...) instead of renderText(...) would
cause warnings.
2015-12-14 17:11:40 -08:00
Joe Cheng
a2700c900d Fix validation error handling
Validation errors were behaving too much like real errors: they were
being printed with stack traces, and passed to the options(shiny.error)
function. Also, if a reactive() cached a validation error, on future
calls the error would be re-raised (which is correct) without the
custom class names attached (which is not).
2015-12-14 16:31:57 -08:00
Winston Chang
4e1caee7da Simpler fix for updating hidden sliders (#1010)
This also reverts the previous fix, #1026
2015-12-10 15:49:23 -06:00
Yihui Xie
76a54249bb Add some tests for character encodings 2015-12-10 15:23:31 -06:00
Winston Chang
0e894cb043 Use templates from htmltools 2015-12-10 15:11:03 -06:00
Winston Chang
01bbee59eb Merge pull request #1023 from yihui/bugfix/1018
Fixes #1018: make sure the selected value is always returned from sever-side selectize
2015-12-10 13:44:24 -06:00
Joe Cheng
26a0c3520c Add knit_print.reactive
Allows us to drop reactive expressions right into Rmd docs
with runtime:shiny and have them stay up-to-date.
2015-12-09 16:34:55 -08:00
Yihui Xie
6056c35de3 add a news item, and run grunt 2015-12-09 11:56:13 -06:00
Yihui Xie
4202991ca5 Fixes #1018: make sure the selected value is always returned from server-side selectize 2015-12-09 11:52:50 -06:00
Joe Cheng
788931c7c7 Merge pull request #1027 from yihui/yihui/srcfilecopy
Check R version at runtime (#968)
2015-12-09 09:48:59 -08:00
Yihui Xie
b2d0505c7c Check R version at runtime (#968) 2015-12-08 20:33:52 -06:00
Winston Chang
8b710d651f Merge pull request #1026 from rstudio/joe/bugfix/hidden-slider-update
Fix #1010: updateSliderInput doesn't update hidden sliders
2015-12-08 19:50:12 -06:00
Joe Cheng
93697bb01d Merge pull request #1025 from yihui/bugfix/source-utf8
Fixes #1003: correctly parse source code that can be represented with native encoding
2015-12-08 15:51:51 -08:00
Joe Cheng
89cd58e4f8 Fix #1010: updateSliderInput doesn't update hidden sliders 2015-12-08 11:13:18 -08:00
Yihui Xie
a622f029a0 Fixes #1003: when the source code can be represented via the native encoding on Windows, rewrite the code to a temporary file with the native encoding and parse it 2015-12-07 16:28:45 -06:00
Winston Chang
97afb52904 Re-document with Roxygen2 5.0.1 2015-12-02 15:43:25 -06:00
Joe Cheng
02ea31be08 Fix #1020: Graphics are cut off 2015-12-01 13:47:26 -08:00
david.zotloeterer
d1353e8eae fixed custom message obj 2015-12-01 13:36:32 +01:00
david.zotloeterer
935a76d16b cleanup 2015-12-01 13:29:41 +01:00
david.zotloeterer
db4c41f420 grunted 2015-12-01 12:59:48 +01:00
david.zotloeterer
62f5af8e0b fixed typo 2015-12-01 12:42:05 +01:00
david.zotloeterer
ff9aefb649 more tagging 2015-12-01 12:39:41 +01:00
david.zotloeterer
2b10d03e1f added binary tags 2015-12-01 12:11:44 +01:00
david.zotloeterer
a27efbd937 added binary messages, yes, ws can do dat! 2015-12-01 12:03:07 +01:00
Joe Cheng
b5e49a6619 Fix bugs reported by @daattali
- reactive srcrefToLabel call fails if code is pasted in at RStudio console
- Stack trace fails when promise (!?) is in call stack
2015-11-24 11:55:08 -08:00
Joe Cheng
179c931f85 Bump version 2015-11-23 10:58:30 -08:00
Joe Cheng
4d3fa2c8ac Merge pull request #1011 from rstudio/joe/feature/better-errors
Better errors
2015-11-20 12:56:24 -08:00
Joe Cheng
8e4f7387d0 Better stack traces for observeEvent and eventReactive 2015-11-16 13:10:53 -08:00
Joe Cheng
feb630b2c5 Catch errors when trying to print stack trace
No known errors will occur when printing the stack trace, but since
this is fairly complicated code executing when an error has already
happened, better to be on the defensive.
2015-11-16 13:08:34 -08:00
Joe Cheng
948dfbb56b More tests 2015-11-16 12:50:20 -08:00
Joe Cheng
5c3ac75b34 Errors in ui function should get stack traces 2015-11-16 12:46:22 -08:00
Joe Cheng
adc5c8e37a Normalize shiny dir in smoketest stack traces 2015-11-16 12:45:59 -08:00
Joe Cheng
52d594c143 Quick and dirty smoke test infrastructure 2015-11-16 12:29:02 -08:00
Joe Cheng
1018b0d966 Errors in ui.R, server.R, server func, etc. all get stack traces 2015-11-16 10:48:16 -08:00
Joe Cheng
0ce153d788 Doc updates
Code had to be rearranged to make the functions show up in the
help docs in the right order
2015-11-14 00:26:23 -08:00
Joe Cheng
ff9756c739 Add more exports, tests 2015-11-14 00:17:19 -08:00
Joe Cheng
b3dd7e5397 Remove support for shiny.observer.error option
It is dangerous to set this on a per-process basis. If we even
still want the ability to have observer errors not kill the
session (debatable) we should do so by having an option on the
session object that is set when the shiny server function runs.

Not only does this remove support for the option, but now we
will refuse to connect and will abort the session if the option
is set to a non-NULL value. This is to prevent apps from moving
forward with the assumption that their option will work, when
it really won't. (I doubt anyone is using it anyway, it's so
obscure...)
2015-11-13 18:13:14 -08:00
Joe Cheng
6ac0a80896 Update NEWS with stack trace announcement 2015-11-13 17:58:18 -08:00
Joe Cheng
93f774c7e7 Add exports/docs 2015-11-13 17:52:26 -08:00
Joe Cheng
661c08549d Automatically wrap installExprFunction with function labels
This lowers the burden on renderXXX functions having nice stack traces
2015-11-13 16:43:15 -08:00
Joe Cheng
c8acc44012 Add comments to stacktraceon/off directives indicating their partners 2015-11-13 16:25:22 -08:00
Joe Cheng
aabf00659e Random improvements to stack traces
- More selective removal of frames at end of call stack
- Add withLogErrors, printError, stripStackTrace convenience funcs
- Properly capture/log errors for various levels of unhandled errors
- Unhide stacks for flush/flushed/sessionended callbacks
2015-11-13 16:03:18 -08:00
Joe Cheng
a9bc41492c Collapse stack trace to eliminate irrelevant bits 2015-11-13 12:36:55 -08:00
Winston Chang
12b0484e9a Merge pull request #875 from wch/rendertable-args
Separate xtable from print.xtable args. Closes #761
2015-11-13 11:48:41 -06:00
Joe Cheng
d3605dbcb3 Collapse parts of the stack 2015-11-12 16:40:56 -08:00
Joe Cheng
d9a016f94c Move error-handling logic into conditions.R 2015-11-11 16:20:26 -08:00
Joe Cheng
a13657ac23 Work properly with non-call sys.call() objects 2015-11-11 15:54:03 -08:00
Joe Cheng
d6c95a9e89 wip 2015-11-11 12:14:42 -08:00
Winston Chang
825e9e04c1 Get xtable args using formals() 2015-11-05 13:04:43 -06:00
Winston Chang
c596e44c5a Separate xtable from print.xtable args. Closes #761 2015-11-05 13:01:01 -06:00
Winston Chang
66be9004fe Move renderTable to separate file 2015-11-05 12:59:04 -06:00
Winston Chang
119ebb0f07 Merge pull request #1005 from rstudio/joe/bootstrap-3.3.5
Upgrade bootstrap to 3.3.5
2015-11-05 12:43:34 -06:00
Joe Cheng
d509fcac29 Merge pull request #1004 from rstudio/joe/feature/autoreload
Implement autoreload
2015-11-05 10:43:13 -08:00
Joe Cheng
c79e933586 Merge pull request #1002 from rstudio/joe/bugfix/nested-scopes
Fix nested scopes
2015-11-05 10:32:23 -08:00
Joe Cheng
3c3cfc02a0 Merge pull request #997 from wch/download-fix
Update download to work correctly with R 3.2.2 on Mac. Fixes #996
2015-11-05 10:27:51 -08:00
Joe Cheng
e32de7b940 Update bootstrap htmlDependency version 2015-11-05 07:38:00 -08:00
Joe Cheng
d7b1759afb Upgrade bootstrap to 3.3.5 2015-11-04 22:56:31 -08:00
Joe Cheng
e391c1fda3 Implement autoreload 2015-11-04 13:44:38 -08:00
Joe Cheng
46d8d3b469 Fix nested scopes
The session$makeScope function was not correctly taking the session's
own namespace into account (i.e. all makeScope() method calls were
being performed against the root session).
2015-11-03 12:33:39 -08:00
Joe Cheng
9fde7509fa Merge pull request #1000 from rstudio/joe/feature/invalidatelater-default-session
Don't require session to be explicitly passed to invalidateLater
2015-11-02 16:21:43 -08:00
Joe Cheng
a767a61f43 Don't require session to be explicitly passed to invalidateLater
These functions were created before getDefaultReactiveDomain()
existed, so the only way to get ahold of the current session was
if the caller explicitly passed it.

This is slightly backwards incompatible, in that existing calls
to invalidateLater() that don't pass a session argument will
behave slightly differently (bound to the current session instead
of to no session), but those calls would have triggered a warning
for all but the very earliest versions of Shiny.
2015-11-02 14:19:29 -08:00
Joe Cheng
ad28e03536 Merge pull request #999 from yihui/bugfix/ie9-upload
Fixes #990: revert to checking if FileList is supported by the browser
2015-11-02 11:13:57 -08:00
Yihui Xie
5f9e9c2e03 grunt 2015-11-02 12:21:56 -06:00
Yihui Xie
2be6d7a65c Fixes #990: revert to checking if FileList is supported by the browser, instead of checking IE8 literally (partially reverted a41e1dafc2) 2015-11-02 12:21:30 -06:00
Winston Chang
712c4cb985 Update download to work correctly with R 3.2.2 on Mac. Fixes #996 2015-10-30 12:08:48 -05:00
Winston Chang
7948a0a4fa Merge pull request #993 from rstudio/joe/feature/reactlog-labels
Cleaner reactlog labels for reactives and outputs
2015-10-30 11:57:57 -05:00
Joe Cheng
ce9e95f256 Fix parseQueryString example (thanks Chris Beeley) 2015-10-29 16:15:47 -07:00
Joe Cheng
7ed5ca94a2 reactlog label code review feedback
- Don't clobber explicitly-provided label
- Let `reactives$x1 <- reactive(...)` work
2015-10-29 10:27:06 -07:00
Winston Chang
e635589c52 Merge pull request #992 from rstudio/joe/feature/reactlog-time
Add timing data to reactlog entries
2015-10-29 10:06:08 -05:00
Joe Cheng
490721437f Merge pull request #994 from RCura/patch-1
Correct a regression within modules
2015-10-28 18:23:36 -07:00
Robin Cura
7e25a1566f Correct a regression within modules
Commit 07f2792cf9 introduced an error, replacing `e$parent = parentSession` with `e$parent <= parentSession`, while it should have been `e$parent <- parentSession`
2015-10-29 02:03:12 +01:00
Joe Cheng
c08b3b0c30 Update metadata 2015-10-28 15:30:07 -07:00
Joe Cheng
f1de132a2a Merge pull request #988 from rstudio/joe/feature/namespaces
Shiny modules
2015-10-28 15:22:57 -07:00
Joe Cheng
c440c60bdf Cleaner reactlog labels for reactives and outputs
Instead of showing the code, try to just show the name
of the reactive/output. Uses a fairly flaky algorithm
for determining the name of the reactive; will only
work in cases where the definition of the reactive
begins with "foo <- reactive({".
2015-10-28 14:37:00 -07:00
Joe Cheng
c367176a17 Add timing data to reactlog entries 2015-10-28 13:49:39 -07:00
Joe Cheng
3d13c39a4c Also override registerDataObj (essential for data table, DT, and selectize server mode) 2015-10-27 16:33:46 -07:00
Joe Cheng
07f2792cf9 Change evil = to righteous <- 2015-10-27 15:01:44 -07:00
Joe Cheng
303fce5f15 Merge pull request #983 from wch/clear-checkbox
updateCheckboxGroup: allow clearing all choices. Fixes #981
2015-10-27 15:00:20 -07:00
Joe Cheng
46c8bfdd34 Merge pull request #979 from wch/profiling-enhancements
Enhancements to help profiling
2015-10-27 14:59:59 -07:00
Winston Chang
c36a22ad5e Add information about clearing choices 2015-10-23 16:04:17 -05:00
Winston Chang
49eba95a9c Simplify function naming 2015-10-23 15:48:33 -05:00
Winston Chang
2dacc6ce40 Use cleaner method for generating function 2015-10-23 15:44:24 -05:00
Winston Chang
5ccf02f5c3 Merge pull request #984 from rstudio/update-jquery
Update to jQuery 1.11.3 and jQuery UI 1.11.4
2015-10-23 15:08:27 -05:00
Joe Cheng
86c67de8ff Add session$ns(); some light refactoring; add tests; fix staticdocs 2015-10-20 23:49:24 -07:00
Joe Cheng
dac7eb5997 Simplify modules, by removing moduleUI and path arguments 2015-10-20 15:12:11 -07:00
Winston Chang
fd725552a5 Update to jQuery UI 1.11.4 2015-10-19 13:44:23 -05:00
Winston Chang
1e1a897970 Update to jQuery 1.11.3 2015-10-19 13:31:44 -05:00
Joe Cheng
59a643c006 Update metadata 2015-10-16 10:24:05 -07:00
Joe Cheng
1a492208e6 Merge pull request #982 from rstudio/feature/single-file-runapp
Let runApp accept single .R file parameters
2015-10-16 10:16:29 -07:00
Winston Chang
bc0a19f55d updateCheckboxGroup: allow clearing all choices. Fixes #981 2015-10-16 11:44:09 -05:00
Jonathan McPherson
85e2e00bc4 let runApp accept single .R file parameters 2015-10-16 09:22:44 -07:00
Joe Cheng
93dd8bbf28 Merge pull request #980 from rstudio/joe/bugfix/select-value-escaping
Fix improperly escaped quote chars in select choices
2015-10-15 12:56:54 -07:00
Joe Cheng
2f15a219df Fix improperly escaped quote chars in select choices 2015-10-15 12:49:50 -07:00
Yihui Xie
4fc73b1344 A news item for #968 2015-10-15 13:45:00 -05:00
Joe Cheng
48b56ba08d Merge pull request #968 from yihui/bugfix/961-unicode
A more fundamental fix to the Unicode issue
2015-10-15 11:17:45 -07:00
Joe Cheng
94ca733c7c Merge pull request #967 from rstudio/joe/bugfix/placeholder
Add placeholder parameter to textInput
2015-10-15 11:15:10 -07:00
Winston Chang
4af2436a0e Rename 'func' to more descriptive name 2015-10-14 16:40:33 -05:00
Winston Chang
20c2bb9d50 Simplify try(tryCatch()) to just tryCatch()
This also has the benefit of printing a more helpful error message at
the console. It now indicates which output an error occurred in.
2015-10-14 16:40:33 -05:00
Winston Chang
33bac0db3c Add function relabeler and relabel outputs 2015-10-14 16:40:33 -05:00
Winston Chang
8893db9098 Update NEWS 2015-10-12 22:39:51 -05:00
Winston Chang
c46658a5c8 Correctly handle unit and unit.list objects. Closes #962 2015-10-12 22:31:32 -05:00
Joe Cheng
5f651aed3e LICENSE typo 2015-10-12 12:32:00 -04:00
Joe Cheng
534bc9c6e2 Pass tests 2015-10-05 15:15:54 -04:00
Joe Cheng
bb09885237 Add callModule/moduleUI functions; scope sendInputMessage 2015-10-05 14:56:01 -04:00
Joe Cheng
3ff6aaa6db Add namespacing functionality
- Need to carefully review ShinySession, think about
  each and every public method and whether it should
  be passthrough or do something different for a
  namespaced session.

- It would be nice if we do the namespace splitting
  at the server.R level and actually have numerous
  reactivevalues objects for inputs, one for each
  namespace. This will make the reactive hooks for
  names() and reactiveValuesToList() correct, as
  right now they will oversubscribe if you're in a
  namespaced session. Also it would prevent outer
  sessions from seeing the values of their child
  namespaces. (Though is that good or bad...?)
2015-10-02 12:51:18 -07:00
Joe Cheng
fa7c034d16 Merge pull request #955 from rstudio/joe/feature/reactlog-bysession
Fix couple of issues with reactlog
2015-09-24 09:54:35 -07:00
Joe Cheng
ca870ccd75 Merge pull request #966 from rstudio/joe/bugfix/showcase-highlighting
Fix highlighting of reactives in showcase mode
2015-09-24 09:54:01 -07:00
Yihui Xie
89fe2ff217 a more fundamental fix of the Unicode issue
for R <= 3.2.2, Unicode chars don't work for shiny mainly because we want to
preserve the source reference, and unfortunately srcfilecopy() fails because of
the bug https://bugs.r-project.org/bugzilla3/show_bug.cgi?id=16264

here I use lines = '' to get around the bug, and assign the source lines to
the srcfile object later, so there is no grep("\n", multibyte_chars) occuring

I also replaced source() with a custom version, which is much simpler and works
better with Unicode chars
2015-09-23 23:00:20 -05:00
Joe Cheng
6c6775376e Add placeholder parameter to textInput 2015-09-23 18:28:08 -07:00
Yihui Xie
bd3b3881d8 after reading a file as UTF-8, try to convert it to native encoding on Windows only if the conversion is not lossy 2015-09-23 18:33:01 -05:00
Yihui Xie
078189599c the check is.na(iconv(x, 'UTF-8')) does not really make sense
e.g. iconv('\u2264', 'UTF-8') converts the smaller than or equal sign to an
equal sign in the English (US) locale, which is lossy

we just assume the input is UTF-8 instead, and do not fall back to native
encoding any more
2015-09-23 18:31:16 -05:00
Yihui Xie
9afcd2a411 don't declare encoding on the anonymouse file connection
otherwise writeLines(useBytes = TRUE) may not work (#961)
2015-09-22 01:11:38 -05:00
Yihui Xie
0afd1649c1 textConnection() can be lossy; use an anonymous file connection instead to preserve UTF-8 characters 2015-09-22 01:11:37 -05:00
Yihui Xie
0947a63103 make sure writeLines() does not convert text to native encoding 2015-09-22 01:11:32 -05:00
Yihui Xie
79223bddc5 make sure the UI HTML is rendered into a connection with UTF-8 encoding 2015-09-21 22:25:12 -05:00
Yihui Xie
1871dd6b71 convert the result of readLines() to UTF-8 using enc2utf8() instead, since enc2native() can be lossy 2015-09-21 21:49:12 -05:00
Yihui Xie
4adc3088d1 fixes #961: the JSON string from jsonlite::toJSON() is always encoded in UTF-8, so there is no need to re-encode it
actually re-encoding the string with native encoding can be lossy (some UTF-8 characters may not be representable in the native encoding)
2015-09-21 21:46:11 -05:00
Yihui Xie
79dd7e1bf5 shiny requires R >= 3.0.0 2015-09-21 21:44:03 -05:00
Joe Cheng
6718d377bb Fix and enhance reactlog
* Reactlog shortcut was broken due to reactlog.js not being included in
  the gruntfile
* Reactlog has always shown every reactive event since the beginning of
  the process (or at least since options(shiny.reactlog=TRUE)). This
  commit makes it so Ctrl+F3 invocation only shows reactives that either
  belong to no session (e.g. observers and reactives declared at the top
  of server.R), or belong to this session.
2015-09-13 02:46:52 -07:00
Joe Cheng
d4e3329d7a Fix highlighting of reactives in showcase mode
This has been broken since we switched to jsonlite. Reactives
don't highlight because their srcref is in an S3(?) class that
jsonlite doesn't recognize, whereas RJSONIO would treat it as
a numeric vector.
2015-09-12 20:20:13 -07:00
Joe Cheng
99295f0983 Bump version, update NEWS 2015-09-09 22:13:45 -07:00
Winston Chang
3606c36cb9 Merge pull request #950 from rstudio/joe/feature/grunt-clean
Add 'grunt clean' to delete shiny build artifacts
2015-09-09 15:49:02 -05:00
Winston Chang
3d5c184acc Merge pull request #949 from rstudio/joe/feature/shared-brush-id
Allow shared brush IDs
2015-09-09 15:48:43 -05:00
Joe Cheng
9e03b17498 Add 'grunt clean' to delete shiny build artifacts 2015-09-09 12:19:43 -07:00
Joe Cheng
129714b044 Allow shared brush IDs 2015-09-09 12:12:47 -07:00
Joe Cheng
67823556d2 Merge pull request #939 from yihui/bugfix/selectize-selected
Fixes #929: defer setValue() for selectize until the options have been loaded from server
2015-09-09 12:10:03 -07:00
Yihui Xie
273e71e3c4 run grunt 2015-09-09 13:38:51 -05:00
Yihui Xie
076ac26929 call setValue() only once
otherwise every time an Ajax response is returned (e.g. during searching), the value is set to data.value
2015-09-09 13:29:37 -05:00
Yihui Xie
5a022b0a2c upgrade selectize.js from 0.11.2 to 0.12.1 to fix the ordering issue in #929
also fixes #865
2015-09-09 13:29:37 -05:00
Yihui Xie
9ab493a81f make sure mop is numeric
it is funny that head(1:11,'100') produces a bunch of NA's, but  head(1:11,'12') does not
2015-09-09 13:29:37 -05:00
Yihui Xie
dfc1f32595 Fixes #929: defer setValue() for selectize until the options have been loaded from server
Previously I was using a hack to set the selected value of selectize, i.e. add the selected option(s) to selectize via addOptions() (because the selected option(s) may not have existed); this hack can be removed and we can set the value in the `success` callback of the Ajax request, by which time the options will be available
2015-09-09 13:29:37 -05:00
Joe Cheng
e6fd30fb78 Merge pull request #935 from yihui/doc/events-vignette
Add a package vignette for the JS events
2015-09-09 10:33:52 -07:00
Joe Cheng
c8d338912a Merge pull request #918 from rstudio/joe/bugfix/binding-getid
Fix #676: getId(el) not being used in output binding
2015-09-09 10:33:45 -07:00
Winston Chang
d9d9e0b33f Add info about printing ggplot objects 2015-09-08 15:07:40 -05:00
Yihui Xie
1da7b83956 Bump version 2015-09-04 20:54:30 -05:00
Yihui Xie
29c545d2e3 Bump fontawesome version after #932 2015-09-04 20:53:54 -05:00
Joe Cheng
431b345c82 Fix #676: getId(el) not being used in output binding 2015-09-04 15:14:04 -07:00
Joe Cheng
8773b1b38f Merge pull request #946 from yihui/bugfix/recalculating
Typo: name -> message.name
2015-09-04 14:53:43 -07:00
Yihui Xie
52efb3dc16 Typo: name -> message.name 2015-09-04 15:43:21 -05:00
Yihui Xie
9ccd179b04 document the rest of events: shiny:conditional, shiny:visualchange, shiny:recalculating, shiny:recalculated 2015-09-04 15:09:34 -05:00
Joe Cheng
a8b35c49a7 Merge pull request #932 from dselivanov/master
Bump fontawesome version to 4.4.0, fixes #913
2015-09-04 12:15:31 -07:00
Peter K. Shultz
4e027f1a45 Update README.md
Add missing period.
2015-09-04 12:13:28 -07:00
Joe Cheng
119182454b Merge pull request #943 from yihui/feature/events-more
Events shiny:visualchange, shiny:conditional, and shiny:recalculating
2015-09-04 11:34:48 -07:00
Yihui Xie
04e10a4f0d Run grunt 2015-09-02 12:30:22 -05:00
Yihui Xie
4e5c5f9c5b Move the shiny:conditional event to the top of $updateConditionals() 2015-09-02 12:29:37 -05:00
Yihui Xie
1ee2a25eca Trigger the recalculating event on null when the binding does not exist 2015-09-02 12:29:01 -05:00
Yihui Xie
838e132515 Trigger shiny:visualchange in doSendOutputHiddenState() as well. Added a 'visible' property to the event object. 2015-08-31 17:07:11 -05:00
Yihui Xie
da76a843ee DO not assume '# + message.name' is the element id. Find the element through this.$bindings[name].el instead. 2015-08-31 17:02:14 -05:00
Yihui Xie
b0676b8b31 Trigger shiny:recalculated after the calculation is done 2015-08-31 16:38:46 -05:00
Yihui Xie
83c3656d29 Events shiny:visualchange, shiny:conditional, and shiny:recalculating 2015-08-31 15:58:05 -05:00
Yihui Xie
5ddd6cc94e add a summary section to the events vignette 2015-08-27 23:21:36 -05:00
Yihui Xie
90419765af Merge pull request #938 from yihui/bugfix/933
Fixes #933: make sure type is of length one
2015-08-26 11:27:05 -05:00
Yihui Xie
acad9354a6 Fixes #933: make sure type is of length one 2015-08-26 10:48:38 -05:00
Yihui Xie
9105dd7b04 Add a package vignette for the JS events 2015-08-24 15:25:58 -05:00
Joe Cheng
71adee1f38 Merge pull request #930 from yihui/feature/events
More JS events
2015-08-24 12:50:10 -07:00
Yihui Xie
3cbe3831ec Run grunt 2015-08-21 16:19:02 -05:00
Yihui Xie
619aa4f05a Events shiny:busy and shiny:idle
Keep track of the number of observer callbacks during addPendingFlush() and executeFlushCallbacks(). Send a custom message when the number is 0.
2015-08-21 16:12:45 -05:00
Yihui Xie
4df37d6f3e The shiny:updateinput event for inputs 2015-08-21 16:10:01 -05:00
Yihui Xie
58f8b482f5 The message object may be exactly false (or a falsy value), in which case shiny will fail to send the message to the handler. Use .hasOwnProperty() instead. 2015-08-21 16:09:25 -05:00
Yihui Xie
168f5e32af The shiny:message event 2015-08-21 16:08:16 -05:00
Yihui Xie
06bf28f10c Events shiny:value and shiny:error for outputs 2015-08-21 16:07:33 -05:00
Yihui Xie
620fdc0d9f The shiny:inputchanged event 2015-08-21 16:06:56 -05:00
Yihui Xie
bdac0e2456 Events shiny:bound and shiny:unbound for inputs and outputs 2015-08-21 16:06:00 -05:00
Dmitry Selivanov
60a57a0a40 Bump fontawesome version to 4.4.0, fixes #913 2015-08-20 12:34:52 +03:00
Yihui Xie
a242ae3849 Merge pull request #920 from yihui/yihui/selectize-dragdrop
fixes #902: add jQueryUI dependency when the drag_drop plugin is used in selectize
2015-08-06 21:46:30 -05:00
Yihui Xie
ced4060b5c fixes #902: add jQueryUI dependency when the drag_drop plugin is used in selectize 2015-08-06 16:59:51 -05:00
Winston Chang
0b3eb7a237 Merge tag 'v0.12.2'
Shiny 0.12.2
2015-08-05 15:08:21 -05:00
568 changed files with 69281 additions and 28715 deletions

View File

@@ -6,6 +6,7 @@
^shiny\.cmd$
^run\.R$
^\.gitignore$
^smoketests$
^res$
^man-roxygen$
^\.travis\.yml$
@@ -14,3 +15,8 @@
^srcjs$
^CONTRIBUTING.md$
^cran-comments.md$
^.*\.o$
^appveyor\.yml$
^revdep$
^TODO-promises.md$
^manualtests$

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@
/src-x86_64/
shinyapps/
README.html
.*.Rnb.cached

View File

@@ -1,10 +1,10 @@
language: r
warnings_are_errors: true
r_binary_packages:
- Rcpp
- cairo
- knitr
r:
- oldrel
- release
- devel
sudo: false
cache: packages
notifications:
email:

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. Ensure that you have signed the [individual](https://rstudioblog.files.wordpress.com/2017/05/rstudio_individual_contributor_agreement.pdf) or [corporate](https://rstudioblog.files.wordpress.com/2017/05/rstudio_corporate_contributor_agreement.pdf) contributor agreement as appropriate. You can send the signed copy to jj@rstudio.com.
3. Submit a [pull request](https://help.github.com/articles/using-pull-requests).
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,8 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 0.12.2
Date: 2015-08-04
Version: 1.1.0.9001
Authors@R: c(
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"),
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
@@ -15,7 +14,7 @@ Authors@R: c(
person(family = "jQuery contributors", role = c("ctb", "cph"),
comment = "jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt"),
person(family = "jQuery UI contributors", role = c("ctb", "cph"),
comment = "jQuery UI library; authors listed in inst/www/shared/jqueryui/1.10.4/AUTHORS.txt"),
comment = "jQuery UI library; authors listed in inst/www/shared/jqueryui/AUTHORS.txt"),
person("Mark", "Otto", role = "ctb",
comment = "Bootstrap library"),
person("Jacob", "Thornton", role = "ctb",
@@ -57,41 +56,63 @@ Authors@R: c(
)
Description: Makes it incredibly easy to build interactive web
applications with R. Automatic "reactive" binding between inputs and
outputs and extensive pre-built widgets make it possible to build
outputs and extensive prebuilt widgets make it possible to build
beautiful, responsive, and powerful applications with minimal effort.
License: GPL-3 | file LICENSE
Depends:
R (>= 3.0.0),
R (>= 3.0.2),
methods
Imports:
utils,
httpuv (>= 1.3.3),
grDevices,
httpuv (>= 1.4.3.9001),
mime (>= 0.3),
jsonlite (>= 0.9.16),
xtable,
digest,
htmltools (>= 0.2.6),
R6 (>= 2.0)
htmltools (>= 0.3.5),
R6 (>= 2.0),
sourcetools,
later (>= 0.7.2),
promises (>= 1.0.1),
tools,
crayon,
rlang
Suggests:
datasets,
Cairo (>= 1.5-5),
testthat,
knitr (>= 1.6),
markdown,
ggplot2
rmarkdown,
ggplot2,
magrittr
URL: http://shiny.rstudio.com
BugReports: https://github.com/rstudio/shiny/issues
Remotes:
tidyverse/ggplot2,
rstudio/httpuv
Collate:
'app.R'
'bookmark-state-local.R'
'stack.R'
'bookmark-state.R'
'bootstrap-layout.R'
'map.R'
'globals.R'
'conditions.R'
'map.R'
'utils.R'
'bootstrap.R'
'cache.R'
'cache-context.R'
'cache-disk.R'
'cache-memory.R'
'cache-utils.R'
'diagnose.R'
'fileupload.R'
'stack.R'
'graph.R'
'reactives.R'
'reactive-domains.R'
'history.R'
'hooks.R'
'html-deps.R'
'htmltools.R'
@@ -111,23 +132,34 @@ Collate:
'input-slider.R'
'input-submit.R'
'input-text.R'
'input-textarea.R'
'input-utils.R'
'insert-tab.R'
'insert-ui.R'
'jqueryui.R'
'middleware-shiny.R'
'middleware.R'
'modal.R'
'modules.R'
'notifications.R'
'priorityqueue.R'
'progress.R'
'react.R'
'reactive-domains.R'
'reactives.R'
'render-cached-plot.R'
'render-plot.R'
'render-table.R'
'run-url.R'
'serializers.R'
'server-input-handlers.R'
'server.R'
'shiny-options.R'
'shiny.R'
'shinyui.R'
'shinywrappers.R'
'showcase.R'
'snapshot.R'
'tar.R'
'test-export.R'
'timer.R'
'update-input.R'
RoxygenNote: 6.1.0

View File

@@ -1,7 +1,7 @@
The shiny package as a whole is distributed under GPL-3 (GNU GENERAL PUBLIC
LICENSE version 3).
The shiny package inludes other open source software components. The following
The shiny package includes other open source software components. The following
is a list of these components (full copies of the license agreements used by
these components are included below):
@@ -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

@@ -1,14 +1,17 @@
# Generated by roxygen2 (4.1.1): do not edit by hand
# Generated by roxygen2: do not edit by hand
S3method("$",reactivevalues)
S3method("$",session_proxy)
S3method("$",shinyoutput)
S3method("$<-",reactivevalues)
S3method("$<-",session_proxy)
S3method("$<-",shinyoutput)
S3method("[",reactivevalues)
S3method("[",shinyoutput)
S3method("[<-",reactivevalues)
S3method("[<-",shinyoutput)
S3method("[[",reactivevalues)
S3method("[[",session_proxy)
S3method("[[",shinyoutput)
S3method("[[<-",reactivevalues)
S3method("[[<-",shinyoutput)
@@ -19,11 +22,18 @@ 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,key_missing)
S3method(print,reactive)
S3method(print,shiny.appobj)
S3method(str,reactivevalues)
export("conditionStackTrace<-")
export(..stacktraceoff..)
export(..stacktraceon..)
export(HTML)
export(NS)
export(Progress)
export(a)
export(absolutePanel)
@@ -31,38 +41,62 @@ export(actionButton)
export(actionLink)
export(addResourcePath)
export(animationOptions)
export(appendTab)
export(as.shiny.appobj)
export(basicPage)
export(bookmarkButton)
export(bootstrapLib)
export(bootstrapPage)
export(br)
export(browserViewer)
export(brushOpts)
export(brushedPoints)
export(callModule)
export(captureStackTraces)
export(checkboxGroupInput)
export(checkboxInput)
export(clickOpts)
export(code)
export(column)
export(conditionStackTrace)
export(conditionalPanel)
export(createRenderFunction)
export(createWebDependency)
export(dataTableOutput)
export(dateInput)
export(dateRangeInput)
export(dblclickOpts)
export(debounce)
export(dialogViewer)
export(diskCache)
export(div)
export(downloadButton)
export(downloadHandler)
export(downloadLink)
export(em)
export(enableBookmarking)
export(eventReactive)
export(exportTestValues)
export(exprToFunction)
export(extractStackTrace)
export(fileInput)
export(fillCol)
export(fillPage)
export(fillRow)
export(fixedPage)
export(fixedPanel)
export(fixedRow)
export(flowLayout)
export(fluidPage)
export(fluidRow)
export(formatStackTrace)
export(freezeReactiveVal)
export(freezeReactiveValue)
export(getCurrentOutputInfo)
export(getDefaultReactiveDomain)
export(getQueryString)
export(getShinyOption)
export(getUrlHash)
export(h1)
export(h2)
export(h3)
@@ -71,9 +105,11 @@ export(h5)
export(h6)
export(headerPanel)
export(helpText)
export(hideTab)
export(hoverOpts)
export(hr)
export(htmlOutput)
export(htmlTemplate)
export(icon)
export(imageOutput)
export(img)
@@ -84,14 +120,21 @@ export(includeMarkdown)
export(includeScript)
export(includeText)
export(inputPanel)
export(insertTab)
export(insertUI)
export(installExprFunction)
export(invalidateLater)
export(is.key_missing)
export(is.reactive)
export(is.reactivevalues)
export(is.shiny.appobj)
export(is.singleton)
export(isRunning)
export(isTruthy)
export(isolate)
export(key_missing)
export(knit_print.html)
export(knit_print.reactive)
export(knit_print.shiny.appobj)
export(knit_print.shiny.render.function)
export(knit_print.shiny.tag)
@@ -100,23 +143,39 @@ export(mainPanel)
export(makeReactiveBinding)
export(markRenderFunction)
export(maskReactiveContext)
export(memoryCache)
export(modalButton)
export(modalDialog)
export(navbarMenu)
export(navbarPage)
export(navlistPanel)
export(nearPoints)
export(need)
export(ns.sep)
export(numericInput)
export(observe)
export(observeEvent)
export(onBookmark)
export(onBookmarked)
export(onFlush)
export(onFlushed)
export(onReactiveDomainEnded)
export(onRestore)
export(onRestored)
export(onSessionEnded)
export(onStop)
export(outputOptions)
export(p)
export(pageWithSidebar)
export(paneViewer)
export(parseQueryString)
export(passwordInput)
export(plotOutput)
export(plotPNG)
export(pre)
export(prependTab)
export(printError)
export(printStackTrace)
export(radioButtons)
export(reactive)
export(reactiveFileReader)
@@ -127,10 +186,16 @@ export(reactiveTable)
export(reactiveText)
export(reactiveTimer)
export(reactiveUI)
export(reactiveVal)
export(reactiveValues)
export(reactiveValuesToList)
export(registerInputHandler)
export(removeInputHandler)
export(removeModal)
export(removeNotification)
export(removeTab)
export(removeUI)
export(renderCachedPlot)
export(renderDataTable)
export(renderImage)
export(renderPlot)
@@ -139,29 +204,46 @@ export(renderTable)
export(renderText)
export(renderUI)
export(repeatable)
export(req)
export(restoreInput)
export(runApp)
export(runExample)
export(runGadget)
export(runGist)
export(runGitHub)
export(runUrl)
export(safeError)
export(selectInput)
export(selectizeInput)
export(serverInfo)
export(setBookmarkExclude)
export(setProgress)
export(setSerializer)
export(shinyApp)
export(shinyAppDir)
export(shinyAppFile)
export(shinyOptions)
export(shinyServer)
export(shinyUI)
export(showBookmarkUrlModal)
export(showModal)
export(showNotification)
export(showReactLog)
export(showTab)
export(sidebarLayout)
export(sidebarPanel)
export(singleton)
export(sizeGrowthRatio)
export(sliderInput)
export(snapshotExclude)
export(snapshotPreprocessInput)
export(snapshotPreprocessOutput)
export(span)
export(splitLayout)
export(stopApp)
export(strong)
export(submitButton)
export(suppressDependencies)
export(tabPanel)
export(tableOutput)
export(tabsetPanel)
@@ -172,10 +254,13 @@ export(tagAppendChildren)
export(tagList)
export(tagSetChildren)
export(tags)
export(textAreaInput)
export(textInput)
export(textOutput)
export(throttle)
export(titlePanel)
export(uiOutput)
export(updateActionButton)
export(updateCheckboxGroupInput)
export(updateCheckboxInput)
export(updateDateInput)
@@ -183,17 +268,25 @@ export(updateDateRangeInput)
export(updateNavbarPage)
export(updateNavlistPanel)
export(updateNumericInput)
export(updateQueryString)
export(updateRadioButtons)
export(updateSelectInput)
export(updateSelectizeInput)
export(updateSliderInput)
export(updateTabsetPanel)
export(updateTextAreaInput)
export(updateTextInput)
export(updateVarSelectInput)
export(updateVarSelectizeInput)
export(urlModal)
export(validate)
export(validateCssUnit)
export(varSelectInput)
export(varSelectizeInput)
export(verbatimTextOutput)
export(verticalLayout)
export(wellPanel)
export(withLogErrors)
export(withMathJax)
export(withProgress)
export(withReactiveDomain)
@@ -205,3 +298,5 @@ import(httpuv)
import(methods)
import(mime)
import(xtable)
importFrom(grDevices,dev.cur)
importFrom(grDevices,dev.set)

870
NEWS
View File

@@ -1,870 +0,0 @@
shiny 0.12.2
--------------------------------------------------------------------------------
* GitHub changed URLs for gists from .tar.gz to .zip, so `runGist` was updated
to work with the new URLs.
* Callbacks from the session object are now guaranteed to execute in the order
in which registration occurred.
* Minor bugs in sliderInput's animation behavior have been fixed. (#852)
* Updated to ion.rangeSlider to 2.0.12.
* Added `shiny.minified` option, which controls whether the minified version
of shiny.js is used. Setting it to FALSe can be useful for debugging. (#826,
#850)
* Fixed an issue for outputting plots from ggplot objects which also have an
additional class whose print method takes precedence over `print.ggplot`.
(#840, 841)
* Added `width` option to Shiny's input functions. (#589, #834)
* Added two alias functions of `updateTabsetPanel()` to update the selected tab:
`updateNavbarPage()` and `updateNavlistPanel()`. (#881)
* All non-base functions are now explicitly namespaced, to pass checks in
R-devel.
* Shiny now correctly handles HTTP HEAD requests. (#876)
shiny 0.12.1
--------------------------------------------------------------------------------
* Fixed an issue where unbindAll() causes subsequent bindAll() to be ignored for
previously bound outputs. (#856)
* Undeprecate `dataTableOutput` and `renderDataTable`, which had been deprecated
in favor of the new DT package. The DT package is a bit too new and has a
slightly different API, we were too hasty in deprecating the existing Shiny
functions.
shiny 0.12.0
--------------------------------------------------------------------------------
* Switched from RJSONIO to jsonlite. This improves consistency and speed when
converting between R data structures and JSON. One notable change is that
POSIXt objects are now serialized to JSON in UTC8601 format (like
"2015-03-20T20:00:00Z"), instead of as seconds from the epoch).
* In addition to the existing support for clicking and hovering on plots
created by base graphics, added support for double-clicking and brushing.
(#769)
* Added support for clicking, hovering, double-clicking, and brushing for
plots created by ggplot2, including support for facets. (#802)
* Added `nearPoints` and `brushedPoints` functions for easily selecting rows of
data that are clicked/hovered, or brushed. (#802)
* Added `shiny.port` option. If this is option is set, `runApp()` will listen on
this port by default. (#756)
* `runUrl`, `runGist`, and `runGitHub` now can save downloaded applications,
with the `destdir` argument. (#688)
* Restored ability to set labels for `selectInput`. (#741)
* Travis continuous integration now uses Travis's native R support.
* Fixed encoding issue when the server receives data from the client browser.
(#742)
* The `session` object now has class `ShinySession`, making it easier to test
whether an object is indeed a session object. (#720, #746)
* Fix JavaScript error when an output appears in nested uiOutputs. (Thanks,
Gregory Zhang. #749)
* Eliminate delay on receiving new value when `updateSliderInput(value=...)` is
called.
* Updated to DataTables (Javascript library) 1.10.5.
* Fixed downloading of files that have no filename extension. (#575, #753)
* Fixed bug where nested UI outputs broke outputs. (#749, #750)
* Removed unneeded HTML ID attributes for `checkboxGroupInputs` and
`radioButtons`. (#684)
* Fixed bug where checkboxes were still active even after `Shiny.unbindAll()`
was called. (#206)
* The server side selectize input will load the first 1000 options by default
before users start to type and search in the box. (#823)
* renderDataTable() and dataTableOutput() have been deprecated in shiny and will
be removed in future versions of shiny. Please use the DT package instead:
http://rstudio.github.io/DT/ (#807)
shiny 0.11.1
--------------------------------------------------------------------------------
* Major client-side performance improvements for pages that have many
conditionalPanels, tabPanels, and plotOutputs. (#693, #717, #723)
* `tabPanel`s now use the `title` for `value` by default. This fixes a bug
in which an icon in the title caused problems with a conditionalPanel's test
condition. (#725, #728)
* `selectInput` now has a `size` argument to control the height of the input
box. (#729)
* `navbarPage` no longer includes a first row of extra whitespace when
`header=NULL`. (#722)
* `selectInput`s now use Bootstrap styling when `selectize=FALSE`. (#724)
* Better vertical spacing of label for checkbox groups and radio buttons.
* `selectInput` correctly uses width for both selectize and non-selectize
inputs. (#702)
* The wrapper tag generated by `htmlOutput` and `uiOutput` can now be any type
of HTML tag, instead of just span and div. Also, custom classes are now
allowed on the tag. (#704)
* Slider problems in IE 11 and Chrome on touchscreen-equipped Windows computers
have been fixed. (#700)
* Sliders now work correctly with draggable panels. (#711)
* Fixed arguments in `fixedPanel`. (#709)
* downloadHandler content callback functions are now invoked with a temp file
name that has the same extension as the final filename that will be used by
the download. This is to deal with the fact that some file writing functions
in R will auto-append the extension for their file type (pdf, zip).
shiny 0.11
--------------------------------------------------------------------------------
* Changed sliders from jquery-slider to ion.rangeSlider. These sliders have
an improved appearance, support updating more properties from the server,
and can be controlled with keyboard input.
* Switched from Bootstrap 2 to Bootstrap 3. For most users, this will work
seamlessly, but some users may need to use the shinybootstrap2 package for
backward compatibility.
* The UI of a Shiny app can now have a body tag. This is useful for CSS
templates that use classes on the body tag.
* `actionButton` and `actionLink` now pass their `...` arguments to the
underlying tag function. (#607)
* Added `observeEvent` and `eventReactive` functions for clearer, more concise
handling of `actionButton`, plot clicks, and other naturally-imperative
inputs.
* Errors that happen in reactives no longer prevent any remaining pending
observers from executing. It is also now possible for users to control how
errors are handled, with the 'shiny.observer.error' global option. (#603,
#604)
* Added an `escape` argument to `renderDataTable()` to escape the HTML entities
in the data table for security reasons. This might break tables from previous
versions of shiny that use raw HTML in the table content, and the old behavior
can be brought back by `escape = FALSE` if you are aware of the security
implications. (#627)
* Changed the URI encoding/decoding functions internally to use `encodeURI()`,
`encodeURIComponent()`, and `decodeURIComponent()` from the httpuv package
instead of `utils::URLencode()` and `utils::URLdecode()`. (#630)
* Shiny's web assets are now minified.
* The default reactive domain is now available in event handler functions. (#669)
* Password input fields can now be used, with `passwordInput()`. (#672)
shiny 0.10.2.2
--------------------------------------------------------------------------------
* Remove use of `rstudio::viewer` in a code example, for R CMD check.
shiny 0.10.2.1
--------------------------------------------------------------------------------
* Changed some examples to use \donttest instead of \dontrun.
shiny 0.10.2
--------------------------------------------------------------------------------
* The minimal version of R required for the shiny package is 3.0.0 now.
* Shiny apps can now consist of a single file, app.R, instead of ui.R and
server.R.
* Upgraded DataTables from 1.9.4 to 1.10.2. This might be a breaking change if
you have customized the DataTables options in your apps. (More info:
https://github.com/rstudio/shiny/pull/558)
* File uploading via `fileInput()` works for Internet Explorer 8 and 9 now. Note
IE8/9 do not support multiple files from a single file input. If you need to
upload multiple files, you have to use one file input for each file.
* Switched away from reference classes to R6.
* Reactive log performance has been greatly improved.
* Added `Progress` and `withProgress`, to display the progress of computation
on the client browser.
* Fixed #557: updateSelectizeInput(choices, server = TRUE) did not work when
`choices` is a character vector.
* Searching in DataTables is case-insensitive and the search strings are not
treated as regular expressions by default now. If you want case-sensitive
searching or regular expressions, you can use the configuration options
`search$caseInsensitive` and `search$regex`, e.g. `renderDataTable(...,
options = list(search = list(caseInsensitve = FALSE, regex = TRUE)))`.
* Added support for `htmltools::htmlDependency`'s new `attachment` parameter to
`renderUI`/`uiOutput`.
* Exported `createWebDependency`. It takes an `htmltools::htmlDependency` object
and makes it available over Shiny's built-in web server.
* Custom output bindings can now render `htmltools::htmlDependency` objects at
runtime using `Shiny.renderDependencies()`.
* Fixes to rounding behavior of sliderInput. (#301, #502)
* Updated selectize.js to version 0.11.2. (#596)
* Added `position` parameter to `navbarPage`.
shiny 0.10.1
--------------------------------------------------------------------------------
* Added Unicode support for Windows. Shiny apps running on Windows must use the
UTF-8 encoding for ui.R and server.R (also the optional global.R) if they
contain non-ASCII characters. See this article for details and examples:
http://shiny.rstudio.com/gallery/unicode-characters.html (#516)
* `runGitHub()` also allows the 'username/repo' syntax now, which is equivalent
to `runGitHub('repo', 'username')`. (#427)
* `navbarPage()` now accepts a `windowTitle` parameter to set the web browser
page title to something other than the title displayed in the navbar.
* Added an `inline` argument to `textOutput()`, `imageOutput()`, `plotOutput()`,
and `htmlOutput()`. When `inline = TRUE`, these outputs will be put in
`span()` instead of the default `div()`. This occurs automatically when these
outputs are created via the inline expressions (e.g. `r renderText(expr)`) in
R Markdown documents. See an R Markdown example at
http://shiny.rstudio.com/gallery/inline-output.html (#512)
* Added support for option groups in the select/selectize inputs. When the
`choices` argument for `selectInput()`/`selectizeInput()` is a list of
sub-lists and any sub-list is of length greater than 1, the HTML tag
`<optgroup>` will be used. See an example at
http://shiny.rstudio.com/gallery/option-groups-for-selectize-input.html (#542)
shiny 0.10.0
--------------------------------------------------------------------------------
* BREAKING CHANGE: By default, observers now terminate themselves if they were
created during a session and that session ends. See ?domains for more details.
* Shiny can now be used in R Markdown v2 documents, to create "Shiny Docs":
reports and presentations that combine narrative, statically computed output,
and fully dynamic inputs and outputs. For more info, including examples, see
http://rmarkdown.rstudio.com/authoring_shiny.html.
* The `session` object that can be passed into a server function (e.g.
shinyServer(function(input, output, session) {...})) is now documented: see
`?session`.
* Most inputs can now accept `NULL` label values to omit the label altogether.
* New `actionLink` input control; like `actionButton`, but with the appearance
of a normal link.
* `renderPlot` now calls `print` on its result if it's visible (i.e. no more
explicit `print()` required for ggplot2).
* Introduced Shiny app objects (see `?shinyApp`). These essentially replace the
little-advertised ability for `runApp` to take a `list(ui=..., server=...)`
as the first argument instead of a directory (though that ability remains for
backward compatibility). Unlike those lists, Shiny app objects are tagged with
class `shiny.appobj` so they can be run simply by printing them.
* Added `maskReactiveContext` function. It blocks the current reactive context,
to evaluate expressions that shouldn't use reactive sources directly. (This
should not be commonly needed.)
* Added `flowLayout`, `splitLayout`, and `inputPanel` functions for putting UI
elements side by side. `flowLayout` lays out its children in a left-to-right,
top-to-bottom arrangement. `splitLayout` evenly divides its horizontal space
among its children (or unevenly divides if `cellWidths` argument is provided).
`inputPanel` is like `flowPanel`, but with a light grey background, and is
intended to be used to encapsulate small input controls wherever vertical
space is at a premium.
* Added `serverInfo` to obtain info about the Shiny Server if the app is served
through it.
* Added an `inline` argument (TRUE/FALSE) in `checkboxGroupInput()` and
`radioButtons()` to allow the horizontal layout (inline = TRUE) of checkboxes
or radio buttons. (Thanks, @saurfang, #481)
* `sliderInput` and `selectizeInput`/`selectInput` now use a standard horizontal
size instead of filling up all available horizontal space. Pass `width="100%"`
explicitly for the old behavior.
* Added the `updateSelectizeInput()` function to make it possible to process
searching on the server side (i.e. using R), which can be much faster than the
client side processing (i.e. using HTML and JavaScript). See the article at
http://shiny.rstudio.com/articles/selectize.html for a detailed introduction.
* Fixed a bug of renderDataTable() when the data object only has 1 row and 1
column. (Thanks, ZJ Dai, #429)
* `renderPrint` gained a new argument 'width' to control the width of the text
output, e.g. renderPrint({mtcars}, width = 40).
* Fixed #220: the zip file for a directory created by some programs may not have
the directory name as its first entry, in which case runUrl() can fail. (#220)
* `runGitHub()` can also take a value of the form "username/repo" in its first
argument, e.g. both runGitHub("shiny_example", "rstudio") and
runGitHub("rstudio/shiny_example") are valid ways to run the GitHub repo.
shiny 0.9.1
--------------------------------------------------------------------------------
* Fixed warning 'Error in Context$new : could not find function "loadMethod"'
that was happening to dependent packages on "R CMD check".
shiny 0.9.0
--------------------------------------------------------------------------------
* BREAKING CHANGE: Added a `host` parameter to runApp() and runExample(),
which defaults to the shiny.host option if it is non-NULL, or "127.0.0.1"
otherwise. This means that by default, Shiny applications can only be
accessed on the same machine from which they are served. To allow other
clients to connect, as in previous versions of Shiny, use "0.0.0.0"
(or the IP address of one of your network interfaces, if you care to be
explicit about it).
* Added a new function `selectizeInput()` to use the JavaScript library
selectize.js (https://github.com/brianreavis/selectize.js), which extends
the basic select input in many aspects.
* The `selectInput()` function also gained a new argument `selectize = TRUE`
to makes use of selectize.js by default. If you want to revert back to the
original select input, you have to call selectInput(..., selectize = FALSE).
* Added Showcase mode, which displays the R code for an app right in the app
itself. You can invoke Showcase mode by passing `display.mode="showcase"`
to the `runApp()` function. Or, if an app is designed to run in Showcase
mode by default, add a DESCRIPTION file in the app dir with Title, Author,
and License fields; with "Type: Shiny"; and with "DisplayMode: Showcase".
* Upgraded to Bootstrap 2.3.2 and jQuery 1.11.0.
* Make `tags$head()` and `singleton()` behave correctly when used with
`renderUI()` and `uiOutput()`. Previously, "hoisting content to the head"
and "only rendering items a single time" were features that worked only
when the page was initially loading, not in dynamic rendering.
* Files are now sourced with the `keep.source` option, to help with debugging
and profiling.
* Support user-defined input parsers for data coming in from JavaScript using
the parseShinyInput method.
* Fixed the bug #299: renderDataTable() can deal with 0-row data frames now.
(reported by Harlan Harris)
* Added `navbarPage()` and `navbarMenu()` functions to create applications
with multiple top level panels.
* Added `navlistPanel()` function to create layouts with a a bootstrap
navlist on the left and tabPanels on the right
* Added `type` parameter to `tabsetPanel()` to enable the use of pill
style tabs in addition to the standard ones.
* Added `position` paramter to `tabsetPanel()` to enable positioning of tabs
above, below, left, or right of tab content.
* Added `fluidPage()` and `fixedPage()` functions as well as related row and
column layout functions for creating arbitrary bootstrap grid layouts.
* Added `hr()` builder function for creating horizontal rules.
* Automatically concatenate duplicate attributes in tag definitions
* Added `responsive` parameter to page building functions for opting-out of
bootstrap responsive css.
* Added `theme` parameter to page building functions for specifying alternate
bootstrap css styles.
* Added `icon()` function for embedding icons from the
[font awesome](http://fontawesome.io/) icon library
* Added `makeReactiveBinding` function to turn a "regular" variable into a
reactive one (i.e. reading the variable makes the current reactive context
dependent on it, and setting the variable is a source of reactivity).
* Added a function `withMathJax()` to include the MathJax library in an app.
* The argument `selected` in checkboxGroupInput(), selectInput(), and
radioButtons() refers to the value(s) instead of the name(s) of the
argument `choices` now. For example, the value of the `selected` argument
in selectInput(..., choices = c('Label 1' = 'x1', 'Label 2' = 'x2'),
selected = 'Label 2') must be updated to 'x2', although names/labels will
be automatically converted to values internally for backward
compatibility. The same change applies to updateCheckboxGroupInput(),
updateSelectInput(), and updateRadioButtons() as well. (#340)
* Now it is possible to only update the value of a checkbox group, select input,
or radio buttons using the `selected` argument without providing the
`choices` argument in updateCheckboxGroupInput(), updateSelectInput(), and
updateRadioButtons(), respectively. (#340)
* Added `absolutePanel` and `fixedPanel` functions for creating absolute-
and fixed-position panels. They can be easily made user-draggable by
specifying `draggable = TRUE`.
* For the `options` argument of the function `renderDataTable()`, we can
pass literal JavaScript code to the DataTables library via `I()`. This
makes it possible to use any JavaScript object in the options, e.g. a
JavaScript function (which is not supported in JSON). See
`?renderDataTable` for details and examples.
* DataTables also works under IE8 now.
* Fixed a bug in DataTables pagination when searching is turned on, which
caused failures for matrices as well as empty rows when displaying data
frames using renderDataTable().
* The `options` argument in `renderDataTable()` can also take a function
that returns a list. This makes it possible to use reactive values in the
options. (#392)
* `renderDataTable()` respects more DataTables options now: (1) either
bPaginate = FALSE or iDisplayLength = -1 will disable pagination (i.e. all
rows are returned from the data); besides, this means we can also use -1
in the length menu, e.g. aLengthMenu = list(c(10, 30, -1), list(10, 30,
'All')); (2) we can disable searching for individual columns through the
bSearchable option, e.g. aoColumns = list(list(bSearchable = FALSE),
list(bSearchable = TRUE),...) (the search box for the first column is
hidden); (3) we can turn off searching entirely (for both global searching
and individual columns) using the option bFilter = FALSE.
* Added an argument `callback` in `renderDataTable()` so that a custom
JavaScript function can be applied to the DataTable object. This makes it
much easier to use DataTables plug-ins.
* For numeric columns in a DataTable, the search boxes support lower and
upper bounds now: a search query of the form "lower,upper" (without
quotes) indicates the limits [lower, upper]. For a column X, this means
the rows corresponding to X >= lower & X <= upper are returned. If we omit
either the lower limit or the upper limit, only the other limit will be
used, e.g. ",upper" means X <= upper.
* `updateNumericInput(value)` tries to preserve numeric precision by avoiding
scientific notation when possible, e.g. 102145 is no longer rounded to
1.0214e+05 = 102140. (Thanks, Martin Loos. #401)
* `sliderInput()` no longer treats a label wrapped in HTML() as plain text,
e.g. the label in sliderInput(..., label = HTML('<em>A Label</em>')) will
not be escaped any more. (#119)
* Fixed #306: the trailing slash in a path could fail `addResourcePath()`
under Windows. (Thanks, ZJ Dai)
* Dots are now legal characters for inputId/outputId. (Thanks, Kevin
Lindquist. #358)
shiny 0.8.0
--------------------------------------------------------------------------------
* Debug hooks are registered on all user-provided functions and (reactive)
expressions (e.g., in renderPlot()), which makes it possible to set
breakpoints in these functions using the latest version of the RStudio
IDE, and the RStudio visual debugging tools can be used to debug Shiny
apps. Internally, the registration is done via installExprFunction(),
which is a new function introduced in this version to replace
exprToFunction() so that the registration can be automatically done.
* Added a new function renderDataTable() to display tables using the
JavaScript library DataTables. It includes basic features like pagination,
searching (global search or search by individual columns), sorting (by
single or multiple columns). All these features are implemented on the R
side; for example, we can use R regular expressions for searching.
Besides, it also uses the Bootstrap CSS style. See the full
documentation and examples in the tutorial:
http://rstudio.github.io/shiny/tutorial/#datatables
* Added a new option `shiny.error` which can take a function as an error
handler. It is called when an error occurs in an app (in user-provided
code), e.g., after we set options(shiny.error = recover), we can enter a
specified environment in the call stack to debug our code after an error
occurs.
* The argument `launch.browser` in runApp() can also be a function,
which takes the URL of the shiny app as its input value.
* runApp() uses a random port between 3000 and 8000 instead of 8100 now. It
will try up to 20 ports in case certain ports are not available.
* Fixed a bug for conditional panels: the value `input.id` in the condition
was not correctly retrieved when the input widget had a type, such as
numericInput(). (reported by Jason Bryer)
* Fixed two bugs in plotOutput(); clickId and hoverId did not give correct
coordinates in Firefox, or when the axis limits of the plot were changed.
(reported by Chris Warth and Greg D)
* The minimal required version for the httpuv package was increased to 1.2
(on CRAN now).
shiny 0.7.0
--------------------------------------------------------------------------------
* Stopped sending websocket subprotocol. This fixes a compatibility issue with
Google Chrome 30.
* The `input` and `output` objects are now also accessible via `session$input`
and `session$output`.
* Added click and hover events for static plots; see `?plotOutput` for details.
* Added optional logging of the execution states of a reactive program, and
tools for visualizing the log data. To use, start a new R session and call
`options(shiny.reactlog=TRUE)`. Then launch a Shiny app and interact with it.
Press Ctrl+F3 (or for Mac, Cmd+F3) in the browser to launch an interactive
visualization of the reactivity that has occurred. See `?showReactLog` for
more information.
* Added `includeScript()` and `includeCSS()` functions.
* Reactive expressions now have class="reactive" attribute. Also added
`is.reactive()` and `is.reactivevalues()` functions.
* New `stopApp()` function, which stops an app and returns a value to the caller
of `runApp()`.
* Added the `shiny.usecairo` option, which can be used to tell Shiny not to use
Cairo for PNG output even when it is installed. (Defaults to `TRUE`.)
* Speed increases for `selectInput()` and `radioButtons()`, and their
corresponding updater functions, for when they have many options.
* Added `tagSetChildren()` and `tagAppendChildren()` functions.
* The HTTP request object that created the websocket is now accessible from the
`session` object, as `session$request`. This is a Rook-like request
environment that can be used to access HTTP headers, among other things.
(Note: When running in a Shiny Server environment, the request will reflect
the proxy HTTP request that was made from the Shiny Server process to the R
process, not the request that was made from the web browser to Shiny Server.)
* Fix `getComputedStyle` issue, for IE8 browser compatibility (#196). Note:
Shiny Server is still required for IE8/9 compatibility.
* Add shiny.sharedSecret option, to require the HTTP header Shiny-Shared-Secret
to be set to the given value.
shiny 0.6.0
--------------------------------------------------------------------------------
* `tabsetPanel()` can be directed to start with a specific tab selected.
* Fix bug where multiple file uploads with 3 or more files result in incorrect
data.
* Add `withTags()` function.
* Add dateInput and dateRangeInput.
* `shinyServer()` now takes an optional `session` argument, which is used for
communication with the session object.
* Add functions to update values of existing inputs on a page, instead of
replacing them entirely.
* Allow listening on domain sockets.
* Added `actionButton()` to Shiny.
* The server can now send custom JSON messages to the client. On the client
side, functions can be registered to handle these messages.
* Callbacks can be registered to be called at the end of a client session.
* Add ability to set priority of observers and outputs. Each priority level
gets its own queue.
* Fix bug where the presence of a submit button would prevent sending of
metadata until the button was clicked.
* `reactiveTimer()` and `invalidateLater()` by default no longer invalidate
reactive objects after the client session has closed.
* Shiny apps can be run without a server.r and ui.r file.
shiny 0.5.0
--------------------------------------------------------------------------------
* Switch from websockets package for handling websocket connections to httpuv.
* New method for detecting hidden output objects. Instead of checking that
height and width are 0, it checks that the object or any ancestor in the DOM
has style display:none.
* Add `clientData` reactive values object, which carries information about the
client. This includes the hidden status of output objects, height/width plot
output objects, and the URL of the browser.
* Add `parseQueryString()` function.
* Add `renderImage()` function for sending arbitrary image files to the client,
and its counterpart, `imageOutput()`.
* Add support for high-resolution (Retina) displays.
* Fix bug #55, where `renderTable()` would throw error with an empty data frame.
shiny 0.4.1
--------------------------------------------------------------------------------
* Fix bug where width and height weren't passed along properly from
`reactivePlot` to `renderPlot`.
* Fix bug where infinite recursion would happen when `reactivePlot` was passed
a function for width or height.
shiny 0.4.0
--------------------------------------------------------------------------------
* Added suspend/resume capability to observers.
* Output objects are automatically suspended when they are hidden on the user's
web browser.
* `runGist()` accepts GitHub's new URL format, which includes the username.
* `reactive()` and `observe()` now take expressions instead of functions.
* `reactiveText()`, `reactivePlot()`, and so on, have been renamed to
`renderText()`, `renderPlot()`, etc. They also now take expressions instead
of functions.
* Fixed a bug where empty values in a numericInput were sent to the R process
as 0. They are now sent as NA.
shiny 0.3.1
--------------------------------------------------------------------------------
* Fix issue #91: bug where downloading files did not work.
* Add [[<- operator for shinyoutput object, making it possible to assign values
with `output[['plot1']] <- ...`.
* Reactive functions now preserve the visible/invisible state of their returned
values.
shiny 0.3.0
--------------------------------------------------------------------------------
* Reactive functions are now evaluated lazily.
* Add `reactiveValues()`.
* Using `as.list()` to convert a reactivevalues object (like `input`) to a list
is deprecated. The new function `reactiveValuesToList()` should be used
instead.
* Add `isolate()`. This function is used for accessing reactive functions,
without them invalidating their parent contexts.
* Fix issue #58: bug where reactive functions are not re-run when all items in
a checkboxGroup are unchecked.
* Fix issue #71, where `reactiveTable()` would return blank if the first
element of a data frame was NA.
* In `plotOutput`, better validation for CSS units when specifying width and
height.
* `reactivePrint()` no longer displays invisible output.
* `reactiveText()` no longer displays printed output, only the return value
from a function.
* The `runGitHub()` and `runUrl()` functions have been added, for running
Shiny apps from GitHub repositories and zip/tar files at remote URLs.
* Fix issue #64, where pressing Enter in a textbox would cause a form to
submit.
shiny 0.2.4
--------------------------------------------------------------------------------
* `runGist` has been updated to use the new download URLs from
https://gist.github.com.
* Shiny now uses `CairoPNG()` for output, when the Cairo package is available.
This provides better-looking output on Linux and Windows.
shiny 0.2.3
--------------------------------------------------------------------------------
* Ignore request variables for routing purposes
shiny 0.2.2
--------------------------------------------------------------------------------
* Fix CRAN warning (assigning to global environment)
shiny 0.2.1
--------------------------------------------------------------------------------
* [BREAKING] Modify API of `downloadHandler`: The `content` function now takes
a file path, not writable connection, as an argument. This makes it much
easier to work with APIs that only write to file paths, not connections.
shiny 0.2.0
--------------------------------------------------------------------------------
* Fix subtle name resolution bug--the usual symptom being S4 methods not being
invoked correctly when called from inside of ui.R or server.R
shiny 0.1.14
--------------------------------------------------------------------------------
* Fix slider animator, which broke in 0.1.10
shiny 0.1.13
--------------------------------------------------------------------------------
* Fix temp file leak in reactivePlot
shiny 0.1.12
--------------------------------------------------------------------------------
* Fix problems with runGist on Windows
* Add feature for on-the-fly file downloads (e.g. CSV data, PDFs)
* Add CSS hooks for app-wide busy indicators
shiny 0.1.11
--------------------------------------------------------------------------------
* Fix input binding with IE8 on Shiny Server
* Fix issue #41: reactiveTable should allow print options too
* Allow dynamic sizing of reactivePlot (i.e. using a function instead of a fixed
value)
shiny 0.1.10
--------------------------------------------------------------------------------
* Support more MIME types when serving out of www
* Fix issue #35: Allow modification of untar args
* headerPanel can take an explicit window title parameter
* checkboxInput uses correct attribute `checked` instead of `selected`
* Fix plot rendering with IE8 on Shiny Server
shiny 0.1.9
--------------------------------------------------------------------------------
* Much less flicker when updating plots
* More customizable error display
* Add `includeText`, `includeHTML`, and `includeMarkdown` functions for putting
text, HTML, and Markdown content from external files in the application's UI.
shiny 0.1.8
--------------------------------------------------------------------------------
* Add `runGist` function for conveniently running a Shiny app that is published
on gist.github.com.
* Fix issue #27: Warnings cause reactive functions to stop executing.
* The server.R and ui.R filenames are now case insensitive.
* Add `wellPanel` function for creating inset areas on the page.
* Add `bootstrapPage` function for creating new Bootstrap based
layouts from scratch.
shiny 0.1.7
--------------------------------------------------------------------------------
* Fix issue #26: Shiny.OutputBindings not correctly exported.
* Add `repeatable` function for making easily repeatable versions of random
number generating functions.
* Transcode JSON into UTF-8 (prevents non-ASCII reactivePrint values from
causing errors on Windows).
shiny 0.1.6
--------------------------------------------------------------------------------
* Import package dependencies, instead of attaching them (with the exception of
websockets, which doesn't currently work unless attached).
* conditionalPanel was animated, now it is not.
* bindAll was not correctly sending initial values to the server; fixed.
shiny 0.1.5
--------------------------------------------------------------------------------
* BREAKING CHANGE: JS APIs Shiny.bindInput and Shiny.bindOutput removed and
replaced with Shiny.bindAll; Shiny.unbindInput and Shiny.unbindOutput removed
and replaced with Shiny.unbindAll.
* Add file upload support (currently only works with Chrome and Firefox). Use
a normal HTML file input, or call the `fileInput` UI function.
* Shiny.unbindOutputs did not work, now it does.
* Generally improved robustness of dynamic input/output bindings.
* Add conditionalPanel UI function to allow showing/hiding UI based on a JS
expression; for example, whether an input is a particular value. Also works in
raw HTML (add the `data-display-if` attribute to the element that should be
shown/hidden).
* htmlOutput (CSS class `shiny-html-output`) can contain inputs and outputs.
shiny 0.1.4
--------------------------------------------------------------------------------
* Allow Bootstrap tabsets to act as reactive inputs; their value indicates which
tab is active
* Upgrade to Bootstrap 2.1
* Add `checkboxGroupInput` control, which presents a list of checkboxes and
returns a vector of the selected values
* Add `addResourcePath`, intended for reusable component authors to access CSS,
JavaScript, image files, etc. from their package directories
* Add Shiny.bindInputs(scope), .unbindInputs(scope), .bindOutputs(scope), and
.unbindOutputs(scope) JS API calls to allow dynamic binding/unbinding of HTML
elements
shiny 0.1.3
--------------------------------------------------------------------------------
* Introduce Shiny.inputBindings.register JS API and InputBinding class, for
creating custom input controls
* Add `step` parameter to numericInput
* Read names of input using `names(input)`
* Access snapshot of input as a list using `as.list(input)`
* Fix issue #10: Plots in tabsets not rendered
shiny 0.1.2
--------------------------------------------------------------------------------
Initial private beta release!

1390
NEWS.md Normal file

File diff suppressed because it is too large Load Diff

174
R/app.R
View File

@@ -20,19 +20,29 @@
#' @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
#' expression in order for the match to be considered successful.
#' @param enableBookmarking Can be one of \code{"url"}, \code{"server"}, or
#' \code{"disable"}. This is equivalent to calling the
#' \code{\link{enableBookmarking}()} function just before calling
#' \code{shinyApp()}. With the default value (\code{NULL}), the app will
#' respect the setting from any previous calls to \code{enableBookmarking()}.
#' See \code{\link{enableBookmarking}} for more information.
#' @return An object that represents the app. Printing the object or passing it
#' to \code{\link{runApp}} will run the app.
#'
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' shinyApp(
#' ui = fluidPage(
#' numericInput("n", "n", 1),
@@ -59,10 +69,9 @@
#'
#' runApp(app)
#' }
#'
#' @export
shinyApp <- function(ui=NULL, server=NULL, onStart=NULL, options=list(),
uiPattern="/") {
uiPattern="/", enableBookmarking=NULL) {
if (is.null(server)) {
stop("`server` missing from shinyApp")
}
@@ -76,12 +85,24 @@ shinyApp <- function(ui=NULL, server=NULL, onStart=NULL, options=list(),
server
}
if (!is.null(enableBookmarking)) {
bookmarkStore <- match.arg(enableBookmarking, c("url", "server", "disable"))
enableBookmarking(bookmarkStore)
}
# Store the appDir and bookmarking-related options, so that we can read them
# from within the app.
shinyOptions(appDir = getwd())
appOptions <- consumeAppOptions()
structure(
list(
httpHandler = httpHandler,
serverFuncSource = serverFuncSource,
onStart = onStart,
options = options),
options = options,
appOptions = appOptions
),
class = "shiny.appobj"
)
}
@@ -102,12 +123,22 @@ shinyAppDir <- function(appDir, options=list()) {
if (file.exists.ci(appDir, "server.R")) {
shinyAppDir_serverR(appDir, options = options)
} else if (file.exists.ci(appDir, "app.R")) {
shinyAppDir_appR(appDir, options = options)
shinyAppDir_appR("app.R", appDir, options = options)
} else {
stop("App dir must contain either app.R or server.R.")
}
}
#' @rdname shinyApp
#' @param appFile Path to a .R file containing a Shiny application
#' @export
shinyAppFile <- function(appFile, options=list()) {
appFile <- normalizePath(appFile, mustWork = TRUE)
appDir <- dirname(appFile)
shinyAppDir_appR(basename(appFile), appDir, options = options)
}
# This reads in an app dir in the case that there's a server.R (and ui.R/www)
# present, and returns a shiny.appobj.
shinyAppDir_serverR <- function(appDir, options=list()) {
@@ -124,7 +155,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
# If not, then take the last expression that's returned from ui.R.
.globals$ui <- NULL
on.exit(.globals$ui <- NULL, add = FALSE)
ui <- sourceUTF8(uiR, local = new.env(parent = globalenv()))$value
ui <- sourceUTF8(uiR, envir = new.env(parent = globalenv()))
if (!is.null(.globals$ui)) {
ui <- .globals$ui[[1]]
}
@@ -147,7 +178,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
# server.R.
.globals$server <- NULL
on.exit(.globals$server <- NULL, add = TRUE)
result <- sourceUTF8(serverR, local = new.env(parent = globalenv()))$value
result <- sourceUTF8(serverR, envir = new.env(parent = globalenv()))
if (!is.null(.globals$server)) {
result <- .globals$server[[1]]
}
@@ -170,15 +201,21 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
}
}
shinyOptions(appDir = appDir)
oldwd <- NULL
monitorHandle <- NULL
onStart <- function() {
oldwd <<- getwd()
setwd(appDir)
monitorHandle <<- initAutoReloadMonitor(appDir)
if (file.exists(file.path.ci(appDir, "global.R")))
sourceUTF8(file.path.ci(appDir, "global.R"))
}
onEnd <- function() {
onStop <- function() {
setwd(oldwd)
monitorHandle()
monitorHandle <<- NULL
}
structure(
@@ -186,27 +223,77 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
httpHandler = joinHandlers(c(uiHandler, wwwDir, fallbackWWWDir)),
serverFuncSource = serverFuncSource,
onStart = onStart,
onEnd = onEnd,
options = options),
onStop = onStop,
options = options
),
class = "shiny.appobj"
)
}
# This reads in an app dir in the case that there's a app.R present, and returns
# a shiny.appobj.
shinyAppDir_appR <- function(appDir, options=list()) {
fullpath <- file.path.ci(appDir, "app.R")
# Start a reactive observer that continually monitors dir for changes to files
# that have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif. Case is
# ignored when checking extensions. If any changes are detected, all connected
# Shiny sessions are reloaded.
#
# Use options(shiny.autoreload = TRUE) to enable this behavior. Since monitoring
# for changes is expensive (we are polling for mtimes here, nothing fancy) this
# feature is intended only for development.
#
# You can customize the file patterns Shiny will monitor by setting the
# shiny.autoreload.pattern option. For example, to monitor only ui.R:
# options(shiny.autoreload.pattern = glob2rx("ui.R"))
#
# The return value is a function that halts monitoring when called.
initAutoReloadMonitor <- function(dir) {
if (!getOption("shiny.autoreload", FALSE)) {
return(function(){})
}
filePattern <- getOption("shiny.autoreload.pattern",
".*\\.(r|html?|js|css|png|jpe?g|gif)$")
lastValue <- NULL
obs <- observe({
files <- sort(list.files(dir, pattern = filePattern, recursive = TRUE,
ignore.case = TRUE))
times <- file.info(files)$mtime
names(times) <- files
if (is.null(lastValue)) {
# First run
lastValue <<- times
} else if (!identical(lastValue, times)) {
# We've changed!
lastValue <<- times
for (session in appsByToken$values()) {
session$reload()
}
}
invalidateLater(getOption("shiny.autoreload.interval", 500))
})
obs$destroy
}
# This reads in an app dir for a single-file application (e.g. app.R), and
# returns a shiny.appobj.
shinyAppDir_appR <- function(fileName, appDir, options=list())
{
fullpath <- file.path.ci(appDir, fileName)
# This sources app.R and caches the content. When appObj() is called but
# app.R hasn't changed, it won't re-source the file. But if called and
# app.R has changed, it'll re-source the file and return the result.
appObj <- cachedFuncWithFile(appDir, "app.R", case.sensitive = FALSE,
appObj <- cachedFuncWithFile(appDir, fileName, case.sensitive = FALSE,
function(appR) {
result <- sourceUTF8(fullpath, local = new.env(parent = globalenv()))$value
result <- sourceUTF8(fullpath, envir = new.env(parent = globalenv()))
if (!is.shiny.appobj(result))
stop("app.R did not return a shiny.appobj object.")
unconsumeAppOptions(result$appOptions)
return(result)
}
)
@@ -225,12 +312,17 @@ shinyAppDir_appR <- function(appDir, options=list()) {
fallbackWWWDir <- system.file("www-dir", package = "shiny")
oldwd <- NULL
monitorHandle <- NULL
onStart <- function() {
oldwd <<- getwd()
setwd(appDir)
monitorHandle <<- initAutoReloadMonitor(appDir)
if (!is.null(appObj()$onStart)) appObj()$onStart()
}
onEnd <- function() {
onStop <- function() {
setwd(oldwd)
monitorHandle()
monitorHandle <<- NULL
}
structure(
@@ -238,7 +330,7 @@ shinyAppDir_appR <- function(appDir, options=list()) {
httpHandler = joinHandlers(c(dynHttpHandler, wwwDir, fallbackWWWDir)),
serverFuncSource = dynServerFuncSource,
onStart = onStart,
onEnd = onEnd,
onStop = onStop,
options = options
),
class = "shiny.appobj"
@@ -268,7 +360,10 @@ as.shiny.appobj.list <- function(x) {
#' @rdname shinyApp
#' @export
as.shiny.appobj.character <- function(x) {
shinyAppDir(x)
if (identical(tolower(tools::file_ext(x)), "r"))
shinyAppFile(x)
else
shinyAppDir(x)
}
#' @rdname shinyApp
@@ -283,11 +378,13 @@ 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)
# Quote x and put runApp in quotes so that there's a nicer stack trace (#1851)
args <- c(list(quote(x)), opts)
do.call(runApp, args)
do.call("runApp", args)
}
#' @rdname shinyApp
@@ -302,7 +399,20 @@ as.tags.shiny.appobj <- function(x, ...) {
height <- if (is.null(opts$height)) "400" else opts$height
path <- addSubApp(x)
tags$iframe(src=path, width=width, height=height, class="shiny-frame")
deferredIFrame(path, width, height)
}
# Generate subapp iframes in such a way that they will not actually load right
# away. Loading subapps immediately upon app load can result in a storm of
# connections, all of which are contending for the few concurrent connections
# that a browser will make to a specific origin. Instead, we load dummy iframes
# and let the client load them when convenient. (See the initIframes function in
# init_shiny.js.)
deferredIFrame <- function(path, width, height) {
tags$iframe("data-deferred-src" = path,
width = width, height = height,
class = "shiny-frame shiny-frame-deferred"
)
}
#' Knitr S3 methods
@@ -353,8 +463,7 @@ knit_print.shiny.appobj <- function(x, ...) {
}
else {
path <- addSubApp(x)
output <- tags$iframe(src=path, width=width, height=height,
class="shiny-frame")
output <- deferredIFrame(path, width, height)
}
# If embedded Shiny apps ever have JS/CSS dependencies (like pym.js) we'll
@@ -378,3 +487,14 @@ knit_print.shiny.render.function <- function(x, ..., inline = FALSE) {
shiny_rmd_warning())
output
}
# Lets us drop reactive expressions directly into a knitr chunk and have the
# value printed out! Nice for teaching if nothing else.
#' @rdname knitr_methods
#' @export
knit_print.reactive <- function(x, ..., inline = FALSE) {
renderFunc <- if (inline) renderText else renderPrint
knitr::knit_print(renderFunc({
x()
}), inline = inline)
}

28
R/bookmark-state-local.R Normal file
View File

@@ -0,0 +1,28 @@
# Function wrappers for saving and restoring state to/from disk when running
# Shiny locally.
#
# These functions provide a directory to the callback function.
#
# @param id A session ID to save.
# @param callback A callback function that saves state to or restores state from
# a directory. It must take one argument, \code{stateDir}, which is a
# directory to which it writes/reads.
saveInterfaceLocal <- function(id, callback) {
# Try to save in app directory
appDir <- getShinyOption("appDir", default = getwd())
stateDir <- file.path(appDir, "shiny_bookmarks", id)
if (!dirExists(stateDir))
dir.create(stateDir, recursive = TRUE)
callback(stateDir)
}
loadInterfaceLocal <- function(id, callback) {
# Try to load from app directory
appDir <- getShinyOption("appDir", default = getwd())
stateDir <- file.path(appDir, "shiny_bookmarks", id)
callback(stateDir)
}

1207
R/bookmark-state.R Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,11 @@
#' @seealso \code{\link{column}}, \code{\link{sidebarLayout}}
#'
#' @examples
#' shinyUI(fluidPage(
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' # Example of UI with fluidPage
#' ui <- fluidPage(
#'
#' # Application title
#' titlePanel("Hello Shiny!"),
@@ -52,9 +56,21 @@
#' plotOutput("distPlot")
#' )
#' )
#' ))
#' )
#'
#' shinyUI(fluidPage(
#' # Server logic
#' server <- function(input, output) {
#' output$distPlot <- renderPlot({
#' hist(rnorm(input$obs))
#' })
#' }
#'
#' # Complete app with UI and server components
#' shinyApp(ui, server)
#'
#'
#' # UI demonstrating column layouts
#' ui <- fluidPage(
#' title = "Hello Shiny!",
#' fluidRow(
#' column(width = 4,
@@ -64,8 +80,10 @@
#' "3 offset 2"
#' )
#' )
#' ))
#' )
#'
#' shinyApp(ui, server = function(input, output) { })
#' }
#' @rdname fluidPage
#' @export
fluidPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
@@ -115,7 +133,10 @@ fluidRow <- function(...) {
#' @seealso \code{\link{column}}
#'
#' @examples
#' shinyUI(fixedPage(
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fixedPage(
#' title = "Hello, Shiny!",
#' fixedRow(
#' column(width = 4,
@@ -125,7 +146,10 @@ fluidRow <- function(...) {
#' "3 offset 2"
#' )
#' )
#' ))
#' )
#'
#' shinyApp(ui, server = function(input, output) { })
#' }
#'
#' @rdname fixedPage
#' @export
@@ -160,24 +184,43 @@ fixedRow <- function(...) {
#' @seealso \code{\link{fluidRow}}, \code{\link{fixedRow}}.
#'
#' @examples
#' fluidRow(
#' column(4,
#' sliderInput("obs", "Number of observations:",
#' min = 1, max = 1000, value = 500)
#' ),
#' column(8,
#' plotOutput("distPlot")
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' fluidRow(
#' column(4,
#' sliderInput("obs", "Number of observations:",
#' min = 1, max = 1000, value = 500)
#' ),
#' column(8,
#' plotOutput("distPlot")
#' )
#' )
#' )
#'
#' fluidRow(
#' column(width = 4,
#' "4"
#' ),
#' column(width = 3, offset = 2,
#' "3 offset 2"
#' server <- function(input, output) {
#' output$distPlot <- renderPlot({
#' hist(rnorm(input$obs))
#' })
#' }
#'
#' shinyApp(ui, server)
#'
#'
#'
#' ui <- fluidPage(
#' fluidRow(
#' column(width = 4,
#' "4"
#' ),
#' column(width = 3, offset = 2,
#' "3 offset 2"
#' )
#' )
#' )
#' shinyApp(ui, server = function(input, output) { })
#' }
#' @export
column <- function(width, ..., offset = 0) {
@@ -202,8 +245,14 @@ column <- function(width, ..., offset = 0) {
#'
#'
#' @examples
#' titlePanel("Hello Shiny!")
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' titlePanel("Hello Shiny!")
#' )
#' shinyApp(ui, server = function(input, output) { })
#' }
#' @export
titlePanel <- function(title, windowTitle=title) {
tagList(
@@ -226,8 +275,12 @@ titlePanel <- function(title, windowTitle=title) {
#' layout.
#'
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' # Define UI
#' shinyUI(fluidPage(
#' ui <- fluidPage(
#'
#' # Application title
#' titlePanel("Hello Shiny!"),
@@ -248,8 +301,18 @@ titlePanel <- function(title, windowTitle=title) {
#' plotOutput("distPlot")
#' )
#' )
#' ))
#' )
#'
#' # Server logic
#' server <- function(input, output) {
#' output$distPlot <- renderPlot({
#' hist(rnorm(input$obs))
#' })
#' }
#'
#' # Complete app with UI and server components
#' shinyApp(ui, server)
#' }
#' @export
sidebarLayout <- function(sidebarPanel,
mainPanel,
@@ -286,13 +349,18 @@ sidebarLayout <- function(sidebarPanel,
#' @seealso \code{\link{fluidPage}}, \code{\link{flowLayout}}
#'
#' @examples
#' shinyUI(fluidPage(
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' verticalLayout(
#' a(href="http://example.com/link1", "Link One"),
#' a(href="http://example.com/link2", "Link Two"),
#' a(href="http://example.com/link3", "Link Three")
#' )
#' ))
#' )
#' shinyApp(ui, server = function(input, output) { })
#' }
#' @export
verticalLayout <- function(..., fluid = TRUE) {
lapply(list(...), function(row) {
@@ -319,11 +387,16 @@ verticalLayout <- function(..., fluid = TRUE) {
#' @seealso \code{\link{verticalLayout}}
#'
#' @examples
#' flowLayout(
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- flowLayout(
#' numericInput("rows", "How many rows?", 5),
#' selectInput("letter", "Which letter?", LETTERS),
#' sliderInput("value", "What value?", 0, 100, 50)
#' )
#' shinyApp(ui, server = function(input, output) { })
#' }
#' @export
flowLayout <- function(..., cellArgs = list()) {
@@ -346,7 +419,6 @@ flowLayout <- function(..., cellArgs = list()) {
#' suitable for wrapping inputs.
#'
#' @param ... Input controls or other HTML elements.
#'
#' @export
inputPanel <- function(...) {
div(class = "shiny-input-panel",
@@ -369,21 +441,34 @@ inputPanel <- function(...) {
#' of the layout.
#'
#' @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) {
#' output$plot1 <- renderPlot(plot(cars))
#' output$plot2 <- renderPlot(plot(pressure))
#' output$plot3 <- renderPlot(plot(AirPassengers))
#' }
#'
#' # Equal sizing
#' splitLayout(
#' ui <- splitLayout(
#' plotOutput("plot1"),
#' plotOutput("plot2")
#' )
#' shinyApp(ui, server)
#'
#' # Custom widths
#' splitLayout(cellWidths = c("25%", "75%"),
#' ui <- splitLayout(cellWidths = c("25%", "75%"),
#' plotOutput("plot1"),
#' plotOutput("plot2")
#' )
#' shinyApp(ui, server)
#'
#' # All cells at 300 pixels wide, with cell padding
#' # and a border around everything
#' splitLayout(
#' ui <- splitLayout(
#' style = "border: 1px solid silver;",
#' cellWidths = 300,
#' cellArgs = list(style = "padding: 6px"),
@@ -391,6 +476,8 @@ inputPanel <- function(...) {
#' plotOutput("plot2"),
#' plotOutput("plot3")
#' )
#' shinyApp(ui, server)
#' }
#' @export
splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) {
@@ -417,3 +504,193 @@ splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) {
}, SIMPLIFY = FALSE)
))
}
#' Flex Box-based row/column layouts
#'
#' Creates row and column layouts with proportionally-sized cells, using the
#' Flex Box layout model of CSS3. These can be nested to create arbitrary
#' proportional-grid layouts. \strong{Warning:} Flex Box is not well supported
#' by Internet Explorer, so these functions should only be used where modern
#' browsers can be assumed.
#'
#' @details If you try to use \code{fillRow} and \code{fillCol} inside of other
#' Shiny containers, such as \code{\link{sidebarLayout}},
#' \code{\link{navbarPage}}, or even \code{tags$div}, you will probably find
#' that they will not appear. This is due to \code{fillRow} and \code{fillCol}
#' defaulting to \code{height="100\%"}, which will only work inside of
#' containers that have determined their own size (rather than shrinking to
#' the size of their contents, as is usually the case in HTML).
#'
#' To avoid this problem, you have two options:
#' \itemize{
#' \item only use \code{fillRow}/\code{fillCol} inside of \code{fillPage},
#' \code{fillRow}, or \code{fillCol}
#' \item provide an explicit \code{height} argument to
#' \code{fillRow}/\code{fillCol}
#' }
#'
#' @param ... UI objects to put in each row/column cell; each argument will
#' occupy a single cell. (To put multiple items in a single cell, you can use
#' \code{\link{tagList}} or \code{\link{div}} to combine them.) Named
#' arguments will be used as attributes on the \code{div} element that
#' encapsulates the row/column.
#' @param flex Determines how space should be distributed to the cells. Can be a
#' single value like \code{1} or \code{2} to evenly distribute the available
#' space; or use a vector of numbers to specify the proportions. For example,
#' \code{flex = c(2, 3)} would cause the space to be split 40\%/60\% between
#' two cells. NA values will cause the corresponding cell to be sized
#' according to its contents (without growing or shrinking).
#' @param width,height The total amount of width and height to use for the
#' entire row/column. For the default height of \code{"100\%"} to be
#' effective, the parent must be \code{fillPage}, another
#' \code{fillRow}/\code{fillCol}, or some other HTML element whose height is
#' not determined by the height of its contents.
#'
#' @examples
#' # Only run this example in interactive R sessions.
#' if (interactive()) {
#'
#' ui <- fillPage(fillRow(
#' plotOutput("plotLeft", height = "100%"),
#' fillCol(
#' plotOutput("plotTopRight", height = "100%"),
#' plotOutput("plotBottomRight", height = "100%")
#' )
#' ))
#'
#' server <- function(input, output, session) {
#' output$plotLeft <- renderPlot(plot(cars))
#' output$plotTopRight <- renderPlot(plot(pressure))
#' output$plotBottomRight <- renderPlot(plot(AirPassengers))
#' }
#'
#' shinyApp(ui, server)
#'
#' }
#' @export
fillRow <- function(..., flex = 1, width = "100%", height = "100%") {
flexfill(..., direction = "row", flex = flex, width = width, height = height)
}
#' @rdname fillRow
#' @export
fillCol <- function(..., flex = 1, width = "100%", height = "100%") {
flexfill(..., direction = "column", flex = flex, width = width, height = height)
}
flexfill <- function(..., direction, flex, width = width, height = height) {
children <- list(...)
attrs <- list()
if (!is.null(names(children))) {
attrs <- children[names(children) != ""]
children <- children[names(children) == ""]
}
if (length(flex) > length(children)) {
flex <- flex[seq_along(children)]
}
# The dimension along the main axis
main <- switch(direction,
row = "width",
"row-reverse" = "width",
column = "height",
"column-reverse" = "height",
stop("Unexpected direction")
)
# The dimension along the cross axis
cross <- if (main == "width") "height" else "width"
divArgs <- list(
class = sprintf("flexfill-container flexfill-container-%s", direction),
style = css(
display = "-webkit-flex",
display = "-ms-flexbox",
display = "flex",
.webkit.flex.direction = direction,
.ms.flex.direction = direction,
flex.direction = direction,
width = validateCssUnit(width),
height = validateCssUnit(height)
),
mapply(children, flex, FUN = function(el, flexValue) {
if (is.na(flexValue)) {
# If the flex value is NA, then put the element in a simple flex item
# that sizes itself (along the main axis) to its contents
tags$div(
class = "flexfill-item",
style = css(
position = "relative",
"-webkit-flex" = "none",
"-ms-flex" = "none",
flex = "none"
),
style = paste0(main, ":auto;", cross, ":100%;"),
el
)
} else if (is.numeric(flexValue)) {
# If the flex value is numeric, we need *two* wrapper divs. The outer is
# the flex item, and the inner is an absolute-fill div that is needed to
# make percentage-based sizing for el work correctly. I don't understand
# why this is needed but the truth is probably in this SO page:
# http://stackoverflow.com/questions/15381172/css-flexbox-child-height-100
tags$div(
class = "flexfill-item",
style = css(
position = "relative",
"-webkit-flex" = flexValue,
"-ms-flex" = flexValue,
flex = flexValue,
width = "100%", height = "100%"
),
tags$div(
class = "flexfill-item-inner",
style = css(
position = "absolute",
top = 0, left = 0, right = 0, bottom = 0
),
el
)
)
} else {
stop("Unexpected flex argument: ", flexValue)
}
}, SIMPLIFY = FALSE, USE.NAMES = FALSE)
)
do.call(tags$div, c(attrs, divArgs))
}
css <- function(..., collapse_ = "") {
props <- list(...)
if (length(props) == 0) {
return("")
}
if (is.null(names(props)) || any(names(props) == "")) {
stop("cssList expects all arguments to be named")
}
# Necessary to make factors show up as level names, not numbers
props[] <- lapply(props, paste, collapse = " ")
# Drop null args
props <- props[!sapply(props, empty)]
if (length(props) == 0) {
return("")
}
# Replace all '.' and '_' in property names to '-'
names(props) <- gsub("[._]", "-", tolower(gsub("([A-Z])", "-\\1", names(props))))
# Create "!important" suffix for each property whose name ends with !, then
# remove the ! from the property name
important <- ifelse(grepl("!$", names(props), perl = TRUE), " !important", "")
names(props) <- sub("!$", "", names(props), perl = TRUE)
paste0(names(props), ":", props, important, ";", collapse = collapse_)
}
empty <- function(x) {
length(x) == 0 || (is.character(x) && !any(nzchar(x)))
}

View File

@@ -25,7 +25,6 @@ NULL
#' \code{\link{fluidPage}} function instead.
#'
#' @seealso \code{\link{fluidPage}}, \code{\link{fixedPage}}
#'
#' @export
bootstrapPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
@@ -33,26 +32,6 @@ bootstrapPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
shinyDeprecated("The 'responsive' argument is no longer used with Bootstrap 3.")
}
# required head tags for boostrap
importBootstrap <- function() {
list(
htmlDependency("bootstrap", "3.3.1",
c(
href = "shared/bootstrap",
file = system.file("www/shared/bootstrap", package = "shiny")
),
script = c(
"js/bootstrap.min.js",
# These shims are necessary for IE 8 compatibility
"shim/html5shiv.min.js",
"shim/respond.min.js"
),
stylesheet = if (is.null(theme)) "css/bootstrap.min.css",
meta = list(viewport = "width=device-width, initial-scale=1")
)
)
}
attachDependencies(
tagList(
if (!is.null(title)) tags$head(tags$title(title)),
@@ -63,7 +42,37 @@ bootstrapPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
# remainder of tags passed to the function
list(...)
),
importBootstrap()
bootstrapLib()
)
}
#' Bootstrap libraries
#'
#' This function returns a set of web dependencies necessary for using Bootstrap
#' components in a web page.
#'
#' It isn't necessary to call this function if you use
#' \code{\link{bootstrapPage}} or others which use \code{bootstrapPage}, such
#' \code{\link{basicPage}}, \code{\link{fluidPage}}, \code{\link{fillPage}},
#' \code{\link{pageWithSidebar}}, and \code{\link{navbarPage}}, because they
#' already include the Bootstrap web dependencies.
#'
#' @inheritParams bootstrapPage
#' @export
bootstrapLib <- function(theme = NULL) {
htmlDependency("bootstrap", "3.3.7",
c(
href = "shared/bootstrap",
file = system.file("www/shared/bootstrap", package = "shiny")
),
script = c(
"js/bootstrap.min.js",
# These shims are necessary for IE 8 compatibility
"shim/html5shiv.min.js",
"shim/respond.min.js"
),
stylesheet = if (is.null(theme)) "css/bootstrap.min.css",
meta = list(viewport = "width=device-width, initial-scale=1")
)
}
@@ -73,6 +82,102 @@ basicPage <- function(...) {
bootstrapPage(div(class="container-fluid", list(...)))
}
#' Create a page that fills the window
#'
#' \code{fillPage} creates a page whose height and width always fill the
#' available area of the browser window.
#'
#' The \code{\link{fluidPage}} and \code{\link{fixedPage}} functions are used
#' for creating web pages that are laid out from the top down, leaving
#' whitespace at the bottom if the page content's height is smaller than the
#' browser window, and scrolling if the content is larger than the window.
#'
#' \code{fillPage} is designed to latch the document body's size to the size of
#' the window. This makes it possible to fill it with content that also scales
#' to the size of the window.
#'
#' For example, \code{fluidPage(plotOutput("plot", height = "100\%"))} will not
#' work as expected; the plot element's effective height will be \code{0},
#' because the plot's containing elements (\code{<div>} and \code{<body>}) have
#' \emph{automatic} height; that is, they determine their own height based on
#' the height of their contained elements. However,
#' \code{fillPage(plotOutput("plot", height = "100\%"))} will work because
#' \code{fillPage} fixes the \code{<body>} height at 100\% of the window height.
#'
#' Note that \code{fillPage(plotOutput("plot"))} will not cause the plot to fill
#' the page. Like most Shiny output widgets, \code{plotOutput}'s default height
#' is a fixed number of pixels. You must explicitly set \code{height = "100\%"}
#' if you want a plot (or htmlwidget, say) to fill its container.
#'
#' One must be careful what layouts/panels/elements come between the
#' \code{fillPage} and the plots/widgets. Any container that has an automatic
#' height will cause children with \code{height = "100\%"} to misbehave. Stick
#' to functions that are designed for fill layouts, such as the ones in this
#' package.
#'
#' @param ... Elements to include within the page.
#' @param padding Padding to use for the body. This can be a numeric vector
#' (which will be interpreted as pixels) or a character vector with valid CSS
#' lengths. The length can be between one and four. If one, then that value
#' will be used for all four sides. If two, then the first value will be used
#' for the top and bottom, while the second value will be used for left and
#' right. If three, then the first will be used for top, the second will be
#' left and right, and the third will be bottom. If four, then the values will
#' be interpreted as top, right, bottom, and left respectively.
#' @param title The title to use for the browser window/tab (it will not be
#' shown in the document).
#' @param bootstrap If \code{TRUE}, load the Bootstrap CSS library.
#' @param theme URL to alternative Bootstrap stylesheet.
#'
#' @examples
#' fillPage(
#' tags$style(type = "text/css",
#' ".half-fill { width: 50%; height: 100%; }",
#' "#one { float: left; background-color: #ddddff; }",
#' "#two { float: right; background-color: #ccffcc; }"
#' ),
#' div(id = "one", class = "half-fill",
#' "Left half"
#' ),
#' div(id = "two", class = "half-fill",
#' "Right half"
#' ),
#' padding = 10
#' )
#'
#' fillPage(
#' fillRow(
#' div(style = "background-color: red; width: 100%; height: 100%;"),
#' div(style = "background-color: blue; width: 100%; height: 100%;")
#' )
#' )
#' @export
fillPage <- function(..., padding = 0, title = NULL, bootstrap = TRUE,
theme = NULL) {
fillCSS <- tags$head(tags$style(type = "text/css",
"html, body { width: 100%; height: 100%; overflow: hidden; }",
sprintf("body { padding: %s; margin: 0; }", collapseSizes(padding))
))
if (isTRUE(bootstrap)) {
bootstrapPage(title = title, theme = theme, fillCSS, ...)
} else {
tagList(
fillCSS,
if (!is.null(title)) tags$head(tags$title(title)),
...
)
}
}
collapseSizes <- function(padding) {
paste(
sapply(padding, shiny::validateCssUnit, USE.NAMES = FALSE),
collapse = " ")
}
#' Create a page with a sidebar
#'
#' Create a Shiny UI that contains a header with the application title, a
@@ -89,7 +194,7 @@ basicPage <- function(...) {
#'
#' @examples
#' # Define UI
#' shinyUI(pageWithSidebar(
#' pageWithSidebar(
#'
#' # Application title
#' headerPanel("Hello Shiny!"),
@@ -107,8 +212,7 @@ basicPage <- function(...) {
#' mainPanel(
#' plotOutput("distPlot")
#' )
#' ))
#'
#' )
#' @export
pageWithSidebar <- function(headerPanel,
sidebarPanel,
@@ -135,18 +239,24 @@ pageWithSidebar <- function(headerPanel,
#' toggle a set of \code{\link{tabPanel}} elements.
#'
#' @param title The title to display in the navbar
#' @param ... \code{\link{tabPanel}} elements to include in the page
#' @param ... \code{\link{tabPanel}} elements to include in the page. The
#' \code{navbarMenu} function also accepts strings, which will be used as menu
#' section headers. If the string is a set of dashes like \code{"----"} a
#' horizontal separator will be displayed in the menu.
#' @param id If provided, you can use \code{input$}\emph{\code{id}} in your
#' server logic to determine which of the current tabs is active. The value
#' will correspond to the \code{value} argument that is passed to
#' \code{\link{tabPanel}}.
#' @param selected The \code{value} (or, if none was supplied, the \code{title})
#' of the tab that should be selected by default. If \code{NULL}, the first
#' tab will be selected.
#' @param position Determines whether the navbar should be displayed at the top
#' of the page with normal scrolling behavior (\code{"static-top"}), pinned
#' at the top (\code{"fixed-top"}), or pinned at the bottom
#' of the page with normal scrolling behavior (\code{"static-top"}), pinned at
#' the top (\code{"fixed-top"}), or pinned at the bottom
#' (\code{"fixed-bottom"}). Note that using \code{"fixed-top"} or
#' \code{"fixed-bottom"} will cause the navbar to overlay your body content,
#' unless you add padding, e.g.:
#' \code{tags$style(type="text/css", "body {padding-top: 70px;}")}
#' unless you add padding, e.g.: \code{tags$style(type="text/css", "body
#' {padding-top: 70px;}")}
#' @param header Tag or list of tags to display as a common header above all
#' tabPanels.
#' @param footer Tag or list of tags to display as a common footer below all
@@ -175,26 +285,30 @@ pageWithSidebar <- function(headerPanel,
#' example below).
#'
#' @seealso \code{\link{tabPanel}}, \code{\link{tabsetPanel}},
#' \code{\link{updateNavbarPage}}
#' \code{\link{updateNavbarPage}}, \code{\link{insertTab}},
#' \code{\link{showTab}}
#'
#' @examples
#' shinyUI(navbarPage("App Title",
#' navbarPage("App Title",
#' tabPanel("Plot"),
#' tabPanel("Summary"),
#' tabPanel("Table")
#' ))
#' )
#'
#' shinyUI(navbarPage("App Title",
#' navbarPage("App Title",
#' tabPanel("Plot"),
#' navbarMenu("More",
#' tabPanel("Summary"),
#' "----",
#' "Section header",
#' tabPanel("Table")
#' )
#' ))
#' )
#' @export
navbarPage <- function(title,
...,
id = NULL,
selected = NULL,
position = c("static-top", "fixed-top", "fixed-bottom"),
header = NULL,
footer = NULL,
@@ -222,14 +336,25 @@ navbarPage <- function(title,
if (inverse)
navbarClass <- paste(navbarClass, "navbar-inverse")
if (!is.null(id))
selected <- restoreInput(id = id, default = selected)
# build the tabset
tabs <- list(...)
tabset <- buildTabset(tabs, "nav navbar-nav", NULL, id)
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),
@@ -243,7 +368,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)
),
@@ -251,14 +376,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))
@@ -277,10 +394,15 @@ navbarPage <- function(title,
)
}
#' @param menuName A name that identifies this \code{navbarMenu}. This
#' is needed if you want to insert/remove or show/hide an entire
#' \code{navbarMenu}.
#'
#' @rdname navbarPage
#' @export
navbarMenu <- function(title, ..., icon = NULL) {
navbarMenu <- function(title, ..., menuName = title, icon = NULL) {
structure(list(title = title,
menuName = menuName,
tabs = list(...),
iconClass = iconClass(icon)),
class = "shiny.navbarmenu")
@@ -314,7 +436,6 @@ headerPanel <- function(title, windowTitle=title) {
#'
#' @param ... UI elements to include inside the panel.
#' @return The newly created panel.
#'
#' @export
wellPanel <- function(...) {
div(class="well", ...)
@@ -387,6 +508,8 @@ mainPanel <- function(..., width = 8) {
#'
#' @param condition A JavaScript expression that will be evaluated repeatedly to
#' determine whether the panel should be displayed.
#' @param ns The \code{\link[=NS]{namespace}} object of the current module, if
#' any.
#' @param ... Elements to include in the panel.
#'
#' @note You are not recommended to use special JavaScript characters such as a
@@ -395,33 +518,55 @@ mainPanel <- function(..., width = 8) {
#' \code{input["foo.bar"]} instead of \code{input.foo.bar} to read the input
#' value.
#' @examples
#' sidebarPanel(
#' selectInput(
#' "plotType", "Plot Type",
#' c(Scatter = "scatter",
#' Histogram = "hist")),
#'
#' # Only show this panel if the plot type is a histogram
#' conditionalPanel(
#' condition = "input.plotType == 'hist'",
#' selectInput(
#' "breaks", "Breaks",
#' c("Sturges",
#' "Scott",
#' "Freedman-Diaconis",
#' "[Custom]" = "custom")),
#'
#' # Only show this panel if Custom is selected
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' ui <- fluidPage(
#' sidebarPanel(
#' selectInput("plotType", "Plot Type",
#' c(Scatter = "scatter", Histogram = "hist")
#' ),
#' # Only show this panel if the plot type is a histogram
#' conditionalPanel(
#' condition = "input.breaks == 'custom'",
#' sliderInput("breakCount", "Break Count", min=1, max=1000, value=10)
#' condition = "input.plotType == 'hist'",
#' selectInput(
#' "breaks", "Breaks",
#' c("Sturges", "Scott", "Freedman-Diaconis", "[Custom]" = "custom")
#' ),
#' # Only show this panel if Custom is selected
#' conditionalPanel(
#' condition = "input.breaks == 'custom'",
#' sliderInput("breakCount", "Break Count", min = 1, max = 50, value = 10)
#' )
#' )
#' )
#' )
#' ),
#' mainPanel(
#' plotOutput("plot")
#' )
#' )
#'
#' server <- function(input, output) {
#' x <- rnorm(100)
#' y <- rnorm(100)
#'
#' output$plot <- renderPlot({
#' if (input$plotType == "scatter") {
#' plot(x, y)
#' } else {
#' breaks <- input$breaks
#' if (breaks == "custom") {
#' breaks <- input$breakCount
#' }
#'
#' hist(x, breaks = breaks)
#' }
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
conditionalPanel <- function(condition, ...) {
div('data-display-if'=condition, ...)
conditionalPanel <- function(condition, ..., ns = NS(NULL)) {
div(`data-display-if`=condition, `data-ns-prefix`=ns(""), ...)
}
#' Create a help text element
@@ -491,13 +636,12 @@ tabPanel <- function(title, ..., value = title, icon = NULL) {
#' tab will be selected.
#' @param type Use "tabs" for the standard look; Use "pills" for a more plain
#' look where tabs are selected using a background fill color.
#' @param position The position of the tabs relative to the content. Valid
#' values are "above", "below", "left", and "right" (defaults to "above").
#' Note that the \code{position} argument is not valid when \code{type} is
#' "pill".
#' @param position This argument is deprecated; it has been discontinued in
#' Bootstrap 3.
#' @return A tabset that can be passed to \code{\link{mainPanel}}
#'
#' @seealso \code{\link{tabPanel}}, \code{\link{updateTabsetPanel}}
#' @seealso \code{\link{tabPanel}}, \code{\link{updateTabsetPanel}},
#' \code{\link{insertTab}}, \code{\link{showTab}}
#'
#' @examples
#' # Show a tabset that includes a plot, summary, and
@@ -514,25 +658,28 @@ tabsetPanel <- function(...,
id = NULL,
selected = NULL,
type = c("tabs", "pills"),
position = c("above", "below", "left", "right")) {
position = NULL) {
if (!is.null(position)) {
shinyDeprecated(msg = paste("tabsetPanel: argument 'position' is deprecated;",
"it has been discontinued in Bootstrap 3."),
version = "0.10.2.2")
}
if (!is.null(id))
selected <- restoreInput(id = id, default = selected)
# build the tabset
tabs <- list(...)
type <- match.arg(type)
tabset <- buildTabset(tabs, paste0("nav nav-", type), NULL, id, selected)
# position the nav list and content appropriately
position <- match.arg(position)
if (position %in% c("above", "left", "right")) {
first <- tabset$navList
second <- tabset$content
} else if (position %in% c("below")) {
first <- tabset$content
second <- tabset$navList
}
# create the content
first <- tabset$navList
second <- tabset$content
# create the tab div
tags$div(class = paste("tabbable tabs-", position, sep=""), first, second)
tags$div(class = "tabbable", first, second)
}
#' Create a navigation list panel
@@ -561,9 +708,11 @@ tabsetPanel <- function(...,
#' supported. This is because version 0.11 switched to Bootstrap 3, which
#' doesn't support separators.
#'
#' @seealso \code{\link{tabPanel}}, \code{\link{updateNavlistPanel}}
#' @seealso \code{\link{tabPanel}}, \code{\link{updateNavlistPanel}},
#' \code{\link{insertTab}}, \code{\link{showTab}}
#'
#' @examples
#' shinyUI(fluidPage(
#' fluidPage(
#'
#' titlePanel("Application Title"),
#'
@@ -573,7 +722,7 @@ tabsetPanel <- function(...,
#' tabPanel("Second"),
#' tabPanel("Third")
#' )
#' ))
#' )
#' @export
navlistPanel <- function(...,
id = NULL,
@@ -587,6 +736,9 @@ navlistPanel <- function(...,
tags$li(class="navbar-brand", text)
}
if (!is.null(id))
selected <- restoreInput(id = id, default = selected)
# build the tabset
tabs <- list(...)
tabset <- buildTabset(tabs,
@@ -608,120 +760,160 @@ navlistPanel <- function(...,
fixedRow(columns)
}
# Helpers to build tabsetPanels (& Co.) and their elements
markTabAsSelected <- function(x) {
attr(x, "selected") <- TRUE
x
}
buildTabset <- function(tabs,
ulClass,
textFilter = NULL,
id = NULL,
selected = NULL) {
isTabSelected <- function(x) {
isTRUE(attr(x, "selected", exact = TRUE))
}
# build tab nav list and tab content div
containsSelectedTab <- function(tabs) {
any(vapply(tabs, isTabSelected, logical(1)))
}
# add tab input sentinel class if we have an id
if (!is.null(id))
ulClass <- paste(ulClass, "shiny-tab-input")
findAndMarkSelectedTab <- function(tabs, selected, foundSelected) {
tabs <- lapply(tabs, function(div) {
if (foundSelected || is.character(div)) {
# Strings are not selectable items
tabNavList <- tags$ul(class = ulClass, id = id)
tabContent <- tags$div(class = "tab-content")
firstTab <- TRUE
tabsetId <- p_randomInt(1000, 10000)
tabId <- 1
for (divTag in tabs) {
} else if (inherits(div, "shiny.navbarmenu")) {
# Recur for navbarMenus
res <- findAndMarkSelectedTab(div$tabs, selected, foundSelected)
div$tabs <- res$tabs
foundSelected <<- res$foundSelected
# check for text; pass it to the textFilter or skip it if there is none
if (is.character(divTag)) {
if (!is.null(textFilter))
tabNavList <- tagAppendChild(tabNavList, textFilter(divTag))
next
}
# compute id and assign it to the div
thisId <- paste("tab", tabsetId, tabId, sep="-")
divTag$attribs$id <- thisId
tabId <- tabId + 1
tabValue <- divTag$attribs$`data-value`
# function to append an optional icon to an aTag
appendIcon <- function(aTag, iconClass) {
if (!is.null(iconClass)) {
# for font-awesome we specify fixed-width
if (grepl("fa-", iconClass, fixed = TRUE))
iconClass <- paste(iconClass, "fa-fw")
aTag <- tagAppendChild(aTag, icon(name = NULL, class = iconClass))
} else {
# Base case: regular tab item. If the `selected` argument is
# provided, check for a match in the existing tabs; else,
# mark first available item as selected
if (is.null(selected)) {
foundSelected <<- TRUE
div <- markTabAsSelected(div)
} else {
tabValue <- div$attribs$`data-value` %OR% div$attribs$title
if (identical(selected, tabValue)) {
foundSelected <<- TRUE
div <- markTabAsSelected(div)
}
}
aTag
}
return(div)
})
return(list(tabs = tabs, foundSelected = foundSelected))
}
# check for a navbarMenu and handle appropriately
if (inherits(divTag, "shiny.navbarmenu")) {
# create the a tag
aTag <- tags$a(href="#",
class="dropdown-toggle",
`data-toggle`="dropdown")
# add optional icon
aTag <- appendIcon(aTag, divTag$iconClass)
# add the title and caret
aTag <- tagAppendChild(aTag, divTag$title)
aTag <- tagAppendChild(aTag, tags$b(class="caret"))
# build the dropdown list element
liTag <- tags$li(class = "dropdown", aTag)
# build the child tabset
tabset <- buildTabset(divTag$tabs, "dropdown-menu")
liTag <- tagAppendChild(liTag, tabset$navList)
# don't add a standard tab content div, rather add the list of tab
# content divs that are contained within the tabset
divTag <- NULL
tabContent <- tagAppendChildren(tabContent,
list = tabset$content$children)
# Returns the icon object (or NULL if none), provided either a
# tabPanel, OR the icon class
getIcon <- function(tab = NULL, iconClass = NULL) {
if (!is.null(tab)) iconClass <- tab$attribs$`data-icon-class`
if (!is.null(iconClass)) {
if (grepl("fa-", iconClass, fixed = TRUE)) {
# for font-awesome we specify fixed-width
iconClass <- paste(iconClass, "fa-fw")
}
# else it's a standard navbar item
else {
# create the a tag
aTag <- tags$a(href=paste("#", thisId, sep=""),
`data-toggle` = "tab",
`data-value` = tabValue)
icon(name = NULL, class = iconClass)
} else NULL
}
# append optional icon
aTag <- appendIcon(aTag, divTag$attribs$`data-icon-class`)
# Text filter for navbarMenu's (plain text) separators
navbarMenuTextFilter <- function(text) {
if (grepl("^\\-+$", text)) tags$li(class = "divider")
else tags$li(class = "dropdown-header", text)
}
# add the title
aTag <- tagAppendChild(aTag, divTag$attribs$title)
# This function is called internally by navbarPage, tabsetPanel
# and navlistPanel
buildTabset <- function(tabs, ulClass, textFilter = NULL, id = NULL,
selected = NULL, foundSelected = FALSE) {
# create the li tag
liTag <- tags$li(aTag)
}
res <- findAndMarkSelectedTab(tabs, selected, foundSelected)
tabs <- res$tabs
foundSelected <- res$foundSelected
if (is.null(tabValue)) {
tabValue <- divTag$attribs$title
}
# add input class if we have an id
if (!is.null(id)) ulClass <- paste(ulClass, "shiny-tab-input")
# If appropriate, make this the selected tab (don't ever do initial
# selection of tabs that are within a navbarMenu)
if ((ulClass != "dropdown-menu") &&
((firstTab && is.null(selected)) ||
(!is.null(selected) && identical(selected, tabValue)))) {
liTag$attribs$class <- "active"
divTag$attribs$class <- "tab-pane active"
firstTab = FALSE
}
divTag$attribs$title <- NULL
# append the elements to our lists
tabNavList <- tagAppendChild(tabNavList, liTag)
tabContent <- tagAppendChild(tabContent, divTag)
if (anyNamed(tabs)) {
nms <- names(tabs)
nms <- nms[nzchar(nms)]
stop("Tabs should all be unnamed arguments, but some are named: ",
paste(nms, collapse = ", "))
}
tabsetId <- p_randomInt(1000, 10000)
tabs <- lapply(seq_len(length(tabs)), buildTabItem,
tabsetId = tabsetId, foundSelected = foundSelected,
tabs = tabs, textFilter = textFilter)
tabNavList <- tags$ul(class = ulClass, id = id,
`data-tabsetid` = tabsetId, lapply(tabs, "[[", 1))
tabContent <- tags$div(class = "tab-content",
`data-tabsetid` = tabsetId, lapply(tabs, "[[", 2))
list(navList = tabNavList, content = tabContent)
}
# Builds tabPanel/navbarMenu items (this function used to be
# declared inside the buildTabset() function and it's been
# refactored for clarity and reusability). Called internally
# by buildTabset.
buildTabItem <- function(index, tabsetId, foundSelected, tabs = NULL,
divTag = NULL, textFilter = NULL) {
divTag <- if (!is.null(divTag)) divTag else tabs[[index]]
if (is.character(divTag) && !is.null(textFilter)) {
# text item: pass it to the textFilter if it exists
liTag <- textFilter(divTag)
divTag <- NULL
} else if (inherits(divTag, "shiny.navbarmenu")) {
# navbarMenu item: build the child tabset
tabset <- buildTabset(divTag$tabs, "dropdown-menu",
navbarMenuTextFilter, foundSelected = foundSelected)
# if this navbarMenu contains a selected item, mark it active
containsSelected <- containsSelectedTab(divTag$tabs)
liTag <- tags$li(
class = paste0("dropdown", if (containsSelected) " active"),
tags$a(href = "#",
class = "dropdown-toggle", `data-toggle` = "dropdown",
`data-value` = divTag$menuName,
getIcon(iconClass = divTag$iconClass),
divTag$title, tags$b(class = "caret")
),
tabset$navList # inner tabPanels items
)
# list of tab content divs from the child tabset
divTag <- tabset$content$children
} else {
# tabPanel item: create the tab's liTag and divTag
tabId <- paste("tab", tabsetId, index, sep = "-")
liTag <- tags$li(
tags$a(
href = paste("#", tabId, sep = ""),
`data-toggle` = "tab",
`data-value` = divTag$attribs$`data-value`,
getIcon(iconClass = divTag$attribs$`data-icon-class`),
divTag$attribs$title
)
)
# if this tabPanel is selected item, mark it active
if (isTabSelected(divTag)) {
liTag$attribs$class <- "active"
divTag$attribs$class <- "tab-pane active"
}
divTag$attribs$id <- tabId
divTag$attribs$title <- NULL
}
return(list(liTag = liTag, divTag = divTag))
}
#' Create a text output element
#'
@@ -746,24 +938,38 @@ 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 = " ")
)
}
#' @name plotOutput
#' @rdname plotOutput
#' @export
imageOutput <- function(outputId, width = "100%", height="400px",
@@ -872,10 +1078,16 @@ imageOutput <- function(outputId, width = "100%", height="400px",
#' \code{brush}.
#'
#' For \code{plotOutput}, the coordinates will be sent scaled to the data
#' space, if possible. (At the moment, plots generated by base graphics
#' support this scaling, although plots generated by grid or ggplot2 do not.)
#' If scaling is not possible, the raw pixel coordinates will be sent. For
#' \code{imageOutput}, the coordinates will be sent in raw pixel coordinates.
#' space, if possible. (At the moment, plots generated by base graphics and
#' ggplot2 support this scaling, although plots generated by lattice and
#' others do not.) If scaling is not possible, the raw pixel coordinates will
#' be sent. For \code{imageOutput}, the coordinates will be sent in raw pixel
#' coordinates.
#'
#' With ggplot2 graphics, the code in \code{renderPlot} should return a ggplot
#' object; if instead the code prints the ggplot2 object with something like
#' \code{print(p)}, then the coordinates for interactive graphics will not be
#' properly scaled to the data space.
#'
#' @param outputId output variable to read the plot/image from.
#' @param width,height Image width/height. Must be a valid CSS unit (like
@@ -889,8 +1101,8 @@ imageOutput <- function(outputId, width = "100%", height="400px",
#' created by the \code{\link{clickOpts}} function. If you use a value like
#' \code{"plot_click"} (or equivalently, \code{clickOpts(id="plot_click")}),
#' the plot will send coordinates to the server whenever it is clicked, and
#' the value will be accessible via \code{input$plot_click}. The value will
#' be a named list with \code{x} and \code{y} elements indicating the mouse
#' the value will be accessible via \code{input$plot_click}. The value will be
#' a named list with \code{x} and \code{y} elements indicating the mouse
#' position.
#' @param dblclick This is just like the \code{click} argument, but for
#' double-click events.
@@ -921,10 +1133,13 @@ imageOutput <- function(outputId, width = "100%", height="400px",
#' be able to draw a rectangle in the plotting area and drag it around. The
#' value will be a named list with \code{xmin}, \code{xmax}, \code{ymin}, and
#' \code{ymax} elements indicating the brush area. To control the brush
#' behavior, use \code{\link{brushOpts}}.
#' behavior, use \code{\link{brushOpts}}. Multiple
#' \code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
#' value; brushing one image or plot will cause any other brushes with the
#' 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.
#'
@@ -1222,6 +1437,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{
@@ -1240,27 +1456,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, ...)
}
@@ -1271,8 +1489,9 @@ downloadLink <- function(outputId, label="Download", class=NULL) {
#' \code{\link{navbarPage}}.
#'
#' @param name Name of icon. Icons are drawn from the
#' \href{http://fontawesome.io/icons/}{Font Awesome} and
#' \href{http://getbootstrap.com/components/#glyphicons}{Glyphicons"}
#' \href{https://fontawesome.com/v4.7.0/}{Font Awesome} (currently icons from
#' the v4.7.0 set are supported) and
#' \href{http://getbootstrap.com/components/#glyphicons}{Glyphicons}
#' libraries. Note that the "fa-" and "glyphicon-" prefixes should not be used
#' in icon names (i.e. the "fa-calendar" icon should be referred to as
#' "calendar")
@@ -1296,12 +1515,11 @@ downloadLink <- function(outputId, label="Download", class=NULL) {
#' # add an icon to a submit button
#' submitButton("Update View", icon = icon("refresh"))
#'
#' shinyUI(navbarPage("App Title",
#' navbarPage("App Title",
#' tabPanel("Plot", icon = icon("bar-chart-o")),
#' tabPanel("Summary", icon = icon("list-alt")),
#' tabPanel("Table", icon = icon("table"))
#' ))
#'
#' )
#' @export
icon <- function(name, class = NULL, lib = "font-awesome") {
prefixes <- list(
@@ -1329,7 +1547,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.2.0", c(href="shared/font-awesome"),
"font-awesome", "4.7.0", c(href="shared/font-awesome"),
stylesheet = "css/font-awesome.min.css"
)
}

561
R/cache-disk.R Normal file
View File

@@ -0,0 +1,561 @@
#' Create a disk cache object
#'
#' A disk cache object is a key-value store that saves the values as files in a
#' directory on disk. Objects can be stored and retrieved using the \code{get()}
#' and \code{set()} methods. Objects are automatically pruned from the cache
#' according to the parameters \code{max_size}, \code{max_age}, \code{max_n},
#' and \code{evict}.
#'
#'
#' @section Missing Keys:
#'
#' The \code{missing} and \code{exec_missing} parameters controls what happens
#' when \code{get()} is called with a key that is not in the cache (a cache
#' miss). The default behavior is to return a \code{\link{key_missing}}
#' object. This is a \emph{sentinel value} that indicates that the key was not
#' present in the cache. You can test if the returned value represents a
#' missing key by using the \code{\link{is.key_missing}} function. You can
#' also have \code{get()} return a different sentinel value, like \code{NULL}.
#' If you want to throw an error on a cache miss, you can do so by providing a
#' function for \code{missing} that takes one argument, the key, and also use
#' \code{exec_missing=TRUE}.
#'
#' When the cache is created, you can supply a value for \code{missing}, which
#' sets the default value to be returned for missing values. It can also be
#' overridden when \code{get()} is called, by supplying a \code{missing}
#' argument. For example, if you use \code{cache$get("mykey", missing =
#' NULL)}, it will return \code{NULL} if the key is not in the cache.
#'
#' If your cache is configured so that \code{get()} returns a sentinel value
#' to represent a cache miss, then \code{set} will also not allow you to store
#' the sentinel value in the cache. It will throw an error if you attempt to
#' do so.
#'
#' Instead of returning the same sentinel value each time there is cache miss,
#' the cache can execute a function each time \code{get()} encounters missing
#' key. If the function returns a value, then \code{get()} will in turn return
#' that value. However, a more common use is for the function to throw an
#' error. If an error is thrown, then \code{get()} will not return a value.
#'
#' To do this, pass a one-argument function to \code{missing}, and use
#' \code{exec_missing=TRUE}. For example, if you want to throw an error that
#' prints the missing key, you could do this:
#'
#' \preformatted{
#' diskCache(
#' missing = function(key) {
#' stop("Attempted to get missing key: ", key)
#' },
#' exec_missing = TRUE
#' )
#' }
#'
#' If you use this, the code that calls \code{get()} should be wrapped with
#' \code{\link{tryCatch}()} to gracefully handle missing keys.
#'
#' @section Cache pruning:
#'
#' Cache pruning occurs when \code{set()} is called, or it can be invoked
#' manually by calling \code{prune()}.
#'
#' The disk cache will throttle the pruning so that it does not happen on
#' every call to \code{set()}, because the filesystem operations for checking
#' the status of files can be slow. Instead, it will prune once in every 20
#' calls to \code{set()}, or if at least 5 seconds have elapsed since the last
#' prune occurred, whichever is first. These parameters are currently not
#' customizable, but may be in the future.
#'
#' When a pruning occurs, if there are any objects that are older than
#' \code{max_age}, they will be removed.
#'
#' The \code{max_size} and \code{max_n} parameters are applied to the cache as
#' a whole, in contrast to \code{max_age}, which is applied to each object
#' individually.
#'
#' If the number of objects in the cache exceeds \code{max_n}, then objects
#' will be removed from the cache according to the eviction policy, which is
#' set with the \code{evict} parameter. Objects will be removed so that the
#' number of items is \code{max_n}.
#'
#' If the size of the objects in the cache exceeds \code{max_size}, then
#' objects will be removed from the cache. Objects will be removed from the
#' cache so that the total size remains under \code{max_size}. Note that the
#' size is calculated using the size of the files, not the size of disk space
#' used by the files -- these two values can differ because of files are
#' stored in blocks on disk. For example, if the block size is 4096 bytes,
#' then a file that is one byte in size will take 4096 bytes on disk.
#'
#' Another time that objects can be removed from the cache is when
#' \code{get()} is called. If the target object is older than \code{max_age},
#' it will be removed and the cache will report it as a missing value.
#'
#' @section Eviction policies:
#'
#' If \code{max_n} or \code{max_size} are used, then objects will be removed
#' from the cache according to an eviction policy. The available eviction
#' policies are:
#'
#' \describe{
#' \item{\code{"lru"}}{
#' Least Recently Used. The least recently used objects will be removed.
#' This uses the filesystem's mtime property. When "lru" is used, each
#' \code{get()} is called, it will update the file's mtime.
#' }
#' \item{\code{"fifo"}}{
#' First-in-first-out. The oldest objects will be removed.
#' }
#' }
#'
#' Both of these policies use files' mtime. Note that some filesystems (notably
#' FAT) have poor mtime resolution. (atime is not used because support for
#' atime is worse than mtime.)
#'
#'
#' @section Sharing among multiple processes:
#'
#' The directory for a DiskCache can be shared among multiple R processes. To
#' do this, each R process should have a DiskCache object that uses the same
#' directory. Each DiskCache will do pruning independently of the others, so if
#' they have different pruning parameters, then one DiskCache may remove cached
#' objects before another DiskCache would do so.
#'
#' Even though it is possible for multiple processes to share a DiskCache
#' directory, this should not be done on networked file systems, because of
#' slow performance of networked file systems can cause problems. If you need
#' a high-performance shared cache, you can use one built on a database like
#' Redis, SQLite, mySQL, or similar.
#'
#' When multiple processes share a cache directory, there are some potential
#' race conditions. For example, if your code calls \code{exists(key)} to check
#' if an object is in the cache, and then call \code{get(key)}, the object may
#' be removed from the cache in between those two calls, and \code{get(key)}
#' will throw an error. Instead of calling the two functions, it is better to
#' simply call \code{get(key)}, and use \code{tryCatch()} to handle the error
#' that is thrown if the object is not in the cache. This effectively tests for
#' existence and gets the object in one operation.
#'
#' It is also possible for one processes to prune objects at the same time that
#' another processes is trying to prune objects. If this happens, you may see
#' a warning from \code{file.remove()} failing to remove a file that has
#' already been deleted.
#'
#'
#' @section Methods:
#'
#' A disk cache object has the following methods:
#'
#' \describe{
#' \item{\code{get(key, missing, exec_missing)}}{
#' Returns the value associated with \code{key}. If the key is not in the
#' cache, then it returns the value specified by \code{missing} or,
#' \code{missing} is a function and \code{exec_missing=TRUE}, then
#' executes \code{missing}. The function can throw an error or return the
#' value. If either of these parameters are specified here, then they
#' will override the defaults that were set when the DiskCache object was
#' created. See section Missing Keys for more information.
#' }
#' \item{\code{set(key, value)}}{
#' Stores the \code{key}-\code{value} pair in the cache.
#' }
#' \item{\code{exists(key)}}{
#' Returns \code{TRUE} if the cache contains the key, otherwise
#' \code{FALSE}.
#' }
#' \item{\code{size()}}{
#' Returns the number of items currently in the cache.
#' }
#' \item{\code{keys()}}{
#' Returns a character vector of all keys currently in the cache.
#' }
#' \item{\code{reset()}}{
#' Clears all objects from the cache.
#' }
#' \item{\code{destroy()}}{
#' Clears all objects in the cache, and removes the cache directory from
#' disk.
#' }
#' \item{\code{prune()}}{
#' Prunes the cache, using the parameters specified by \code{max_size},
#' \code{max_age}, \code{max_n}, and \code{evict}.
#' }
#' }
#'
#' @param dir Directory to store files for the cache. If \code{NULL} (the
#' default) it will create and use a temporary directory.
#' @param max_age Maximum age of files in cache before they are evicted, in
#' seconds. Use \code{Inf} for no age limit.
#' @param max_size Maximum size of the cache, in bytes. If the cache exceeds
#' this size, cached objects will be removed according to the value of the
#' \code{evict}. Use \code{Inf} for no size limit.
#' @param max_n Maximum number of objects in the cache. If the number of objects
#' exceeds this value, then cached objects will be removed according to the
#' value of \code{evict}. Use \code{Inf} for no limit of number of items.
#' @param evict The eviction policy to use to decide which objects are removed
#' when a cache pruning occurs. Currently, \code{"lru"} and \code{"fifo"} are
#' supported.
#' @param destroy_on_finalize If \code{TRUE}, then when the DiskCache object is
#' garbage collected, the cache directory and all objects inside of it will be
#' deleted from disk. If \code{FALSE} (the default), it will do nothing when
#' finalized.
#' @param missing A value to return or a function to execute when
#' \code{get(key)} is called but the key is not present in the cache. The
#' default is a \code{\link{key_missing}} object. If it is a function to
#' execute, the function must take one argument (the key), and you must also
#' use \code{exec_missing = TRUE}. If it is a function, it is useful in most
#' cases for it to throw an error, although another option is to return a
#' value. If a value is returned, that value will in turn be returned by
#' \code{get()}. See section Missing keys for more information.
#' @param exec_missing If \code{FALSE} (the default), then treat \code{missing}
#' as a value to return when \code{get()} results in a cache miss. If
#' \code{TRUE}, treat \code{missing} as a function to execute when
#' \code{get()} results in a cache miss.
#' @param logfile An optional filename or connection object to where logging
#' information will be written. To log to the console, use \code{stdout()}.
#'
#' @export
diskCache <- function(
dir = NULL,
max_size = 10 * 1024 ^ 2,
max_age = Inf,
max_n = Inf,
evict = c("lru", "fifo"),
destroy_on_finalize = FALSE,
missing = key_missing(),
exec_missing = FALSE,
logfile = NULL)
{
DiskCache$new(dir, max_size, max_age, max_n, evict, destroy_on_finalize,
missing, exec_missing, logfile)
}
DiskCache <- R6Class("DiskCache",
public = list(
initialize = function(
dir = NULL,
max_size = 10 * 1024 ^ 2,
max_age = Inf,
max_n = Inf,
evict = c("lru", "fifo"),
destroy_on_finalize = FALSE,
missing = key_missing(),
exec_missing = FALSE,
logfile = NULL)
{
if (exec_missing && (!is.function(missing) || length(formals(missing)) == 0)) {
stop("When `exec_missing` is true, `missing` must be a function that takes one argument.")
}
if (is.null(dir)) {
dir <- tempfile("DiskCache-")
}
if (!is.numeric(max_size)) stop("max_size must be a number. Use `Inf` for no limit.")
if (!is.numeric(max_age)) stop("max_age must be a number. Use `Inf` for no limit.")
if (!is.numeric(max_n)) stop("max_n must be a number. Use `Inf` for no limit.")
if (!dirExists(dir)) {
private$log(paste0("initialize: Creating ", dir))
dir.create(dir, recursive = TRUE)
}
private$dir <- normalizePath(dir)
private$max_size <- max_size
private$max_age <- max_age
private$max_n <- max_n
private$evict <- match.arg(evict)
private$destroy_on_finalize <- destroy_on_finalize
private$missing <- missing
private$exec_missing <- exec_missing
private$logfile <- logfile
private$prune_last_time <- as.numeric(Sys.time())
},
get = function(key, missing = private$missing, exec_missing = private$exec_missing) {
private$log(paste0('get: key "', key, '"'))
self$is_destroyed(throw = TRUE)
validate_key(key)
private$maybe_prune_single(key)
filename <- private$key_to_filename(key)
# Instead of calling exists() before fetching the value, just try to
# fetch the value. This reduces the risk of a race condition when
# multiple processes share a cache.
read_error <- FALSE
tryCatch(
{
value <- suppressWarnings(readRDS(filename))
if (private$evict == "lru"){
Sys.setFileTime(filename, Sys.time())
}
},
error = function(e) {
read_error <<- TRUE
}
)
if (read_error) {
private$log(paste0('get: key "', key, '" is missing'))
if (exec_missing) {
if (!is.function(missing) || length(formals(missing)) == 0) {
stop("When `exec_missing` is true, `missing` must be a function that takes one argument.")
}
return(missing(key))
} else {
return(missing)
}
}
private$log(paste0('get: key "', key, '" found'))
value
},
set = function(key, value) {
private$log(paste0('set: key "', key, '"'))
self$is_destroyed(throw = TRUE)
validate_key(key)
file <- private$key_to_filename(key)
temp_file <- paste0(file, "-temp-", createUniqueId(8))
save_error <- FALSE
ref_object <- FALSE
tryCatch(
{
saveRDS(value, file = temp_file,
refhook = function(x) {
ref_object <<- TRUE
NULL
}
)
file.rename(temp_file, file)
},
error = function(e) {
save_error <<- TRUE
# Unlike file.remove(), unlink() does not raise warning if file does
# not exist.
unlink(temp_file)
}
)
if (save_error) {
private$log(paste0('set: key "', key, '" error'))
stop('Error setting value for key "', key, '".')
}
if (ref_object) {
private$log(paste0('set: value is a reference object'))
warning("A reference object was cached in a serialized format. The restored object may not work as expected.")
}
private$prune_throttled()
invisible(self)
},
exists = function(key) {
self$is_destroyed(throw = TRUE)
validate_key(key)
file.exists(private$key_to_filename(key))
},
# Return all keys in the cache
keys = function() {
self$is_destroyed(throw = TRUE)
files <- dir(private$dir, "\\.rds$")
sub("\\.rds$", "", files)
},
remove = function(key) {
private$log(paste0('remove: key "', key, '"'))
self$is_destroyed(throw = TRUE)
validate_key(key)
file.remove(private$key_to_filename(key))
invisible(self)
},
reset = function() {
private$log(paste0('reset'))
self$is_destroyed(throw = TRUE)
file.remove(dir(private$dir, "\\.rds$", full.names = TRUE))
invisible(self)
},
prune = function() {
# TODO: It would be good to add parameters `n` and `size`, so that the
# cache can be pruned to `max_n - n` and `max_size - size` before adding
# an object. Right now we prune after adding the object, so the cache
# can temporarily grow past the limits. The reason we don't do this now
# is because it is expensive to find the size of the serialized object
# before adding it.
private$log(paste0('prune'))
self$is_destroyed(throw = TRUE)
current_time <- Sys.time()
filenames <- dir(private$dir, "\\.rds$", full.names = TRUE)
info <- file.info(filenames)
info <- info[info$isdir == FALSE, ]
info$name <- rownames(info)
rownames(info) <- NULL
# Files could be removed between the dir() and file.info() calls. The
# entire row for such files will have NA values. Remove those rows.
info <- info[!is.na(info$size), ]
# 1. Remove any files where the age exceeds max age.
if (is.finite(private$max_age)) {
timediff <- as.numeric(current_time - info$mtime, units = "secs")
rm_idx <- timediff > private$max_age
if (any(rm_idx)) {
private$log(paste0("prune max_age: Removing ", paste(info$name[rm_idx], collapse = ", ")))
file.remove(info$name[rm_idx])
info <- info[!rm_idx, ]
}
}
# Sort objects by priority. The sorting is done in a function which can be
# called multiple times but only does the work the first time.
info_is_sorted <- FALSE
ensure_info_is_sorted <- function() {
if (info_is_sorted) return()
info <<- info[order(info$mtime, decreasing = TRUE), ]
info_is_sorted <<- TRUE
}
# 2. Remove files if there are too many.
if (is.finite(private$max_n) && nrow(info) > private$max_n) {
ensure_info_is_sorted()
rm_idx <- seq_len(nrow(info)) > private$max_n
private$log(paste0("prune max_n: Removing ", paste(info$name[rm_idx], collapse = ", ")))
rm_success <- file.remove(info$name[rm_idx])
info <- info[!rm_success, ]
}
# 3. Remove files if cache is too large.
if (is.finite(private$max_size) && sum(info$size) > private$max_size) {
ensure_info_is_sorted()
cum_size <- cumsum(info$size)
rm_idx <- cum_size > private$max_size
private$log(paste0("prune max_size: Removing ", paste(info$name[rm_idx], collapse = ", ")))
rm_success <- file.remove(info$name[rm_idx])
info <- info[!rm_success, ]
}
private$prune_last_time <- as.numeric(current_time)
invisible(self)
},
size = function() {
self$is_destroyed(throw = TRUE)
length(dir(private$dir, "\\.rds$"))
},
destroy = function() {
if (self$is_destroyed()) {
return(invisible(self))
}
private$log(paste0("destroy: Removing ", private$dir))
# First create a sentinel file so that other processes sharing this
# cache know that the cache is to be destroyed. This is needed because
# the recursive unlink is not atomic: another process can add a file to
# the directory after unlink starts removing files but before it removes
# the directory, and when that happens, the directory removal will fail.
file.create(file.path(private$dir, "__destroyed__"))
# Remove all the .rds files. This will not remove the setinel file.
file.remove(dir(private$dir, "\\.rds$", full.names = TRUE))
# Next remove dir recursively, including sentinel file.
unlink(private$dir, recursive = TRUE)
private$destroyed <- TRUE
invisible(self)
},
is_destroyed = function(throw = FALSE) {
if (!dirExists(private$dir) ||
file.exists(file.path(private$dir, "__destroyed__")))
{
# It's possible for another process to destroy a shared cache directory
private$destroyed <- TRUE
}
if (throw) {
if (private$destroyed) {
stop("Attempted to use cache which has been destroyed:\n ", private$dir)
}
} else {
private$destroyed
}
},
finalize = function() {
if (private$destroy_on_finalize) {
self$destroy()
}
}
),
private = list(
dir = NULL,
max_age = NULL,
max_size = NULL,
max_n = NULL,
evict = NULL,
destroy_on_finalize = NULL,
destroyed = FALSE,
missing = NULL,
exec_missing = FALSE,
logfile = NULL,
prune_throttle_counter = 0,
prune_last_time = NULL,
key_to_filename = function(key) {
validate_key(key)
# Additional validation. This 80-char limit is arbitrary, and is
# intended to avoid hitting a filename length limit on Windows.
if (nchar(key) > 80) {
stop("Invalid key: key must have fewer than 80 characters.")
}
file.path(private$dir, paste0(key, ".rds"))
},
# A wrapper for prune() that throttles it, because prune() can be
# expensive due to filesystem operations. This function will prune only
# once every 20 times it is called, or if it has been more than 5 seconds
# since the last time the cache was actually pruned, whichever is first.
# In the future, the behavior may be customizable.
prune_throttled = function() {
# Count the number of times prune() has been called.
private$prune_throttle_counter <- private$prune_throttle_counter + 1
if (private$prune_throttle_counter > 20 ||
private$prune_last_time - as.numeric(Sys.time()) > 5)
{
self$prune()
private$prune_throttle_counter <- 0
}
},
# Prunes a single object if it exceeds max_age. If the object does not
# exceed max_age, or if the object doesn't exist, do nothing.
maybe_prune_single = function(key) {
obj <- private$cache[[key]]
if (is.null(obj)) return()
timediff <- as.numeric(Sys.time()) - obj$mtime
if (timediff > private$max_age) {
private$log(paste0("pruning single object exceeding max_age: Removing ", key))
rm(list = key, envir = private$cache)
}
},
log = function(text) {
if (is.null(private$logfile)) return()
text <- paste0(format(Sys.time(), "[%Y-%m-%d %H:%M:%OS3] DiskCache "), text)
writeLines(text, private$logfile)
}
)
)

366
R/cache-memory.R Normal file
View File

@@ -0,0 +1,366 @@
#' Create a memory cache object
#'
#' A memory cache object is a key-value store that saves the values in an
#' environment. Objects can be stored and retrieved using the \code{get()} and
#' \code{set()} methods. Objects are automatically pruned from the cache
#' according to the parameters \code{max_size}, \code{max_age}, \code{max_n},
#' and \code{evict}.
#'
#' In a \code{MemoryCache}, R objects are stored directly in the cache; they are
#' not \emph{not} serialized before being stored in the cache. This contrasts
#' with other cache types, like \code{\link{diskCache}}, where objects are
#' serialized, and the serialized object is cached. This can result in some
#' differences of behavior. For example, as long as an object is stored in a
#' MemoryCache, it will not be garbage collected.
#'
#'
#' @section Missing keys:
#' The \code{missing} and \code{exec_missing} parameters controls what happens
#' when \code{get()} is called with a key that is not in the cache (a cache
#' miss). The default behavior is to return a \code{\link{key_missing}}
#' object. This is a \emph{sentinel value} that indicates that the key was not
#' present in the cache. You can test if the returned value represents a
#' missing key by using the \code{\link{is.key_missing}} function. You can
#' also have \code{get()} return a different sentinel value, like \code{NULL}.
#' If you want to throw an error on a cache miss, you can do so by providing a
#' function for \code{missing} that takes one argument, the key, and also use
#' \code{exec_missing=TRUE}.
#'
#' When the cache is created, you can supply a value for \code{missing}, which
#' sets the default value to be returned for missing values. It can also be
#' overridden when \code{get()} is called, by supplying a \code{missing}
#' argument. For example, if you use \code{cache$get("mykey", missing =
#' NULL)}, it will return \code{NULL} if the key is not in the cache.
#'
#' If your cache is configured so that \code{get()} returns a sentinel value
#' to represent a cache miss, then \code{set} will also not allow you to store
#' the sentinel value in the cache. It will throw an error if you attempt to
#' do so.
#'
#' Instead of returning the same sentinel value each time there is cache miss,
#' the cache can execute a function each time \code{get()} encounters missing
#' key. If the function returns a value, then \code{get()} will in turn return
#' that value. However, a more common use is for the function to throw an
#' error. If an error is thrown, then \code{get()} will not return a value.
#'
#' To do this, pass a one-argument function to \code{missing}, and use
#' \code{exec_missing=TRUE}. For example, if you want to throw an error that
#' prints the missing key, you could do this:
#'
#' \preformatted{
#' diskCache(
#' missing = function(key) {
#' stop("Attempted to get missing key: ", key)
#' },
#' exec_missing = TRUE
#' )
#' }
#'
#' If you use this, the code that calls \code{get()} should be wrapped with
#' \code{\link{tryCatch}()} to gracefully handle missing keys.
#'
#' @section Cache pruning:
#'
#' Cache pruning occurs when \code{set()} is called, or it can be invoked
#' manually by calling \code{prune()}.
#'
#' When a pruning occurs, if there are any objects that are older than
#' \code{max_age}, they will be removed.
#'
#' The \code{max_size} and \code{max_n} parameters are applied to the cache as
#' a whole, in contrast to \code{max_age}, which is applied to each object
#' individually.
#'
#' If the number of objects in the cache exceeds \code{max_n}, then objects
#' will be removed from the cache according to the eviction policy, which is
#' set with the \code{evict} parameter. Objects will be removed so that the
#' number of items is \code{max_n}.
#'
#' If the size of the objects in the cache exceeds \code{max_size}, then
#' objects will be removed from the cache. Objects will be removed from the
#' cache so that the total size remains under \code{max_size}. Note that the
#' size is calculated using the size of the files, not the size of disk space
#' used by the files -- these two values can differ because of files are
#' stored in blocks on disk. For example, if the block size is 4096 bytes,
#' then a file that is one byte in size will take 4096 bytes on disk.
#'
#' Another time that objects can be removed from the cache is when
#' \code{get()} is called. If the target object is older than \code{max_age},
#' it will be removed and the cache will report it as a missing value.
#'
#' @section Eviction policies:
#'
#' If \code{max_n} or \code{max_size} are used, then objects will be removed
#' from the cache according to an eviction policy. The available eviction
#' policies are:
#'
#' \describe{
#' \item{\code{"lru"}}{
#' Least Recently Used. The least recently used objects will be removed.
#' This uses the filesystem's atime property. Some filesystems do not
#' support atime, or have a very low atime resolution. The DiskCache will
#' check for atime support, and if the filesystem does not support atime,
#' a warning will be issued and the "fifo" policy will be used instead.
#' }
#' \item{\code{"fifo"}}{
#' First-in-first-out. The oldest objects will be removed.
#' }
#' }
#'
#' @section Methods:
#'
#' A disk cache object has the following methods:
#'
#' \describe{
#' \item{\code{get(key, missing, exec_missing)}}{
#' Returns the value associated with \code{key}. If the key is not in the
#' cache, then it returns the value specified by \code{missing} or,
#' \code{missing} is a function and \code{exec_missing=TRUE}, then
#' executes \code{missing}. The function can throw an error or return the
#' value. If either of these parameters are specified here, then they
#' will override the defaults that were set when the DiskCache object was
#' created. See section Missing Keys for more information.
#' }
#' \item{\code{set(key, value)}}{
#' Stores the \code{key}-\code{value} pair in the cache.
#' }
#' \item{\code{exists(key)}}{
#' Returns \code{TRUE} if the cache contains the key, otherwise
#' \code{FALSE}.
#' }
#' \item{\code{size()}}{
#' Returns the number of items currently in the cache.
#' }
#' \item{\code{keys()}}{
#' Returns a character vector of all keys currently in the cache.
#' }
#' \item{\code{reset()}}{
#' Clears all objects from the cache.
#' }
#' \item{\code{destroy()}}{
#' Clears all objects in the cache, and removes the cache directory from
#' disk.
#' }
#' \item{\code{prune()}}{
#' Prunes the cache, using the parameters specified by \code{max_size},
#' \code{max_age}, \code{max_n}, and \code{evict}.
#' }
#' }
#'
#' @inheritParams diskCache
#'
#' @export
memoryCache <- function(
max_size = 10 * 1024 ^ 2,
max_age = Inf,
max_n = Inf,
evict = c("lru", "fifo"),
missing = key_missing(),
exec_missing = FALSE,
logfile = NULL)
{
MemoryCache$new(max_size, max_age, max_n, evict, missing, exec_missing, logfile)
}
MemoryCache <- R6Class("MemoryCache",
public = list(
initialize = function(
max_size = 10 * 1024 ^ 2,
max_age = Inf,
max_n = Inf,
evict = c("lru", "fifo"),
missing = key_missing(),
exec_missing = FALSE,
logfile = NULL)
{
if (exec_missing && (!is.function(missing) || length(formals(missing)) == 0)) {
stop("When `exec_missing` is true, `missing` must be a function that takes one argument.")
}
if (!is.numeric(max_size)) stop("max_size must be a number. Use `Inf` for no limit.")
if (!is.numeric(max_age)) stop("max_age must be a number. Use `Inf` for no limit.")
if (!is.numeric(max_n)) stop("max_n must be a number. Use `Inf` for no limit.")
private$cache <- new.env(parent = emptyenv())
private$max_size <- max_size
private$max_age <- max_age
private$max_n <- max_n
private$evict <- match.arg(evict)
private$missing <- missing
private$exec_missing <- exec_missing
private$logfile <- logfile
},
get = function(key, missing = private$missing, exec_missing = private$exec_missing) {
private$log(paste0('get: key "', key, '"'))
validate_key(key)
private$maybe_prune_single(key)
if (!self$exists(key)) {
private$log(paste0('get: key "', key, '" is missing'))
if (exec_missing) {
if (!is.function(missing) || length(formals(missing)) == 0) {
stop("When `exec_missing` is true, `missing` must be a function that takes one argument.")
}
return(missing(key))
} else {
return(missing)
}
}
private$log(paste0('get: key "', key, '" found'))
value <- private$cache[[key]]$value
value
},
set = function(key, value) {
private$log(paste0('set: key "', key, '"'))
validate_key(key)
time <- as.numeric(Sys.time())
# Only record size if we're actually using max_size for pruning.
if (is.finite(private$max_size)) {
# Reported size is rough! See ?object.size.
size <- as.numeric(object.size(value))
} else {
size <- NULL
}
private$cache[[key]] <- list(
key = key,
value = value,
size = size,
mtime = time,
atime = time
)
self$prune()
invisible(self)
},
exists = function(key) {
validate_key(key)
# Faster than `exists(key, envir = private$cache, inherits = FALSE)
!is.null(private$cache[[key]])
},
keys = function() {
ls(private$cache, sorted = FALSE) # Faster with sorted=FALSE
},
remove = function(key) {
private$log(paste0('remove: key "', key, '"'))
validate_key(key)
rm(list = key, envir = private$cache)
invisible(self)
},
reset = function() {
private$log(paste0('reset'))
rm(list = self$keys(), envir = private$cache)
invisible(self)
},
prune = function() {
private$log(paste0('prune'))
info <- private$object_info()
# 1. Remove any objects where the age exceeds max age.
if (is.finite(private$max_age)) {
time <- as.numeric(Sys.time())
timediff <- time - info$mtime
rm_idx <- timediff > private$max_age
if (any(rm_idx)) {
private$log(paste0("prune max_age: Removing ", paste(info$key[rm_idx], collapse = ", ")))
rm(list = info$key[rm_idx], envir = private$cache)
info <- info[!rm_idx, ]
}
}
# Sort objects by priority, according to eviction policy. The sorting is
# done in a function which can be called multiple times but only does
# the work the first time.
info_is_sorted <- FALSE
ensure_info_is_sorted <- function() {
if (info_is_sorted) return()
if (private$evict == "lru") {
info <<- info[order(info$atime, decreasing = TRUE), ]
} else if (private$evict == "fifo") {
info <<- info[order(info$mtime, decreasing = TRUE), ]
} else {
stop('Unknown eviction policy "', private$evict, '"')
}
info_is_sorted <<- TRUE
}
# 2. Remove objects if there are too many.
if (is.finite(private$max_n) && nrow(info) > private$max_n) {
ensure_info_is_sorted()
rm_idx <- seq_len(nrow(info)) > private$max_n
private$log(paste0("prune max_n: Removing ", paste(info$key[rm_idx], collapse = ", ")))
rm(list = info$key[rm_idx], envir = private$cache)
info <- info[!rm_idx, ]
}
# 3. Remove objects if cache is too large.
if (is.finite(private$max_size) && sum(info$size) > private$max_size) {
ensure_info_is_sorted()
cum_size <- cumsum(info$size)
rm_idx <- cum_size > private$max_size
private$log(paste0("prune max_size: Removing ", paste(info$key[rm_idx], collapse = ", ")))
rm(list = info$key[rm_idx], envir = private$cache)
info <- info[!rm_idx, ]
}
invisible(self)
},
size = function() {
length(self$keys())
}
),
private = list(
cache = NULL,
max_age = NULL,
max_size = NULL,
max_n = NULL,
evict = NULL,
missing = NULL,
exec_missing = NULL,
logfile = NULL,
# Prunes a single object if it exceeds max_age. If the object does not
# exceed max_age, or if the object doesn't exist, do nothing.
maybe_prune_single = function(key) {
if (!is.finite(private$max_age)) return()
obj <- private$cache[[key]]
if (is.null(obj)) return()
timediff <- as.numeric(Sys.time()) - obj$mtime
if (timediff > private$max_age) {
private$log(paste0("pruning single object exceeding max_age: Removing ", key))
rm(list = key, envir = private$cache)
}
},
object_info = function() {
keys <- ls(private$cache, sorted = FALSE)
data.frame(
key = keys,
size = vapply(keys, function(key) private$cache[[key]]$size, 0),
mtime = vapply(keys, function(key) private$cache[[key]]$mtime, 0),
atime = vapply(keys, function(key) private$cache[[key]]$atime, 0),
stringsAsFactors = FALSE
)
},
log = function(text) {
if (is.null(private$logfile)) return()
text <- paste0(format(Sys.time(), "[%Y-%m-%d %H:%M:%OS3] MemoryCache "), text)
writeLines(text, private$logfile)
}
)
)

33
R/cache-utils.R Normal file
View File

@@ -0,0 +1,33 @@
#' A Key Missing object
#'
#' A \code{key_missing} object represents a cache miss.
#'
#' @param x An object to test.
#'
#' @seealso \code{\link{diskCache}}, \code{\link{memoryCache}}.
#'
#' @export
key_missing <- function() {
structure(list(), class = "key_missing")
}
#' @rdname key_missing
#' @export
is.key_missing <- function(x) {
inherits(x, "key_missing")
}
#' @export
print.key_missing <- function(x, ...) {
cat("<Key Missing>\n")
}
validate_key <- function(key) {
if (!is.character(key) || length(key) != 1 || nchar(key) == 0) {
stop("Invalid key: key must be single non-empty string.")
}
if (grepl("[^a-z0-9]", key)) {
stop("Invalid key: ", key, ". Only lowercase letters and numbers are allowed.")
}
}

627
R/conditions.R Normal file
View File

@@ -0,0 +1,627 @@
#' Stack trace manipulation functions
#'
#' Advanced (borderline internal) functions for capturing, printing, and
#' manipulating stack traces.
#'
#' @return \code{printError} and \code{printStackTrace} return
#' \code{invisible()}. The other functions pass through the results of
#' \code{expr}.
#'
#' @examples
#' # Keeps tryCatch and withVisible related calls off the
#' # pretty-printed stack trace
#'
#' visibleFunction1 <- function() {
#' stop("Kaboom!")
#' }
#'
#' visibleFunction2 <- function() {
#' visibleFunction1()
#' }
#'
#' hiddenFunction <- function(expr) {
#' expr
#' }
#'
#' # An example without ..stacktraceon/off.. manipulation.
#' # The outer "try" is just to prevent example() from stopping.
#' try({
#' # The withLogErrors call ensures that stack traces are captured
#' # and that errors that bubble up are logged using warning().
#' withLogErrors({
#' # tryCatch and withVisible are just here to add some noise to
#' # the stack trace.
#' tryCatch(
#' withVisible({
#' hiddenFunction(visibleFunction2())
#' })
#' )
#' })
#' })
#'
#' # Now the same example, but with ..stacktraceon/off.. to hide some
#' # of the less-interesting bits (tryCatch and withVisible).
#' ..stacktraceoff..({
#' try({
#' withLogErrors({
#' tryCatch(
#' withVisible(
#' hiddenFunction(
#' ..stacktraceon..(visibleFunction2())
#' )
#' )
#' )
#' })
#' })
#' })
#'
#'
#' @name stacktrace
#' @rdname stacktrace
#' @keywords internal
NULL
getCallNames <- function(calls) {
sapply(calls, function(call) {
if (is.function(call[[1]])) {
"<Anonymous>"
} else if (inherits(call[[1]], "call")) {
paste0(format(call[[1]]), collapse = " ")
} else if (typeof(call[[1]]) == "promise") {
"<Promise>"
} else {
paste0(as.character(call[[1]]), collapse = " ")
}
})
}
getLocs <- function(calls) {
vapply(calls, function(call) {
srcref <- attr(call, "srcref", exact = TRUE)
if (!is.null(srcref)) {
srcfile <- attr(srcref, "srcfile", exact = TRUE)
if (!is.null(srcfile) && !is.null(srcfile$filename)) {
loc <- paste0(srcfile$filename, "#", srcref[[1]])
return(paste0(" [", loc, "]"))
}
}
return("")
}, character(1))
}
getCallCategories <- function(calls) {
vapply(calls, function(call) {
srcref <- attr(call, "srcref", exact = TRUE)
if (!is.null(srcref)) {
srcfile <- attr(srcref, "srcfile", exact = TRUE)
if (!is.null(srcfile)) {
if (!is.null(srcfile$original)) {
return("pkg")
} else {
return("user")
}
}
}
return("")
}, character(1))
}
#' @details \code{captureStackTraces} runs the given \code{expr} and if any
#' \emph{uncaught} errors occur, annotates them with stack trace info for use
#' by \code{printError} and \code{printStackTrace}. It is not necessary to use
#' \code{captureStackTraces} around the same expression as
#' \code{withLogErrors}, as the latter includes a call to the former. Note
#' that if \code{expr} contains calls (either directly or indirectly) to
#' \code{try}, or \code{tryCatch} with an error handler, stack traces therein
#' cannot be captured unless another \code{captureStackTraces} call is
#' inserted in the interior of the \code{try} or \code{tryCatch}. This is
#' because these calls catch the error and prevent it from traveling up to the
#' condition handler installed by \code{captureStackTraces}.
#'
#' @param expr The expression to wrap.
#' @rdname stacktrace
#' @export
captureStackTraces <- function(expr) {
promises::with_promise_domain(createStackTracePromiseDomain(),
expr
)
}
#' @include globals.R
.globals$deepStack <- NULL
createStackTracePromiseDomain <- function() {
# These are actually stateless, we wouldn't have to create a new one each time
# if we didn't want to. They're pretty cheap though.
d <- promises::new_promise_domain(
wrapOnFulfilled = function(onFulfilled) {
force(onFulfilled)
# Subscription time
if (deepStacksEnabled()) {
currentStack <- sys.calls()
currentParents <- sys.parents()
attr(currentStack, "parents") <- currentParents
currentDeepStack <- .globals$deepStack
}
function(...) {
# Fulfill time
if (deepStacksEnabled()) {
origDeepStack <- .globals$deepStack
.globals$deepStack <- c(currentDeepStack, list(currentStack))
on.exit(.globals$deepStack <- origDeepStack, add = TRUE)
}
withCallingHandlers(
onFulfilled(...),
error = doCaptureStack
)
}
},
wrapOnRejected = function(onRejected) {
force(onRejected)
# Subscription time
if (deepStacksEnabled()) {
currentStack <- sys.calls()
currentParents <- sys.parents()
attr(currentStack, "parents") <- currentParents
currentDeepStack <- .globals$deepStack
}
function(...) {
# Fulfill time
if (deepStacksEnabled()) {
origDeepStack <- .globals$deepStack
.globals$deepStack <- c(currentDeepStack, list(currentStack))
on.exit(.globals$deepStack <- origDeepStack, add = TRUE)
}
withCallingHandlers(
onRejected(...),
error = doCaptureStack
)
}
},
wrapSync = function(expr) {
withCallingHandlers(expr,
error = doCaptureStack
)
},
onError = doCaptureStack
)
}
deepStacksEnabled <- function() {
getOption("shiny.deepstacktrace", TRUE)
}
doCaptureStack <- function(e) {
if (is.null(attr(e, "stack.trace", exact = TRUE))) {
calls <- sys.calls()
parents <- sys.parents()
attr(calls, "parents") <- parents
attr(e, "stack.trace") <- calls
}
if (deepStacksEnabled()) {
if (is.null(attr(e, "deep.stack.trace", exact = TRUE)) && !is.null(.globals$deepStack)) {
attr(e, "deep.stack.trace") <- .globals$deepStack
}
}
stop(e)
}
#' @details \code{withLogErrors} captures stack traces and logs errors that
#' occur in \code{expr}, but does allow errors to propagate beyond this point
#' (i.e. it doesn't catch the error). The same caveats that apply to
#' \code{captureStackTraces} with regard to \code{try}/\code{tryCatch} apply
#' to \code{withLogErrors}.
#' @rdname stacktrace
#' @export
withLogErrors <- function(expr,
full = getOption("shiny.fullstacktrace", FALSE),
offset = getOption("shiny.stacktraceoffset", TRUE)) {
withCallingHandlers(
{
result <- captureStackTraces(expr)
# Handle expr being an async operation
if (promises::is.promise(result)) {
result <- promises::catch(result, function(cond) {
# Don't print shiny.silent.error (i.e. validation errors)
if (inherits(cond, "shiny.silent.error")) return()
if (isTRUE(getOption("show.error.messages"))) {
printError(cond, full = full, offset = offset)
}
})
}
result
},
error = function(cond) {
# Don't print shiny.silent.error (i.e. validation errors)
if (inherits(cond, "shiny.silent.error")) return()
if (isTRUE(getOption("show.error.messages"))) {
printError(cond, full = full, offset = offset)
}
}
)
}
#' @details \code{printError} prints the error and stack trace (if any) using
#' \code{warning(immediate.=TRUE)}. \code{printStackTrace} prints the stack
#' trace only.
#'
#' @param cond An condition object (generally, an error).
#' @param full If \code{TRUE}, then every element of \code{sys.calls()} will be
#' included in the stack trace. By default (\code{FALSE}), calls that Shiny
#' deems uninteresting will be hidden.
#' @param offset If \code{TRUE} (the default), srcrefs will be reassigned from
#' the calls they originated from, to the destinations of those calls. If
#' you're used to stack traces from other languages, this feels more
#' intuitive, as the definition of the function indicated in the call and the
#' location specified by the srcref match up. If \code{FALSE}, srcrefs will be
#' left alone (traditional R treatment where the srcref is of the callsite).
#' @rdname stacktrace
#' @export
printError <- function(cond,
full = getOption("shiny.fullstacktrace", FALSE),
offset = getOption("shiny.stacktraceoffset", TRUE)) {
warning(call. = FALSE, immediate. = TRUE, sprintf("Error in %s: %s",
getCallNames(list(conditionCall(cond))), conditionMessage(cond)))
printStackTrace(cond, full = full, offset = offset)
}
#' @rdname stacktrace
#' @export
printStackTrace <- function(cond,
full = getOption("shiny.fullstacktrace", FALSE),
offset = getOption("shiny.stacktraceoffset", TRUE)) {
should_drop <- !full
should_strip <- !full
should_prune <- !full
stackTraceCalls <- c(
attr(cond, "deep.stack.trace", exact = TRUE),
list(attr(cond, "stack.trace", exact = TRUE))
)
stackTraceParents <- lapply(stackTraceCalls, attr, which = "parents", exact = TRUE)
stackTraceCallNames <- lapply(stackTraceCalls, getCallNames)
stackTraceCalls <- lapply(stackTraceCalls, offsetSrcrefs, offset = offset)
# Use dropTrivialFrames logic to remove trailing bits (.handleSimpleError, h)
if (should_drop) {
# toKeep is a list of logical vectors, of which elements (stack frames) to keep
toKeep <- lapply(stackTraceCallNames, dropTrivialFrames)
# We apply the list of logical vector indices to each data structure
stackTraceCalls <- mapply(stackTraceCalls, FUN = `[`, toKeep, SIMPLIFY = FALSE)
stackTraceCallNames <- mapply(stackTraceCallNames, FUN = `[`, toKeep, SIMPLIFY = FALSE)
stackTraceParents <- mapply(stackTraceParents, FUN = `[`, toKeep, SIMPLIFY = FALSE)
}
delayedAssign("all_true", {
# List of logical vectors that are all TRUE, the same shape as
# stackTraceCallNames. Delay the evaluation so we don't create it unless
# we need it, but if we need it twice then we don't pay to create it twice.
lapply(stackTraceCallNames, function(st) {
rep_len(TRUE, length(st))
})
})
# stripStackTraces and lapply(stackTraceParents, pruneStackTrace) return lists
# of logical vectors. Use mapply(FUN = `&`) to boolean-and each pair of the
# logical vectors.
toShow <- mapply(
if (should_strip) stripStackTraces(stackTraceCallNames) else all_true,
if (should_prune) lapply(stackTraceParents, pruneStackTrace) else all_true,
FUN = `&`,
SIMPLIFY = FALSE
)
dfs <- mapply(seq_along(stackTraceCalls), rev(stackTraceCalls), rev(stackTraceCallNames), rev(toShow), FUN = function(i, calls, nms, index) {
st <- data.frame(
num = rev(which(index)),
call = rev(nms[index]),
loc = rev(getLocs(calls[index])),
category = rev(getCallCategories(calls[index])),
stringsAsFactors = FALSE
)
if (i != 1) {
message("From earlier call:")
}
if (nrow(st) == 0) {
message(" [No stack trace available]")
} else {
width <- floor(log10(max(st$num))) + 1
formatted <- paste0(
" ",
formatC(st$num, width = width),
": ",
mapply(paste0(st$call, st$loc), st$category, FUN = function(name, category) {
if (category == "pkg")
crayon::silver(name)
else if (category == "user")
crayon::blue$bold(name)
else
crayon::white(name)
}),
"\n"
)
cat(file = stderr(), formatted, sep = "")
}
st
}, SIMPLIFY = FALSE)
invisible()
}
#' @details \code{extractStackTrace} takes a list of calls (e.g. as returned
#' from \code{conditionStackTrace(cond)}) and returns a data frame with one
#' row for each stack frame and the columns \code{num} (stack frame number),
#' \code{call} (a function name or similar), and \code{loc} (source file path
#' and line number, if available). It was deprecated after shiny 1.0.5 because
#' it doesn't support deep stack traces.
#' @rdname stacktrace
#' @export
extractStackTrace <- function(calls,
full = getOption("shiny.fullstacktrace", FALSE),
offset = getOption("shiny.stacktraceoffset", TRUE)) {
shinyDeprecated(NULL,
"extractStackTrace is deprecated. Please contact the Shiny team if you were using this functionality.",
version = "1.0.5")
srcrefs <- getSrcRefs(calls)
if (offset) {
# Offset calls vs. srcrefs by 1 to make them more intuitive.
# E.g. for "foo [bar.R:10]", line 10 of bar.R will be part of
# the definition of foo().
srcrefs <- c(utils::tail(srcrefs, -1), list(NULL))
}
calls <- setSrcRefs(calls, srcrefs)
callnames <- getCallNames(calls)
# Hide and show parts of the callstack based on ..stacktrace(on|off)..
if (full) {
toShow <- rep.int(TRUE, length(calls))
} else {
# Remove stop(), .handleSimpleError(), and h() calls from the end of
# the calls--they don't add any helpful information. But only remove
# the last *contiguous* block of them, and then, only if they are the
# last thing in the calls list.
hideable <- callnames %in% c("stop", ".handleSimpleError", "h")
# What's the last that *didn't* match stop/.handleSimpleError/h?
lastGoodCall <- max(which(!hideable))
toRemove <- length(calls) - lastGoodCall
# But don't remove more than 5 levels--that's an indication we might
# have gotten it wrong, I guess
if (toRemove > 0 && toRemove < 5) {
calls <- utils::head(calls, -toRemove)
callnames <- utils::head(callnames, -toRemove)
}
# This uses a ref-counting scheme. It might make sense to switch this
# to a toggling scheme, so the most recent ..stacktrace(on|off)..
# directive wins, regardless of what came before it.
# Also explicitly remove ..stacktraceon.. because it can appear with
# score > 0 but still should never be shown.
score <- rep.int(0, length(callnames))
score[callnames == "..stacktraceoff.."] <- -1
score[callnames == "..stacktraceon.."] <- 1
toShow <- (1 + cumsum(score)) > 0 & !(callnames %in% c("..stacktraceon..", "..stacktraceoff..", "..stacktracefloor.."))
# doTryCatch, tryCatchOne, and tryCatchList are not informative--they're
# just internals for tryCatch
toShow <- toShow & !(callnames %in% c("doTryCatch", "tryCatchOne", "tryCatchList"))
}
calls <- calls[toShow]
calls <- rev(calls) # Show in traceback() order
index <- rev(which(toShow))
width <- floor(log10(max(index))) + 1
data.frame(
num = index,
call = getCallNames(calls),
loc = getLocs(calls),
category = getCallCategories(calls),
stringsAsFactors = FALSE
)
}
stripStackTraces <- function(stackTraces, values = FALSE) {
score <- 1L # >=1: show, <=0: hide
lapply(seq_along(stackTraces), function(i) {
res <- stripOneStackTrace(stackTraces[[i]], i != 1, score)
score <<- res$score
toShow <- as.logical(res$trace)
if (values) {
as.character(stackTraces[[i]][toShow])
} else {
as.logical(toShow)
}
})
}
stripOneStackTrace <- function(stackTrace, truncateFloor, startingScore) {
prefix <- logical(0)
if (truncateFloor) {
indexOfFloor <- utils::tail(which(stackTrace == "..stacktracefloor.."), 1)
if (length(indexOfFloor)) {
stackTrace <- stackTrace[(indexOfFloor+1L):length(stackTrace)]
prefix <- rep_len(FALSE, indexOfFloor)
}
}
if (length(stackTrace) == 0) {
return(list(score = startingScore, character(0)))
}
score <- rep.int(0L, length(stackTrace))
score[stackTrace == "..stacktraceon.."] <- 1L
score[stackTrace == "..stacktraceoff.."] <- -1L
score <- startingScore + cumsum(score)
toShow <- score > 0 & !(stackTrace %in% c("..stacktraceon..", "..stacktraceoff..", "..stacktracefloor.."))
list(score = utils::tail(score, 1), trace = c(prefix, toShow))
}
# Given sys.parents() (which corresponds to sys.calls()), return a logical index
# that prunes each subtree so that only the final branch remains. The result,
# when applied to sys.calls(), is a linear list of calls without any "wrapper"
# functions like tryCatch, try, with, hybrid_chain, etc. While these are often
# part of the active call stack, they rarely are helpful when trying to identify
# a broken bit of code.
pruneStackTrace <- function(parents) {
# Detect nodes that are not the last child. This is necessary, but not
# sufficient; we also need to drop nodes that are the last child, but one of
# their ancestors is not.
is_dupe <- duplicated(parents, fromLast = TRUE)
# The index of the most recently seen node that was actually kept instead of
# dropped.
current_node <- 0
# Loop over the parent indices. Anything that is not parented by current_node
# (a.k.a. last-known-good node), or is a dupe, can be discarded. Anything that
# is kept becomes the new current_node.
include <- vapply(seq_along(parents), function(i) {
if (!is_dupe[[i]] && parents[[i]] == current_node) {
current_node <<- i
TRUE
} else {
FALSE
}
}, FUN.VALUE = logical(1))
include
}
dropTrivialFrames <- function(callnames) {
# Remove stop(), .handleSimpleError(), and h() calls from the end of
# the calls--they don't add any helpful information. But only remove
# the last *contiguous* block of them, and then, only if they are the
# last thing in the calls list.
hideable <- callnames %in% c(".handleSimpleError", "h", "base$wrapOnFulfilled")
# What's the last that *didn't* match stop/.handleSimpleError/h?
lastGoodCall <- max(which(!hideable))
toRemove <- length(callnames) - lastGoodCall
c(
rep_len(TRUE, length(callnames) - toRemove),
rep_len(FALSE, toRemove)
)
}
offsetSrcrefs <- function(calls, offset = TRUE) {
if (offset) {
srcrefs <- getSrcRefs(calls)
# Offset calls vs. srcrefs by 1 to make them more intuitive.
# E.g. for "foo [bar.R:10]", line 10 of bar.R will be part of
# the definition of foo().
srcrefs <- c(utils::tail(srcrefs, -1), list(NULL))
calls <- setSrcRefs(calls, srcrefs)
}
calls
}
#' @details \code{formatStackTrace} is similar to \code{extractStackTrace}, but
#' it returns a preformatted character vector instead of a data frame. It was
#' deprecated after shiny 1.0.5 because it doesn't support deep stack traces.
#' @param indent A string to prefix every line of the stack trace.
#' @rdname stacktrace
#' @export
formatStackTrace <- function(calls, indent = " ",
full = getOption("shiny.fullstacktrace", FALSE),
offset = getOption("shiny.stacktraceoffset", TRUE)) {
shinyDeprecated(NULL,
"extractStackTrace is deprecated. Please contact the Shiny team if you were using this functionality.",
version = "1.0.5")
st <- extractStackTrace(calls, full = full, offset = offset)
if (nrow(st) == 0) {
return(character(0))
}
width <- floor(log10(max(st$num))) + 1
paste0(
indent,
formatC(st$num, width = width),
": ",
mapply(paste0(st$call, st$loc), st$category, FUN = function(name, category) {
if (category == "pkg")
crayon::silver(name)
else if (category == "user")
crayon::blue$bold(name)
else
crayon::white(name)
})
)
}
getSrcRefs <- function(calls) {
lapply(calls, function(call) {
attr(call, "srcref", exact = TRUE)
})
}
setSrcRefs <- function(calls, srcrefs) {
mapply(function(call, srcref) {
structure(call, srcref = srcref)
}, calls, srcrefs)
}
stripStackTrace <- function(cond) {
conditionStackTrace(cond) <- NULL
}
#' @details \code{conditionStackTrace} and \code{conditionStackTrace<-} are
#' accessor functions for getting/setting stack traces on conditions.
#'
#' @param cond A condition that may have previously been annotated by
#' \code{captureStackTraces} (or \code{withLogErrors}).
#' @rdname stacktrace
#' @export
conditionStackTrace <- function(cond) {
attr(cond, "stack.trace", exact = TRUE)
}
#' @param value The stack trace value to assign to the condition.
#' @rdname stacktrace
#' @export
`conditionStackTrace<-` <- function(cond, value) {
attr(cond, "stack.trace") <- value
invisible(cond)
}
#' @details The two functions \code{..stacktraceon..} and
#' \code{..stacktraceoff..} have no runtime behavior during normal execution;
#' they exist only to create artifacts on the stack trace (sys.call()) that
#' instruct the stack trace pretty printer what parts of the stack trace are
#' interesting or not. The initial state is 1 and we walk from the outermost
#' call inwards. Each ..stacktraceoff.. decrements the state by one, and each
#' ..stacktraceon.. increments the state by one. Any stack trace frame whose
#' value is less than 1 is hidden, and finally, the ..stacktraceon.. and
#' ..stacktraceoff.. calls themselves are hidden too.
#'
#' @rdname stacktrace
#' @export
..stacktraceon.. <- function(expr) expr
#' @rdname stacktrace
#' @export
..stacktraceoff.. <- function(expr) expr
..stacktracefloor.. <- function(expr) expr

157
R/diagnose.R Normal file
View File

@@ -0,0 +1,157 @@
# Analyze an R file for possible extra or missing commas. Returns FALSE if any
# problems detected, TRUE otherwise.
diagnoseCode <- function(path = NULL, text = NULL) {
if (!xor(is.null(path), is.null(text))) {
stop("Must specify `path` or `text`, but not both.")
}
if (!is.null(path)) {
tokens <- sourcetools::tokenize_file(path)
} else {
tokens <- sourcetools::tokenize_string(text)
}
find_scopes <- function(tokens) {
# Strip whitespace and comments
tokens <- tokens[!(tokens$type %in% c("whitespace", "comment")),]
# Replace various types of things with "value"
tokens$type[tokens$type %in% c("string", "number", "symbol", "keyword")] <- "value"
# Record types for close and open brace/bracket/parens, and commas
brace_idx <- tokens$value %in% c("(", ")", "{", "}", "[", "]", ",")
tokens$type[brace_idx] <- tokens$value[brace_idx]
# Stack-related function for recording scope. Starting scope is "{"
stack <- "{"
push <- function(x) {
stack <<- c(stack, x)
}
pop <- function() {
if (length(stack) == 1) {
# Stack underflow, but we need to keep going
return(NA_character_)
}
res <- stack[length(stack)]
stack <<- stack[-length(stack)]
res
}
peek <- function() {
stack[length(stack)]
}
# First, establish a scope for each token. For opening and closing
# braces/brackets/parens, the scope at that location is the *surrounding*
# scope, not the new scope created by the brace/bracket/paren.
for (i in seq_len(nrow(tokens))) {
value <- tokens$value[i]
tokens$scope[i] <- peek()
if (value %in% c("{", "(", "[")) {
push(value)
} else if (value == "}") {
if (!identical(pop(), "{"))
tokens$err[i] <- "unmatched_brace"
# For closing brace/paren/bracket, get the scope after popping
tokens$scope[i] <- peek()
} else if (value == ")") {
if (!identical(pop(), "("))
tokens$err[i] <- "unmatched_paren"
tokens$scope[i] <- peek()
} else if (value == "]") {
if (!identical(pop(), "["))
tokens$err[i] <- "unmatched_bracket"
tokens$scope[i] <- peek()
}
}
tokens
}
check_commas <- function(tokens) {
# Find extra and missing commas
tokens$err <- mapply(
tokens$type,
c("", tokens$type[-length(tokens$type)]),
c(tokens$type[-1], ""),
tokens$scope,
tokens$err,
SIMPLIFY = FALSE,
FUN = function(type, prevType, nextType, scope, err) {
# If an error was already found, just return it. This could have
# happened in the brace/paren/bracket matching phase.
if (!is.na(err)) {
return(err)
}
if (scope == "(") {
if (type == "," &&
(prevType == "(" || prevType == "," || nextType == ")"))
{
return("extra_comma")
}
if ((prevType == ")" && type == "value") ||
(prevType == "value" && type == "value")) {
return("missing_comma")
}
}
NA_character_
}
)
tokens
}
tokens$err <- NA_character_
tokens <- find_scopes(tokens)
tokens <- check_commas(tokens)
# No errors found
if (all(is.na(tokens$err))) {
return(TRUE)
}
# If we got here, errors were found; print messages.
if (!is.null(path)) {
lines <- readLines(path)
} else {
lines <- strsplit(text, "\n")[[1]]
}
# Print out the line of code with the error, and point to the column with
# the error.
show_code_error <- function(msg, lines, row, col) {
message(paste0(
msg, "\n",
row, ":", lines[row], "\n",
paste0(rep.int(" ", nchar(as.character(row)) + 1), collapse = ""),
gsub(perl = TRUE, "[^\\s]", " ", substr(lines[row], 1, col-1)), "^"
))
}
err_idx <- which(!is.na(tokens$err))
msg <- ""
for (i in err_idx) {
row <- tokens$row[i]
col <- tokens$column[i]
err <- tokens$err[i]
if (err == "missing_comma") {
show_code_error("Possible missing comma at:", lines, row, col)
} else if (err == "extra_comma") {
show_code_error("Possible extra comma at:", lines, row, col)
} else if (err == "unmatched_brace") {
show_code_error("Possible unmatched '}' at:", lines, row, col)
} else if (err == "unmatched_paren") {
show_code_error("Possible unmatched ')' at:", lines, row, col)
} else if (err == "unmatched_bracket") {
show_code_error("Possible unmatched ']' at:", lines, row, col)
}
}
return(FALSE)
}

View File

@@ -20,6 +20,18 @@
# form upload, i.e. traditional HTTP POST-based file upload) doesn't work with
# the websockets package's HTTP server at the moment.
# @description Returns a file's extension, with a leading dot, if one can be
# found. A valid extension contains only alphanumeric characters. If there is
# no extension, or if it contains non-alphanumeric characters, an empty
# string is returned.
# @param x character vector giving file paths.
# @return The extension of \code{x}, with a leading dot, if one was found.
# Otherwise, an empty character vector.
maybeGetExtension <- function(x) {
ext <- tools::file_ext(x)
ifelse(ext == "", ext, paste0(".", ext))
}
FileUploadOperation <- R6Class(
'FileUploadOperation',
portable = FALSE,
@@ -52,8 +64,9 @@ FileUploadOperation <- R6Class(
.currentFileInfo <<- file
.pendingFileInfos <<- tail(.pendingFileInfos, -1)
filename <- file.path(.dir, as.character(length(.files$name)))
row <- data.frame(name=file$name, size=file$size, type=file$type,
fileBasename <- basename(.currentFileInfo$name)
filename <- file.path(.dir, paste0(as.character(length(.files$name)), maybeGetExtension(fileBasename)))
row <- data.frame(name=fileBasename, size=file$size, type=file$type,
datapath=filename, stringsAsFactors=FALSE)
if (length(.files$name) == 0)
@@ -94,7 +107,7 @@ FileUploadContext <- R6Class(
},
createUploadOperation = function(fileInfos) {
while (TRUE) {
id <- paste(as.raw(p_runif(12, min=0, max=0xFF)), collapse='')
id <- createUniqueId(12)
private$ids <- c(private$ids, id)
dir <- file.path(private$basedir, id)
if (!dir.create(dir))

View File

@@ -5,7 +5,7 @@
# R's lazy-loading package scheme causes the private seed to be cached in the
# package itself, making our PRNG completely deterministic. This line resets
# the private seed during load.
withPrivateSeed(reinitializeSeed())
withPrivateSeed(set.seed(NULL))
}
.onAttach <- function(libname, pkgname) {

View File

@@ -1,5 +1,11 @@
writeReactLog <- function(file=stdout()) {
cat(toJSON(.graphStack$as_list(), pretty=TRUE), file=file)
writeReactLog <- function(file=stdout(), sessionToken = NULL) {
log <- .graphStack$as_list()
if (!is.null(sessionToken)) {
log <- Filter(function(x) {
is.null(x$session) || identical(x$session, sessionToken)
}, log)
}
cat(toJSON(log, pretty=TRUE), file=file)
}
#' Reactive Log Visualizer
@@ -35,28 +41,36 @@ writeReactLog <- function(file=stdout()) {
#' enabled, it's possible for any user of your app to see at least some
#' of the source code of your reactive expressions and observers.
#'
#' @param time A boolean that specifies whether or not to display the
#' time that each reactive.
#' @export
showReactLog <- function() {
utils::browseURL(renderReactLog())
showReactLog <- function(time = TRUE) {
utils::browseURL(renderReactLog(time = as.logical(time)))
}
renderReactLog <- function() {
renderReactLog <- function(sessionToken = NULL, time = TRUE) {
templateFile <- system.file('www/reactive-graph.html', package='shiny')
html <- paste(readLines(templateFile, warn=FALSE), collapse='\r\n')
tc <- textConnection(NULL, 'w')
on.exit(close(tc))
writeReactLog(tc)
writeReactLog(tc, sessionToken)
cat('\n', file=tc)
flush(tc)
html <- sub('__DATA__', paste(textConnectionValue(tc), collapse='\r\n'), html, fixed=TRUE)
html <- sub('__TIME__', paste0('"', time, '"'), html, fixed=TRUE)
file <- tempfile(fileext = '.html')
writeLines(html, file)
return(file)
}
.graphAppend <- function(logEntry, domain = getDefaultReactiveDomain()) {
if (isTRUE(getOption('shiny.reactlog')))
.graphStack$push(logEntry)
if (isTRUE(getOption('shiny.reactlog'))) {
sessionToken <- if (is.null(domain)) NULL else domain$token
.graphStack$push(c(logEntry, list(
session = sessionToken,
time = as.numeric(Sys.time())
)))
}
if (!is.null(domain)) {
domain$reactlog(logEntry)
@@ -74,7 +88,7 @@ renderReactLog <- function() {
.graphCreateContext <- function(id, label, type, prevId, domain) {
.graphAppend(list(
action='ctx', id=id, label=paste(label, collapse='\n'),
srcref=attr(label, "srcref"), srcfile=attr(label, "srcfile"),
srcref=as.vector(attr(label, "srcref")), srcfile=attr(label, "srcfile"),
type=type, prevId=prevId
), domain = domain)
}

95
R/history.R Normal file
View File

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

View File

@@ -6,13 +6,18 @@
#' URL.
#'
#' @param dependency A single HTML dependency object, created using
#' \code{\link{htmlDependency}}. If the \code{src} value is named, then
#' \code{href} and/or \code{file} names must be present.
#' \code{\link[htmltools]{htmlDependency}}. If the \code{src} value is named,
#' then \code{href} and/or \code{file} names must be present.
#' @param scrubFile If TRUE (the default), remove \code{src$file} for the
#' dependency. This prevents the local file path from being sent to the client
#' when dynamic web dependencies are used. If FALSE, don't remove
#' \code{src$file}. Setting it to FALSE should be needed only in very unusual
#' cases.
#'
#' @return A single HTML dependency object that has an \code{href}-named element
#' in its \code{src}.
#' @export
createWebDependency <- function(dependency) {
createWebDependency <- function(dependency, scrubFile = TRUE) {
if (is.null(dependency))
return(NULL)
@@ -25,5 +30,27 @@ createWebDependency <- function(dependency) {
dependency$src$href <- prefix
}
# Don't leak local file path to client
if (scrubFile)
dependency$src$file <- NULL
return(dependency)
}
# Given a Shiny tag object, process singletons and dependencies. Returns a list
# with rendered HTML and dependency objects.
processDeps <- function(tags, session) {
ui <- takeSingletons(tags, session$singletons, desingleton=FALSE)$ui
ui <- surroundSingletons(ui)
dependencies <- lapply(
resolveDependencies(findDependencies(ui)),
createWebDependency
)
names(dependencies) <- NULL
list(
html = doRenderTags(ui),
deps = dependencies
)
}

View File

@@ -4,4 +4,5 @@
#' @export tag tagAppendAttributes tagAppendChild tagAppendChildren tagList tags tagSetChildren withTags
#' @export validateCssUnit
#' @export knit_print.html knit_print.shiny.tag knit_print.shiny.tag.list
#' @export htmlTemplate suppressDependencies
NULL

View File

@@ -91,7 +91,10 @@ hoverOpts <- function(id = NULL, delay = 300,
#' \code{\link{plotOutput}}.
#'
#' @param id Input value name. For example, if the value is \code{"plot_brush"},
#' then the coordinates will be available as \code{input$plot_brush}.
#' then the coordinates will be available as \code{input$plot_brush}. Multiple
#' \code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
#' value; brushing one image or plot will cause any other brushes with the
#' same \code{id} to disappear.
#' @param fill Fill color of the brush.
#' @param stroke Outline color of the brush.
#' @param opacity Opacity of the brush

View File

@@ -249,14 +249,20 @@ nearPoints <- function(df, coordinfo, xvar = NULL, yvar = NULL,
x <- asNumber(df[[xvar]])
y <- asNumber(df[[yvar]])
# Get the pixel coordinates of the point
coordPx <- scaleCoords(coordinfo$x, coordinfo$y, coordinfo)
# Get the coordinates of the point (in img pixel coordinates)
point_img <- scaleCoords(coordinfo$x, coordinfo$y, coordinfo)
# Get pixel coordinates of data points
dataPx <- scaleCoords(x, y, coordinfo)
# Get coordinates of data points (in img pixel coordinates)
data_img <- scaleCoords(x, y, coordinfo)
# Distances of data points to coordPx
dists <- sqrt((dataPx$x - coordPx$x) ^ 2 + (dataPx$y - coordPx$y) ^ 2)
# Get x/y distances (in css coordinates)
dist_css <- list(
x = (data_img$x - point_img$x) / coordinfo$pixelratio$x,
y = (data_img$y - point_img$y) / coordinfo$pixelratio$y
)
# Distances of data points to the target point, in css pixels.
dists <- sqrt(dist_css$x^2 + dist_css$y^2)
if (addDist)
df$dist_ <- dists
@@ -298,50 +304,56 @@ nearPoints <- function(df, coordinfo, xvar = NULL, yvar = NULL,
# The coordinfo data structure will look something like the examples below.
# For base graphics, `mapping` is empty, and there are no panelvars:
# List of 7
# $ x : num 4.37
# $ y : num 12
# $ mapping: Named list()
# $ domain :List of 4
# $ x : num 4.37
# $ y : num 12
# $ pixelratio:List of 2
# ..$ x: num 2
# ..$ y: num 2
# $ mapping : Named list()
# $ domain :List of 4
# ..$ left : num 1.36
# ..$ right : num 5.58
# ..$ bottom: num 9.46
# ..$ top : num 34.8
# $ range :List of 4
# $ range :List of 4
# ..$ left : num 58
# ..$ right : num 429
# ..$ bottom: num 226
# ..$ top : num 58
# $ log :List of 2
# $ log :List of 2
# ..$ x: NULL
# ..$ y: NULL
# $ .nonce : num 0.343
# $ .nonce : num 0.343
#
# For ggplot2, the mapping vars usually will be included, and if faceting is
# used, they will be listed as panelvars:
# List of 9
# $ x : num 3.78
# $ y : num 17.1
# $ panelvar1: int 6
# $ panelvar2: int 0
# $ mapping :List of 4
# $ x : num 3.78
# $ y : num 17.1
# $ pixelratio:List of 2
# ..$ x: num 2
# ..$ y: num 2
# $ panelvar1 : int 6
# $ panelvar2 : int 0
# $ mapping :List of 4
# ..$ x : chr "wt"
# ..$ y : chr "mpg"
# ..$ panelvar1: chr "cyl"
# ..$ panelvar2: chr "am"
# $ domain :List of 4
# $ domain :List of 4
# ..$ left : num 1.32
# ..$ right : num 5.62
# ..$ bottom: num 9.22
# ..$ top : num 35.1
# $ range :List of 4
# $ range :List of 4
# ..$ left : num 172
# ..$ right : num 300
# ..$ bottom: num 144
# ..$ top : num 28.5
# $ log :List of 2
# $ log :List of 2
# ..$ x: NULL
# ..$ y: NULL
# $ .nonce : num 0.603
# $ .nonce : num 0.603

View File

@@ -1,3 +1,33 @@
startPNG <- function(filename, width, height, res, ...) {
# If quartz is available, use png() (which will default to quartz).
# Otherwise, if the Cairo package is installed, use CairoPNG().
# Finally, if neither quartz nor Cairo, use png().
if (capabilities("aqua")) {
pngfun <- grDevices::png
} else if ((getOption('shiny.usecairo') %OR% TRUE) &&
nchar(system.file(package = "Cairo"))) {
pngfun <- Cairo::CairoPNG
} else {
pngfun <- grDevices::png
}
pngfun(filename=filename, width=width, height=height, res=res, ...)
# Call plot.new() so that even if no plotting operations are performed at
# least we have a blank background. N.B. we need to set the margin to 0
# temporarily before plot.new() because when the plot size is small (e.g.
# 200x50), we will get an error "figure margin too large", which is triggered
# by plot.new() with the default (large) margin. However, this does not
# guarantee user's code in func() will not trigger the error -- they may have
# to set par(mar = smaller_value) before they draw base graphics.
op <- graphics::par(mar = rep(0, 4))
tryCatch(
graphics::plot.new(),
finally = graphics::par(op)
)
grDevices::dev.cur()
}
#' Run a plotting function and save the output as a PNG
#'
#' This function returns the name of the PNG file that it generates. In
@@ -21,38 +51,51 @@
#' @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.
#'
#' @export
plotPNG <- function(func, filename=tempfile(fileext='.png'),
width=400, height=400, res=72, ...) {
# If quartz is available, use png() (which will default to quartz).
# Otherwise, if the Cairo package is installed, use CairoPNG().
# Finally, if neither quartz nor Cairo, use png().
if (capabilities("aqua")) {
pngfun <- grDevices::png
} else if ((getOption('shiny.usecairo') %OR% TRUE) &&
nchar(system.file(package = "Cairo"))) {
pngfun <- Cairo::CairoPNG
} else {
pngfun <- grDevices::png
}
pngfun(filename=filename, width=width, height=height, res=res, ...)
# Call plot.new() so that even if no plotting operations are performed at
# least we have a blank background. N.B. we need to set the margin to 0
# temporarily before plot.new() because when the plot size is small (e.g.
# 200x50), we will get an error "figure margin too large", which is triggered
# by plot.new() with the default (large) margin. However, this does not
# guarantee user's code in func() will not trigger the error -- they may have
# to set par(mar = smaller_value) before they draw base graphics.
op <- graphics::par(mar = rep(0, 4))
tryCatch(graphics::plot.new(), finally = graphics::par(op))
dv <- grDevices::dev.cur()
tryCatch(shinyCallingHandlers(func()), finally = grDevices::dev.off(dv))
dv <- startPNG(filename, width, height, res, ...)
on.exit(grDevices::dev.off(dv), add = TRUE)
func()
filename
}
#' @importFrom grDevices dev.set dev.cur
createGraphicsDevicePromiseDomain <- function(which = dev.cur()) {
force(which)
promises::new_promise_domain(
wrapOnFulfilled = function(onFulfilled) {
force(onFulfilled)
function(...) {
old <- dev.cur()
dev.set(which)
on.exit(dev.set(old))
onFulfilled(...)
}
},
wrapOnRejected = function(onRejected) {
force(onRejected)
function(...) {
old <- dev.cur()
dev.set(which)
on.exit(dev.set(old))
onRejected(...)
}
},
wrapSync = function(expr) {
old <- dev.cur()
dev.set(which)
on.exit(dev.set(old))
force(expr)
}
)
}

View File

@@ -11,30 +11,43 @@
#'
#' @family input elements
#' @examples
#' \dontrun{
#' # In server.R
#' output$distPlot <- renderPlot({
#' # Take a dependency on input$goButton
#' input$goButton
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' # Use isolate() to avoid dependency on input$obs
#' dist <- isolate(rnorm(input$obs))
#' hist(dist)
#' })
#' ui <- fluidPage(
#' sliderInput("obs", "Number of observations", 0, 1000, 500),
#' actionButton("goButton", "Go!"),
#' plotOutput("distPlot")
#' )
#'
#' server <- function(input, output) {
#' output$distPlot <- renderPlot({
#' # Take a dependency on input$goButton. This will run once initially,
#' # because the value changes from NULL to 0.
#' input$goButton
#'
#' # Use isolate() to avoid dependency on input$obs
#' dist <- isolate(rnorm(input$obs))
#' hist(dist)
#' })
#' }
#'
#' shinyApp(ui, server)
#'
#' # In ui.R
#' actionButton("goButton", "Go!")
#' }
#'
#' @seealso \code{\link{observeEvent}} and \code{\link{eventReactive}}
#'
#' @export
actionButton <- function(inputId, label, icon = NULL, width = NULL, ...) {
value <- restoreInput(id = inputId, default = NULL)
tags$button(id=inputId,
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
type="button",
class="btn btn-default action-button",
list(icon, label),
`data-val` = value,
list(validateIcon(icon), label),
...
)
}
@@ -42,10 +55,32 @@ actionButton <- function(inputId, label, icon = NULL, width = NULL, ...) {
#' @rdname actionButton
#' @export
actionLink <- function(inputId, label, icon = NULL, ...) {
value <- restoreInput(id = inputId, default = NULL)
tags$a(id=inputId,
href="#",
class="action-button",
list(icon, label),
`data-val` = value,
list(validateIcon(icon), label),
...
)
}
# Check that the icon parameter is valid:
# 1) Check if the user wants to actually add an icon:
# -- if icon=NULL, it means leave the icon unchanged
# -- if icon=character(0), it means don't add an icon or, more usefully,
# remove the previous icon
# 2) If so, check that the icon has the right format (this does not check whether
# it is a *real* icon - currently that would require a massive cross reference
# with the "font-awesome" and the "glyphicon" libraries)
validateIcon <- function(icon) {
if (is.null(icon) || identical(icon, character(0))) {
return(icon)
} else if (inherits(icon, "shiny.tag") && icon$name == "i") {
return(icon)
} else {
stop("Invalid icon. Use Shiny's 'icon()' function to generate a valid icon")
}
}

View File

@@ -10,9 +10,23 @@
#' @seealso \code{\link{checkboxGroupInput}}, \code{\link{updateCheckboxInput}}
#'
#' @examples
#' checkboxInput("outliers", "Show outliers", FALSE)
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' checkboxInput("somevalue", "Some value", FALSE),
#' verbatimTextOutput("value")
#' )
#' server <- function(input, output) {
#' output$value <- renderText({ input$somevalue })
#' }
#' shinyApp(ui, server)
#' }
#' @export
checkboxInput <- function(inputId, label, value = FALSE, width = NULL) {
value <- restoreInput(id = inputId, default = value)
inputTag <- tags$input(id = inputId, type="checkbox")
if (!is.null(value) && value)
inputTag$attribs$checked <- "checked"

View File

@@ -6,30 +6,85 @@
#'
#' @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. The values should be strings; other
#' types (such as logicals and numbers) will be coerced to strings.
#' @param selected The values that should be initially selected, if any.
#' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
#' @param choiceNames,choiceValues List of names and values, respectively,
#' 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
#' @seealso \code{\link{checkboxInput}}, \code{\link{updateCheckboxGroupInput}}
#'
#' @examples
#' checkboxGroupInput("variable", "Variable:",
#' c("Cylinders" = "cyl",
#' "Transmission" = "am",
#' "Gears" = "gear"))
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' checkboxGroupInput("variable", "Variables to show:",
#' c("Cylinders" = "cyl",
#' "Transmission" = "am",
#' "Gears" = "gear")),
#' tableOutput("data")
#' )
#'
#' 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) {
# resolve names
choices <- choicesWithNames(choices)
if (!is.null(selected))
selected <- validateSelected(selected, choices, inputId)
# keep backward compatibility with Shiny < 1.0.1 (see #1649)
if (is.null(choices) && is.null(choiceNames) && is.null(choiceValues)) {
choices <- character(0)
}
options <- generateOptions(inputId, choices, selected, inline)
args <- normalizeChoicesArgs(choices, choiceNames, choiceValues)
selected <- restoreInput(id = inputId, default = selected)
# default value if it's not specified
if (!is.null(selected)) selected <- as.character(selected)
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
@@ -21,82 +21,122 @@
#'
#' @inheritParams textInput
#' @param value The starting date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
#' date in the client's time zone.
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current date
#' in the client's time zone.
#' @param min The minimum allowed date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param max The maximum allowed date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param format The format of the date to display in the browser. Defaults to
#' \code{"yyyy-mm-dd"}.
#' @param startview The date range shown when the input object is first
#' clicked. Can be "month" (the default), "year", or "decade".
#' @param startview The date range shown when the input object is first clicked.
#' Can be "month" (the default), "year", or "decade".
#' @param weekstart Which day is the start of the week. Should be an integer
#' from 0 (Sunday) to 6 (Saturday).
#' @param language The language used for month and day names. Default is "en".
#' Other valid values include "bg", "ca", "cs", "da", "de", "el", "es", "fi",
#' "fr", "he", "hr", "hu", "id", "is", "it", "ja", "kr", "lt", "lv", "ms",
#' "nb", "nl", "pl", "pt", "pt-BR", "ro", "rs", "rs-latin", "ru", "sk", "sl",
#' "sv", "sw", "th", "tr", "uk", "zh-CN", and "zh-TW".
#' Other valid values include "ar", "az", "bg", "bs", "ca", "cs", "cy", "da",
#' "de", "el", "en-AU", "en-GB", "eo", "es", "et", "eu", "fa", "fi", "fo",
#' "fr-CH", "fr", "gl", "he", "hr", "hu", "hy", "id", "is", "it-CH", "it",
#' "ja", "ka", "kh", "kk", "ko", "kr", "lt", "lv", "me", "mk", "mn", "ms",
#' "nb", "nl-BE", "nl", "no", "pl", "pt-BR", "pt", "ro", "rs-latin", "rs",
#' "ru", "sk", "sl", "sq", "sr-latin", "sr", "sv", "sw", "th", "tr", "uk",
#' "vi", "zh-CN", and "zh-TW".
#' @param autoclose Whether or not to close the datepicker immediately when a
#' date is selected.
#' @param datesdisabled Which dates should be disabled. Either a Date object,
#' or a string in \code{yyyy-mm-dd} format.
#' @param daysofweekdisabled Days of the week that should be disabled. Should be
#' a integer vector with values from 0 (Sunday) to 6 (Saturday).
#'
#' @family input elements
#' @seealso \code{\link{dateRangeInput}}, \code{\link{updateDateInput}}
#'
#' @examples
#' dateInput("date", "Date:", value = "2012-02-29")
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' # Default value is the date in client's time zone
#' dateInput("date", "Date:")
#' ui <- fluidPage(
#' dateInput("date1", "Date:", value = "2012-02-29"),
#'
#' # value is always yyyy-mm-dd, even if the display format is different
#' dateInput("date", "Date:", value = "2012-02-29", format = "mm/dd/yy")
#' # Default value is the date in client's time zone
#' dateInput("date2", "Date:"),
#'
#' # Pass in a Date object
#' dateInput("date", "Date:", value = Sys.Date()-10)
#' # value is always yyyy-mm-dd, even if the display format is different
#' dateInput("date3", "Date:", value = "2012-02-29", format = "mm/dd/yy"),
#'
#' # Use different language and different first day of week
#' dateInput("date", "Date:",
#' language = "de",
#' weekstart = 1)
#' # Pass in a Date object
#' dateInput("date4", "Date:", value = Sys.Date()-10),
#'
#' # Start with decade view instead of default month view
#' dateInput("date", "Date:",
#' startview = "decade")
#' # Use different language and different first day of week
#' dateInput("date5", "Date:",
#' language = "ru",
#' weekstart = 1),
#'
#' # Start with decade view instead of default month view
#' dateInput("date6", "Date:",
#' startview = "decade"),
#'
#' # Disable Mondays and Tuesdays.
#' dateInput("date7", "Date:", daysofweekdisabled = c(1,2)),
#'
#' # Disable specific dates.
#' dateInput("date8", "Date:", value = "2012-02-29",
#' datesdisabled = c("2012-03-01", "2012-03-02"))
#' )
#'
#' shinyApp(ui, server = function(input, output) { })
#' }
#' @export
dateInput <- function(inputId, label, value = NULL, min = NULL, max = NULL,
format = "yyyy-mm-dd", startview = "month", weekstart = 0, language = "en",
width = NULL) {
format = "yyyy-mm-dd", startview = "month", weekstart = 0,
language = "en", width = NULL, autoclose = TRUE,
datesdisabled = NULL, daysofweekdisabled = NULL) {
# If value is a date object, convert it to a string with yyyy-mm-dd format
# Same for min and max
if (inherits(value, "Date")) value <- format(value, "%Y-%m-%d")
if (inherits(min, "Date")) min <- format(min, "%Y-%m-%d")
if (inherits(max, "Date")) max <- format(max, "%Y-%m-%d")
if (inherits(datesdisabled, "Date")) {
datesdisabled <- format(datesdisabled, "%Y-%m-%d")
}
attachDependencies(
tags$div(id = inputId,
class = "shiny-date-input form-group shiny-input-container",
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
value <- restoreInput(id = inputId, default = value)
controlLabel(inputId, label),
tags$input(type = "text",
# datepicker class necessary for dropdown to display correctly
class = "form-control datepicker",
`data-date-language` = language,
`data-date-weekstart` = weekstart,
`data-date-format` = format,
`data-date-start-view` = startview,
`data-min-date` = min,
`data-max-date` = max,
`data-initial-date` = value
)
tags$div(id = inputId,
class = "shiny-date-input form-group shiny-input-container",
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
controlLabel(inputId, label),
tags$input(type = "text",
class = "form-control",
`data-date-language` = language,
`data-date-week-start` = weekstart,
`data-date-format` = format,
`data-date-start-view` = startview,
`data-min-date` = min,
`data-max-date` = max,
`data-initial-date` = value,
`data-date-autoclose` = if (autoclose) "true" else "false",
`data-date-dates-disabled` =
# Ensure NULL is not sent as `{}` but as 'null'
jsonlite::toJSON(datesdisabled, null = 'null'),
`data-date-days-of-week-disabled` =
jsonlite::toJSON(daysofweekdisabled, null = 'null')
),
datePickerDependency
)
}
datePickerDependency <- htmlDependency(
"bootstrap-datepicker", "1.0.2", c(href = "shared/datepicker"),
"bootstrap-datepicker", "1.6.4", c(href = "shared/datepicker"),
script = "js/bootstrap-datepicker.min.js",
stylesheet = "css/datepicker.css")
stylesheet = "css/bootstrap-datepicker3.min.css",
# Need to enable noConflict mode. See #1346.
head = "<script>
(function() {
var datepicker = $.fn.datepicker.noConflict();
$.fn.bsDatepicker = datepicker;
})();
</script>"
)

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
@@ -32,41 +32,49 @@
#' @seealso \code{\link{dateInput}}, \code{\link{updateDateRangeInput}}
#'
#' @examples
#' dateRangeInput("daterange", "Date range:",
#' start = "2001-01-01",
#' end = "2010-12-31")
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' # Default start and end is the current date in the client's time zone
#' dateRangeInput("daterange", "Date range:")
#' ui <- fluidPage(
#' dateRangeInput("daterange1", "Date range:",
#' start = "2001-01-01",
#' end = "2010-12-31"),
#'
#' # start and end are always specified in yyyy-mm-dd, even if the display
#' # format is different
#' dateRangeInput("daterange", "Date range:",
#' start = "2001-01-01",
#' end = "2010-12-31",
#' min = "2001-01-01",
#' max = "2012-12-21",
#' format = "mm/dd/yy",
#' separator = " - ")
#' # Default start and end is the current date in the client's time zone
#' dateRangeInput("daterange2", "Date range:"),
#'
#' # Pass in Date objects
#' dateRangeInput("daterange", "Date range:",
#' start = Sys.Date()-10,
#' end = Sys.Date()+10)
#' # start and end are always specified in yyyy-mm-dd, even if the display
#' # format is different
#' dateRangeInput("daterange3", "Date range:",
#' start = "2001-01-01",
#' end = "2010-12-31",
#' min = "2001-01-01",
#' max = "2012-12-21",
#' format = "mm/dd/yy",
#' separator = " - "),
#'
#' # Use different language and different first day of week
#' dateRangeInput("daterange", "Date range:",
#' language = "de",
#' weekstart = 1)
#' # Pass in Date objects
#' dateRangeInput("daterange4", "Date range:",
#' start = Sys.Date()-10,
#' end = Sys.Date()+10),
#'
#' # Start with decade view instead of default month view
#' dateRangeInput("daterange", "Date range:",
#' startview = "decade")
#' # Use different language and different first day of week
#' dateRangeInput("daterange5", "Date range:",
#' language = "de",
#' weekstart = 1),
#'
#' # Start with decade view instead of default month view
#' dateRangeInput("daterange6", "Date range:",
#' startview = "decade")
#' )
#'
#' shinyApp(ui, server = function(input, output) { })
#' }
#' @export
dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
min = NULL, max = NULL, format = "yyyy-mm-dd", startview = "month",
weekstart = 0, language = "en", separator = " to ", width = NULL) {
weekstart = 0, language = "en", separator = " to ", width = NULL,
autoclose = TRUE) {
# If start and end are date objects, convert to a string with yyyy-mm-dd format
# Same for min and max
@@ -75,6 +83,10 @@ dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
if (inherits(min, "Date")) min <- format(min, "%Y-%m-%d")
if (inherits(max, "Date")) max <- format(max, "%Y-%m-%d")
restored <- restoreInput(id = inputId, default = list(start, end))
start <- restored[[1]]
end <- restored[[2]]
attachDependencies(
div(id = inputId,
class = "shiny-date-range-input form-group shiny-input-container",
@@ -87,24 +99,26 @@ 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,
`data-max-date` = max,
`data-initial-date` = start
`data-initial-date` = start,
`data-date-autoclose` = if (autoclose) "true" else "false"
),
span(class = "input-group-addon", separator),
tags$input(
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,
`data-max-date` = max,
`data-initial-date` = end
`data-initial-date` = end,
`data-date-autoclose` = if (autoclose) "true" else "false"
)
)
),

View File

@@ -27,21 +27,96 @@
#' 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
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' sidebarLayout(
#' sidebarPanel(
#' fileInput("file1", "Choose CSV File",
#' accept = c(
#' "text/csv",
#' "text/comma-separated-values,text/plain",
#' ".csv")
#' ),
#' tags$hr(),
#' checkboxInput("header", "Header", TRUE)
#' ),
#' mainPanel(
#' tableOutput("contents")
#' )
#' )
#' )
#'
#' server <- function(input, output) {
#' output$contents <- renderTable({
#' # input$file1 will be NULL initially. After the user selects
#' # and uploads a file, it will be a data frame with 'name',
#' # 'size', 'type', and 'datapath' columns. The 'datapath'
#' # column will contain the local filenames where the data can
#' # be found.
#' inFile <- input$file1
#'
#' if (is.null(inFile))
#' return(NULL)
#'
#' read.csv(inFile$datapath, header = input$header)
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @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)
# Catch potential edge case - ensure that it's either NULL or a data frame.
if (!is.null(restoredValue) && !is.data.frame(restoredValue)) {
warning("Restored value for ", inputId, " has incorrect format.")
restoredValue <- NULL
}
if (!is.null(restoredValue)) {
restoredValue <- toJSON(restoredValue, strict_atomic = FALSE)
}
inputTag <- tags$input(
id = inputId,
name = inputId,
type = "file",
style = "display: none;",
`data-restore` = restoredValue
)
inputTag <- tags$input(id = inputId, name = inputId, type = "file")
if (multiple)
inputTag$attribs$multiple <- "multiple"
if (length(accept) > 0)
inputTag$attribs$accept <- paste(accept, collapse=',')
div(class = "form-group shiny-input-container",
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
label %AND% tags$label(label),
inputTag,
div(class = "input-group",
tags$label(class = "input-group-btn",
span(class = "btn btn-default btn-file",
buttonLabel,
inputTag
)
),
tags$input(type = "text", class = "form-control",
placeholder = placeholder, readonly = "readonly"
)
),
tags$div(
id=paste(inputId, "_progress", sep=""),
class="progress progress-striped active shiny-file-input-progress",

View File

@@ -1,4 +1,3 @@
#' Create a numeric input control
#'
#' Create an input control for entry of numeric values
@@ -13,12 +12,24 @@
#' @seealso \code{\link{updateNumericInput}}
#'
#' @examples
#' numericInput("obs", "Observations:", 10,
#' min = 1, max = 100)
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' numericInput("obs", "Observations:", 10, min = 1, max = 100),
#' verbatimTextOutput("value")
#' )
#' server <- function(input, output) {
#' output$value <- renderText({ input$obs })
#' }
#' shinyApp(ui, server)
#' }
#' @export
numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA,
width = NULL) {
value <- restoreInput(id = inputId, default = value)
# build input tag
inputTag <- tags$input(id = inputId, type = "number", class="form-control",
value = formatNoSci(value))

View File

@@ -9,12 +9,29 @@
#' @seealso \code{\link{updateTextInput}}
#'
#' @examples
#' passwordInput("password", "Password:")
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' passwordInput("password", "Password:"),
#' actionButton("go", "Go"),
#' verbatimTextOutput("value")
#' )
#' server <- function(input, output) {
#' output$value <- renderText({
#' req(input$go)
#' isolate(input$password)
#' })
#' }
#' shinyApp(ui, server)
#' }
#' @export
passwordInput <- function(inputId, label, value = "", width = NULL) {
passwordInput <- function(inputId, label, value = "", width = NULL,
placeholder = NULL) {
div(class = "form-group shiny-input-container",
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
label %AND% tags$label(label, `for` = inputId),
tags$input(id = inputId, type="password", class="form-control", value=value)
tags$input(id = inputId, type="password", class="form-control", value=value,
placeholder = placeholder)
)
}

View File

@@ -3,47 +3,101 @@
#' Create a set of radio buttons used to select an item from a list.
#'
#' If you need to represent a "None selected" state, it's possible to default
#' the radio buttons to have no options selected by using
#' \code{selected = character(0)}. However, this is not recommended, as it gives
#' the user no way to return to that state once they've made a selection.
#' Instead, consider having the first of your choices be \code{c("None selected"
#' = "")}.
#' the radio buttons to have no options selected by using \code{selected =
#' character(0)}. However, this is not recommended, as it gives the user no way
#' to return to that state once they've made a selection. Instead, consider
#' having the first of your choices be \code{c("None selected" = "")}.
#'
#' @inheritParams textInput
#' @param choices List of values to select from (if elements of the list are
#' named then that name rather than the value is displayed to the user)
#' @param selected The initially selected value (if not specified then
#' defaults to the first value)
#' 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. The values should be strings; other
#' types (such as logicals and numbers) will be coerced to strings.
#' @param selected The initially selected value (if not specified then defaults
#' to the first value)
#' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
#' @return A set of radio buttons that can be added to a UI definition.
#' @param choiceNames,choiceValues List of names and values, respectively, that
#' are displayed to the user in the app and correspond to the each choice (for
#' this reason, \code{choiceNames} and \code{choiceValues} must have the same
#' length). If either of these arguments is provided, then the other
#' \emph{must} be provided and \code{choices} \emph{must not} be provided. The
#' advantage of using both of these over a named list for \code{choices} is
#' that \code{choiceNames} allows any type of UI object to be passed through
#' (tag objects, icons, HTML code, ...), instead of just simple text. See
#' Examples.
#'
#' @family input elements
#' @seealso \code{\link{updateRadioButtons}}
#'
#' @examples
#' radioButtons("dist", "Distribution type:",
#' c("Normal" = "norm",
#' "Uniform" = "unif",
#' "Log-normal" = "lnorm",
#' "Exponential" = "exp"))
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' radioButtons("dist", "Distribution type:",
#' c("Normal" = "norm",
#' "Uniform" = "unif",
#' "Log-normal" = "lnorm",
#' "Exponential" = "exp")),
#' plotOutput("distPlot")
#' )
#'
#' server <- function(input, output) {
#' output$distPlot <- renderPlot({
#' dist <- switch(input$dist,
#' norm = rnorm,
#' unif = runif,
#' lnorm = rlnorm,
#' exp = rexp,
#' rnorm)
#'
#' hist(dist(500))
#' })
#' }
#'
#' 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.
@@ -28,24 +33,59 @@
#' @return A select list control that can be added to a UI definition.
#'
#' @family input elements
#' @seealso \code{\link{updateSelectInput}}
#' @seealso \code{\link{updateSelectInput}} \code{\link{varSelectInput}}
#'
#' @examples
#' selectInput("variable", "Variable:",
#' c("Cylinders" = "cyl",
#' "Transmission" = "am",
#' "Gears" = "gear"))
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' # 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)
#' }
#' )
#'
#' # demoing optgroup support in the `choices` arg
#' shinyApp(
#' ui = fluidPage(
#' selectInput("state", "Choose a state:",
#' list(`East Coast` = list("NY", "NJ", "CT"),
#' `West Coast` = list("WA", "OR", "CA"),
#' `Midwest` = list("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)
# resolve names
choices <- choicesWithNames(choices)
# 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'.")
@@ -89,7 +129,7 @@ selectOptions <- function(choices, selected = NULL) {
# If sub-list, create an optgroup and recurse into the sublist
sprintf(
'<optgroup label="%s">\n%s\n</optgroup>',
htmlEscape(label),
htmlEscape(label, TRUE),
selectOptions(choice, selected)
)
@@ -97,7 +137,7 @@ selectOptions <- function(choices, selected = NULL) {
# If single item, just return option string
sprintf(
'<option value="%s"%s>%s</option>',
htmlEscape(choice),
htmlEscape(choice, TRUE),
if (choice %in% selected) ' selected' else '',
htmlEscape(label)
)
@@ -115,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\%'};
@@ -152,6 +192,13 @@ selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
))
)
if ('drag_drop' %in% options$plugins) {
selectizeDep <- list(selectizeDep, htmlDependency(
'jqueryui', '1.12.1', c(href = 'shared/jqueryui'),
script = 'jquery-ui.min.js'
))
}
# Insert script on same level as <select> tag
select$children[[2]] <- tagAppendChild(
select$children[[2]],
@@ -165,3 +212,135 @@ selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
attachDependencies(select, selectizeDep)
}
#' Select variables from a data frame
#'
#' Create a select list that can be used to choose a single or multiple items
#' from the column names of a data frame.
#'
#' The resulting server \code{input} value will be returned as:
#' \itemize{
#' \item a symbol if \code{multiple = FALSE}. The \code{input} value should be
#' used with rlang's \code{\link[rlang]{!!}}. For example,
#' \code{ggplot2::aes(!!input$variable)}.
#' \item a list of symbols if \code{multiple = TRUE}. The \code{input} value
#' should be used with rlang's \code{\link[rlang]{!!!}} to expand
#' the symbol list as individual arguments. For example,
#' \code{dplyr::select(mtcars, !!!input$variabls)} which is
#' equivalent to \code{dplyr::select(mtcars, !!input$variabls[[1]], !!input$variabls[[2]], ..., !!input$variabls[[length(input$variabls)]])}.
#' }
#'
#' By default, \code{varSelectInput()} and \code{selectizeInput()} use the
#' JavaScript library \pkg{selectize.js}
#' (\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}.
#'
#' @inheritParams selectInput
#' @param data A data frame. Used to retrieve the column names as choices for a \code{\link{selectInput}}
#' @return A variable select list control that can be added to a UI definition.
#'
#' @family input elements
#' @seealso \code{\link{updateSelectInput}}
#' @examples
#'
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' library(ggplot2)
#'
#' # single selection
#' shinyApp(
#' ui = fluidPage(
#' varSelectInput("variable", "Variable:", mtcars),
#' plotOutput("data")
#' ),
#' server = function(input, output) {
#' output$data <- renderPlot({
#' ggplot(mtcars, aes(!!input$variable)) + geom_histogram()
#' })
#' }
#' )
#'
#'
#' # multiple selections
#' \dontrun{
#' shinyApp(
#' ui = fluidPage(
#' varSelectInput("variables", "Variable:", mtcars, multiple = TRUE),
#' tableOutput("data")
#' ),
#' server = function(input, output) {
#' output$data <- renderTable({
#' if (length(input$variables) == 0) return(mtcars)
#' mtcars %>% dplyr::select(!!!input$variables)
#' }, rownames = TRUE)
#' }
#' )}
#'
#' }
#' @export
varSelectInput <- function(
inputId, label, data, selected = NULL,
multiple = FALSE, selectize = TRUE, width = NULL,
size = NULL
) {
# no place holders
choices <- colnames(data)
selectInputVal <- selectInput(
inputId = inputId,
label = label,
choices = choices,
selected = selected,
multiple = multiple,
selectize = selectize,
width = width,
size = size
)
# set the select tag class to be "symbol"
selectClass <- selectInputVal$children[[2]]$children[[1]]$attribs$class
if (is.null(selectClass)) {
newClass <- "symbol"
} else {
newClass <- paste(selectClass, "symbol", sep = " ")
}
selectInputVal$children[[2]]$children[[1]]$attribs$class <- newClass
selectInputVal
}
#' @rdname varSelectInput
#' @param ... Arguments passed to \code{varSelectInput()}.
#' @param options A list of options. See the documentation of \pkg{selectize.js}
#' 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\%'};
#' see \code{\link{validateCssUnit}}.
#' @note The variable selectize input created from \code{varSelectizeInput()} allows
#' deletion of the selected option even in a single select input, which will
#' return an empty string as its value. This is the default behavior of
#' \pkg{selectize.js}. However, the selectize input created from
#' \code{selectInput(..., selectize = TRUE)} will ignore the empty string
#' value when it is a single choice input and the empty string is not in the
#' \code{choices} argument. This is to keep compatibility with
#' \code{selectInput(..., selectize = FALSE)}.
#' @export
varSelectizeInput <- function(inputId, ..., options = NULL, width = NULL) {
selectizeIt(
inputId,
varSelectInput(inputId, ..., selectize = FALSE, width = width),
options
)
}

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
@@ -48,6 +48,28 @@
#' @family input elements
#' @seealso \code{\link{updateSliderInput}}
#'
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' ui <- fluidPage(
#' sliderInput("obs", "Number of observations:",
#' min = 0, max = 1000, value = 500
#' ),
#' plotOutput("distPlot")
#' )
#'
#' # Server logic
#' server <- function(input, output) {
#' output$distPlot <- renderPlot({
#' hist(rnorm(input$obs))
#' })
#' }
#'
#' # Complete app with UI and server components
#' shinyApp(ui, server)
#' }
#' @export
sliderInput <- function(inputId, label, min, max, value, step = NULL,
round = FALSE, format = NULL, locale = NULL,
@@ -64,38 +86,25 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
version = "0.10.2.2")
}
# If step is NULL, use heuristic to set the step size.
findStepSize <- function(min, max, step) {
if (!is.null(step)) return(step)
dataType <- getSliderType(min, max, value)
range <- max - min
# If short range or decimals, use continuous decimal with ~100 points
if (range < 2 || hasDecimals(min) || hasDecimals(max)) {
step <- pretty(c(min, max), n = 100)
step[2] - step[1]
} else {
1
}
if (is.null(timeFormat)) {
timeFormat <- switch(dataType, date = "%F", datetime = "%F %T", number = NULL)
}
if (inherits(min, "Date")) {
if (!inherits(max, "Date") || !inherits(value, "Date"))
stop("`min`, `max`, and `value must all be Date or non-Date objects")
dataType <- "date"
# Restore bookmarked values here, after doing the type checking, because the
# restored value will be a character vector instead of Date or POSIXct, and we can do
# the conversion to correct type next.
value <- restoreInput(id = inputId, default = value)
if (is.null(timeFormat))
timeFormat <- "%F"
} else if (inherits(min, "POSIXt")) {
if (!inherits(max, "POSIXt") || !inherits(value, "POSIXt"))
stop("`min`, `max`, and `value must all be POSIXt or non-POSIXt objects")
dataType <- "datetime"
if (is.null(timeFormat))
timeFormat <- "%F %T"
} else {
dataType <- "number"
if (is.character(value)) {
# If we got here, the value was restored from a URL-encoded bookmark.
if (dataType == "date") {
value <- as.Date(value, format = "%Y-%m-%d")
} else if (dataType == "datetime") {
# Date-times will have a format like "2018-02-28T03:46:26Z"
value <- as.POSIXct(value, format = "%Y-%m-%dT%H:%M:%SZ", tz = "UTC")
}
}
step <- findStepSize(min, max, step)
@@ -141,11 +150,13 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
`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,
@@ -191,7 +202,7 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
}
dep <- list(
htmlDependency("ionrangeslider", "2.0.12", 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.
@@ -211,6 +222,34 @@ hasDecimals <- function(value) {
return (!identical(value, truncatedValue))
}
# If step is NULL, use heuristic to set the step size.
findStepSize <- function(min, max, step) {
if (!is.null(step)) return(step)
range <- max - min
# If short range or decimals, use continuous decimal with ~100 points
if (range < 2 || hasDecimals(min) || hasDecimals(max)) {
# Workaround for rounding errors (#1006): the intervals between the items
# returned by pretty() can have rounding errors. To avoid this, we'll use
# pretty() to find the min, max, and number of steps, and then use those
# values to calculate the step size.
pretty_steps <- pretty(c(min, max), n = 100)
n_steps <- length(pretty_steps) - 1
# Fix for #2061: Windows has low-significance digits (like 17 digits out)
# even at the boundaries of pretty()'s output. Use signif(digits = 10),
# which should be way way less significant than any data we'd want to keep.
# It might make sense to use signif(steps[2] - steps[1], 10) instead, but
# for now trying to make the minimal change.
signif(digits = 10, (max(pretty_steps) - min(pretty_steps)) / n_steps)
} else {
1
}
}
#' @rdname sliderInput
#'
#' @param interval The interval, in milliseconds, between each animation step.
@@ -221,7 +260,6 @@ hasDecimals <- function(value) {
#' or list of tags (using \code{\link{tag}} and friends), or raw HTML (using
#' \code{\link{HTML}}).
#' @param pauseButton Similar to \code{playButton}, but for the pause button.
#'
#' @export
animationOptions <- function(interval=1000,
loop=FALSE,

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

@@ -7,18 +7,37 @@
#' @param value Initial value.
#' @param width The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
#' see \code{\link{validateCssUnit}}.
#' @param placeholder A character string giving the user a hint as to what can
#' be entered into the control. Internet Explorer 8 and 9 do not support this
#' option.
#' @return A text input control that can be added to a UI definition.
#'
#' @family input elements
#' @seealso \code{\link{updateTextInput}}
#'
#' @examples
#' textInput("caption", "Caption:", "Data Summary")
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' textInput("caption", "Caption", "Data Summary"),
#' verbatimTextOutput("value")
#' )
#' server <- function(input, output) {
#' output$value <- renderText({ input$caption })
#' }
#' shinyApp(ui, server)
#' }
#' @export
textInput <- function(inputId, label, value = "", width = NULL) {
textInput <- function(inputId, label, value = "", width = NULL,
placeholder = NULL) {
value <- restoreInput(id = inputId, default = value)
div(class = "form-group shiny-input-container",
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
label %AND% tags$label(label, `for` = inputId),
tags$input(id = inputId, type="text", class="form-control", value=value)
tags$input(id = inputId, type="text", class="form-control", value=value,
placeholder = placeholder)
)
}

69
R/input-textarea.R Normal file
View File

@@ -0,0 +1,69 @@
#' Create a textarea input control
#'
#' Create a textarea input control for entry of unstructured text values.
#'
#' @inheritParams textInput
#' @param height The height of the input, e.g. \code{'400px'}, or
#' \code{'100\%'}; see \code{\link{validateCssUnit}}.
#' @param cols Value of the visible character columns of the input, e.g.
#' \code{80}. If used with \code{width}, \code{width} will take precedence in
#' the browser's rendering.
#' @param rows The value of the visible character rows of the input, e.g.
#' \code{6}. If used with \code{height}, \code{height} will take precedence in
#' the browser's rendering.
#' @param resize Which directions the textarea box can be resized. Can be one of
#' \code{"both"}, \code{"none"}, \code{"vertical"}, and \code{"horizontal"}.
#' The default, \code{NULL}, will use the client browser's default setting for
#' resizing textareas.
#' @return A textarea input control that can be added to a UI definition.
#'
#' @family input elements
#' @seealso \code{\link{updateTextAreaInput}}
#'
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' textAreaInput("caption", "Caption", "Data Summary", width = "1000px"),
#' verbatimTextOutput("value")
#' )
#' server <- function(input, output) {
#' output$value <- renderText({ input$caption })
#' }
#' shinyApp(ui, server)
#'
#' }
#' @export
textAreaInput <- function(inputId, label, value = "", width = NULL, height = NULL,
cols = NULL, rows = NULL, placeholder = NULL, resize = NULL) {
value <- restoreInput(id = inputId, default = value)
if (!is.null(resize)) {
resize <- match.arg(resize, c("both", "none", "vertical", "horizontal"))
}
style <- paste(
if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
if (!is.null(height)) paste0("height: ", validateCssUnit(height), ";"),
if (!is.null(resize)) paste0("resize: ", resize, ";")
)
# Workaround for tag attribute=character(0) bug:
# https://github.com/rstudio/htmltools/issues/65
if (length(style) == 0) style <- NULL
div(class = "form-group shiny-input-container",
label %AND% tags$label(label, `for` = inputId),
tags$textarea(
id = inputId,
class = "form-control",
placeholder = placeholder,
style = style,
rows = rows,
cols = cols,
value
)
)
}

View File

@@ -2,42 +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.
validateSelected <- function(selected, choices, inputId) {
# drop names, otherwise toJSON() keeps them too
selected <- unname(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
@@ -45,14 +65,18 @@ generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox
if (value %in% selected)
inputTag$attribs$checked <- "checked"
# in case, the options include UI code other than text
# (arbitrary HTML using the tags() function or equivalent)
pd <- processDeps(name, session)
# If inline, there's no wrapper div, and the label needs a class like
# checkbox-inline.
if (inline) {
tags$label(class = paste0(type, "-inline"), inputTag, tags$span(name))
tags$label(class = paste0(type, "-inline"), inputTag,
tags$span(pd$html, pd$deps))
} else {
tags$div(class = type,
tags$label(inputTag, tags$span(name))
)
tags$div(class = type, tags$label(inputTag,
tags$span(pd$html, pd$deps)))
}
},
SIMPLIFY = FALSE, USE.NAMES = FALSE
@@ -63,7 +87,7 @@ generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox
# Takes a vector or list, and adds names (same as the value) to any entries
# without names.
# without names. Coerces all leaf nodes to `character`.
choicesWithNames <- function(choices) {
# Take a vector or list, and convert to list. Also, if any children are
# vectors with length > 1, convert those to list. If the list is unnamed,
@@ -79,7 +103,7 @@ choicesWithNames <- function(choices) {
if (is.list(val))
listify(val)
else if (length(val) == 1 && is.null(names(val)))
val
as.character(val)
else
makeNamed(as.list(val))
})

325
R/insert-tab.R Normal file
View File

@@ -0,0 +1,325 @@
#' Dynamically insert/remove a tabPanel
#'
#' Dynamically insert or remove a \code{\link{tabPanel}} (or a
#' \code{\link{navbarMenu}}) from an existing \code{\link{tabsetPanel}},
#' \code{\link{navlistPanel}} or \code{\link{navbarPage}}.
#'
#' When you want to insert a new tab before or after an existing tab, you
#' should use \code{insertTab}. When you want to prepend a tab (i.e. add a
#' tab to the beginning of the \code{tabsetPanel}), use \code{prependTab}.
#' When you want to append a tab (i.e. add a tab to the end of the
#' \code{tabsetPanel}), use \code{appendTab}.
#'
#' For \code{navbarPage}, you can insert/remove conventional
#' \code{tabPanel}s (whether at the top level or nested inside a
#' \code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
#' For the latter case, \code{target} should be the \code{menuName} that
#' you gave your \code{navbarMenu} when you first created it (by default,
#' this is equal to the value of the \code{title} argument).
#'
#' @param inputId The \code{id} of the \code{tabsetPanel} (or
#' \code{navlistPanel} or \code{navbarPage}) into which \code{tab} will
#' be inserted/removed.
#'
#' @param tab The item to be added (must be created with \code{tabPanel},
#' or with \code{navbarMenu}).
#'
#' @param target If inserting: the \code{value} of an existing
#' \code{tabPanel}, next to which \code{tab} will be added.
#' If removing: the \code{value} of the \code{tabPanel} that
#' you want to remove. See Details if you want to insert next to/remove
#' an entire \code{navbarMenu} instead.
#'
#' @param position Should \code{tab} be added before or after the
#' \code{target} tab?
#'
#' @param select Should \code{tab} be selected upon being inserted?
#'
#' @param session The shiny session within which to call this function.
#'
#' @seealso \code{\link{showTab}}
#'
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#'
#' # example app for inserting/removing a tab
#' ui <- fluidPage(
#' sidebarLayout(
#' sidebarPanel(
#' actionButton("add", "Add 'Dynamic' tab"),
#' actionButton("remove", "Remove 'Foo' tab")
#' ),
#' mainPanel(
#' tabsetPanel(id = "tabs",
#' tabPanel("Hello", "This is the hello tab"),
#' tabPanel("Foo", "This is the foo tab"),
#' tabPanel("Bar", "This is the bar tab")
#' )
#' )
#' )
#' )
#' server <- function(input, output, session) {
#' observeEvent(input$add, {
#' insertTab(inputId = "tabs",
#' tabPanel("Dynamic", "This a dynamically-added tab"),
#' target = "Bar"
#' )
#' })
#' observeEvent(input$remove, {
#' removeTab(inputId = "tabs", target = "Foo")
#' })
#' }
#'
#' shinyApp(ui, server)
#'
#'
#' # example app for prepending/appending a navbarMenu
#' ui <- navbarPage("Navbar page", id = "tabs",
#' tabPanel("Home",
#' actionButton("prepend", "Prepend a navbarMenu"),
#' actionButton("append", "Append a navbarMenu")
#' )
#' )
#' server <- function(input, output, session) {
#' observeEvent(input$prepend, {
#' id <- paste0("Dropdown", input$prepend, "p")
#' prependTab(inputId = "tabs",
#' navbarMenu(id,
#' tabPanel("Drop1", paste("Drop1 page from", id)),
#' tabPanel("Drop2", paste("Drop2 page from", id)),
#' "------",
#' "Header",
#' tabPanel("Drop3", paste("Drop3 page from", id))
#' )
#' )
#' })
#' observeEvent(input$append, {
#' id <- paste0("Dropdown", input$append, "a")
#' appendTab(inputId = "tabs",
#' navbarMenu(id,
#' tabPanel("Drop1", paste("Drop1 page from", id)),
#' tabPanel("Drop2", paste("Drop2 page from", id)),
#' "------",
#' "Header",
#' tabPanel("Drop3", paste("Drop3 page from", id))
#' )
#' )
#' })
#' }
#'
#' shinyApp(ui, server)
#'
#' }
#' @export
insertTab <- function(inputId, tab, target,
position = c("before", "after"), select = FALSE,
session = getDefaultReactiveDomain()) {
force(target)
force(select)
position <- match.arg(position)
inputId <- session$ns(inputId)
# Barbara -- August 2017
# Note: until now, the number of tabs in a tabsetPanel (or navbarPage
# or navlistPanel) was always fixed. So, an easy way to give an id to
# a tab was simply incrementing a counter. (Just like it was easy to
# give a random 4-digit number to identify the tabsetPanel). Since we
# can only know this in the client side, we'll just pass `id` and
# `tsid` (TabSetID) as dummy values that will be fixed in the JS code.
item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
callback <- function() {
session$sendInsertTab(
inputId = inputId,
liTag = processDeps(item$liTag, session),
divTag = processDeps(item$divTag, session),
menuName = NULL,
target = target,
position = position,
select = select)
}
session$onFlush(callback, once = TRUE)
}
#' @param menuName This argument should only be used when you want to
#' prepend (or append) \code{tab} to the beginning (or end) of an
#' existing \code{\link{navbarMenu}} (which must itself be part of
#' an existing \code{\link{navbarPage}}). In this case, this argument
#' should be the \code{menuName} that you gave your \code{navbarMenu}
#' when you first created it (by default, this is equal to the value
#' of the \code{title} argument). Note that you still need to set the
#' \code{inputId} argument to whatever the \code{id} of the parent
#' \code{navbarPage} is. If \code{menuName} is left as \code{NULL},
#' \code{tab} will be prepended (or appended) to whatever
#' \code{inputId} is.
#'
#' @rdname insertTab
#' @export
prependTab <- function(inputId, tab, select = FALSE, menuName = NULL,
session = getDefaultReactiveDomain()) {
force(select)
force(menuName)
inputId <- session$ns(inputId)
item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
callback <- function() {
session$sendInsertTab(
inputId = inputId,
liTag = processDeps(item$liTag, session),
divTag = processDeps(item$divTag, session),
menuName = menuName,
target = NULL,
position = "after",
select = select)
}
session$onFlush(callback, once = TRUE)
}
#' @rdname insertTab
#' @export
appendTab <- function(inputId, tab, select = FALSE, menuName = NULL,
session = getDefaultReactiveDomain()) {
force(select)
force(menuName)
inputId <- session$ns(inputId)
item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
callback <- function() {
session$sendInsertTab(
inputId = inputId,
liTag = processDeps(item$liTag, session),
divTag = processDeps(item$divTag, session),
menuName = menuName,
target = NULL,
position = "before",
select = select)
}
session$onFlush(callback, once = TRUE)
}
#' @rdname insertTab
#' @export
removeTab <- function(inputId, target,
session = getDefaultReactiveDomain()) {
force(target)
inputId <- session$ns(inputId)
callback <- function() {
session$sendRemoveTab(
inputId = inputId,
target = target)
}
session$onFlush(callback, once = TRUE)
}
#' Dynamically hide/show a tabPanel
#'
#' Dynamically hide or show a \code{\link{tabPanel}} (or a
#' \code{\link{navbarMenu}})from an existing \code{\link{tabsetPanel}},
#' \code{\link{navlistPanel}} or \code{\link{navbarPage}}.
#'
#' For \code{navbarPage}, you can hide/show conventional
#' \code{tabPanel}s (whether at the top level or nested inside a
#' \code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
#' For the latter case, \code{target} should be the \code{menuName} that
#' you gave your \code{navbarMenu} when you first created it (by default,
#' this is equal to the value of the \code{title} argument).
#'
#' @param inputId The \code{id} of the \code{tabsetPanel} (or
#' \code{navlistPanel} or \code{navbarPage}) in which to find
#' \code{target}.
#'
#' @param target The \code{value} of the \code{tabPanel} to be
#' hidden/shown. See Details if you want to hide/show an entire
#' \code{navbarMenu} instead.
#'
#' @param select Should \code{target} be selected upon being shown?
#'
#' @param session The shiny session within which to call this function.
#'
#' @seealso \code{\link{insertTab}}
#'
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#'
#' ui <- navbarPage("Navbar page", id = "tabs",
#' tabPanel("Home",
#' actionButton("hideTab", "Hide 'Foo' tab"),
#' actionButton("showTab", "Show 'Foo' tab"),
#' actionButton("hideMenu", "Hide 'More' navbarMenu"),
#' actionButton("showMenu", "Show 'More' navbarMenu")
#' ),
#' tabPanel("Foo", "This is the foo tab"),
#' tabPanel("Bar", "This is the bar tab"),
#' navbarMenu("More",
#' tabPanel("Table", "Table page"),
#' tabPanel("About", "About page"),
#' "------",
#' "Even more!",
#' tabPanel("Email", "Email page")
#' )
#' )
#'
#' server <- function(input, output, session) {
#' observeEvent(input$hideTab, {
#' hideTab(inputId = "tabs", target = "Foo")
#' })
#'
#' observeEvent(input$showTab, {
#' showTab(inputId = "tabs", target = "Foo")
#' })
#'
#' observeEvent(input$hideMenu, {
#' hideTab(inputId = "tabs", target = "More")
#' })
#'
#' observeEvent(input$showMenu, {
#' showTab(inputId = "tabs", target = "More")
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#'
#' @export
showTab <- function(inputId, target, select = FALSE,
session = getDefaultReactiveDomain()) {
force(target)
if (select) updateTabsetPanel(session, inputId, selected = target)
inputId <- session$ns(inputId)
callback <- function() {
session$sendChangeTabVisibility(
inputId = inputId,
target = target,
type = "show"
)
}
session$onFlush(callback, once = TRUE)
}
#' @rdname showTab
#' @export
hideTab <- function(inputId, target,
session = getDefaultReactiveDomain()) {
force(target)
inputId <- session$ns(inputId)
callback <- function() {
session$sendChangeTabVisibility(
inputId = inputId,
target = target,
type = "hide"
)
}
session$onFlush(callback, once = TRUE)
}

174
R/insert-ui.R Normal file
View File

@@ -0,0 +1,174 @@
#' Insert UI objects
#'
#' Insert a UI object into the app.
#'
#' This function allows you to dynamically add an arbitrarily large UI
#' object into your app, whenever you want, as many times as you want.
#' Unlike \code{\link{renderUI}}, the UI generated with \code{insertUI}
#' is not updatable as a whole: once it's created, it stays there. Each
#' new call to \code{insertUI} creates more UI objects, in addition to
#' the ones already there (all independent from one another). To
#' update a part of the UI (ex: an input object), you must use the
#' appropriate \code{render} function or a customized \code{reactive}
#' function. To remove any part of your UI, use \code{\link{removeUI}}.
#'
#' @param selector A string that is accepted by jQuery's selector (i.e. the
#' string \code{s} to be placed in a \code{$(s)} jQuery call). This selector
#' will determine the element(s) relative to which you want to insert your
#' UI object.
#'
#' @param where Where your UI object should go relative to the selector:
#' \describe{
#' \item{\code{beforeBegin}}{Before the selector element itself}
#' \item{\code{afterBegin}}{Just inside the selector element, before its
#' first child}
#' \item{\code{beforeEnd}}{Just inside the selector element, after its
#' last child (default)}
#' \item{\code{afterEnd}}{After the selector element itself}
#' }
#' Adapted from
#' \href{https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML}{here}.
#'
#' @param ui The UI object you want to insert. This can be anything that
#' you usually put inside your apps's \code{ui} function. If you're inserting
#' multiple elements in one call, make sure to wrap them in either a
#' \code{tagList()} or a \code{tags$div()} (the latter option has the
#' advantage that you can give it an \code{id} to make it easier to
#' reference or remove it later on). If you want to insert raw html, use
#' \code{ui = HTML()}.
#'
#' @param multiple In case your selector matches more than one element,
#' \code{multiple} determines whether Shiny should insert the UI object
#' relative to all matched elements or just relative to the first
#' matched element (default).
#'
#' @param immediate Whether the UI object should be immediately inserted into
#' the app when you call \code{insertUI}, or whether Shiny should wait until
#' all outputs have been updated and all observers have been run (default).
#'
#' @param session The shiny session within which to call \code{insertUI}.
#'
#' @seealso \code{\link{removeUI}}
#'
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' # Define UI
#' ui <- fluidPage(
#' actionButton("add", "Add UI")
#' )
#'
#' # Server logic
#' server <- function(input, output, session) {
#' observeEvent(input$add, {
#' insertUI(
#' selector = "#add",
#' where = "afterEnd",
#' ui = textInput(paste0("txt", input$add),
#' "Insert some text")
#' )
#' })
#' }
#'
#' # Complete app with UI and server components
#' shinyApp(ui, server)
#' }
#' @export
insertUI <- function(selector,
where = c("beforeBegin", "afterBegin", "beforeEnd", "afterEnd"),
ui,
multiple = FALSE,
immediate = FALSE,
session = getDefaultReactiveDomain()) {
force(selector)
force(ui)
force(session)
force(multiple)
if (missing(where)) where <- "beforeEnd"
where <- match.arg(where)
callback <- function() {
session$sendInsertUI(selector = selector,
multiple = multiple,
where = where,
content = processDeps(ui, session))
}
if (!immediate) session$onFlushed(callback, once = TRUE)
else callback()
}
#' Remove UI objects
#'
#' Remove a UI object from the app.
#'
#' This function allows you to remove any part of your UI. Once \code{removeUI}
#' is executed on some element, it is gone forever.
#'
#' While it may be a particularly useful pattern to pair this with
#' \code{\link{insertUI}} (to remove some UI you had previously inserted),
#' there is no restriction on what you can use \code{removeUI} on. Any
#' element that can be selected through a jQuery selector can be removed
#' through this function.
#'
#' @param selector A string that is accepted by jQuery's selector (i.e. the
#' string \code{s} to be placed in a \code{$(s)} jQuery call). This selector
#' will determine the element(s) to be removed. If you want to remove a
#' Shiny input or output, note that many of these are wrapped in \code{div}s,
#' so you may need to use a somewhat complex selector -- see the Examples below.
#' (Alternatively, you could also wrap the inputs/outputs that you want to be
#' able to remove easily in a \code{div} with an id.)
#'
#' @param multiple In case your selector matches more than one element,
#' \code{multiple} determines whether Shiny should remove all the matched
#' elements or just the first matched element (default).
#'
#' @param immediate Whether the element(s) should be immediately removed from
#' the app when you call \code{removeUI}, or whether Shiny should wait until
#' all outputs have been updated and all observers have been run (default).
#'
#' @param session The shiny session within which to call \code{removeUI}.
#'
#' @seealso \code{\link{insertUI}}
#'
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' # Define UI
#' ui <- fluidPage(
#' actionButton("rmv", "Remove UI"),
#' textInput("txt", "This is no longer useful")
#' )
#'
#' # Server logic
#' server <- function(input, output, session) {
#' observeEvent(input$rmv, {
#' removeUI(
#' selector = "div:has(> #txt)"
#' )
#' })
#' }
#'
#' # Complete app with UI and server components
#' shinyApp(ui, server)
#' }
#' @export
removeUI <- function(selector,
multiple = FALSE,
immediate = FALSE,
session = getDefaultReactiveDomain()) {
force(selector)
force(multiple)
force(session)
callback <- function() {
session$sendRemoveUI(selector = selector,
multiple = multiple)
}
if (!immediate) session$onFlushed(callback, once = TRUE)
else callback()
}

View File

@@ -53,7 +53,6 @@
#' over text). The default is \code{"auto"}, which is equivalent to
#' \code{ifelse(draggable, "move", "inherit")}.
#' @return An HTML element or list of elements.
#'
#' @export
absolutePanel <- function(...,
top = NULL, left = NULL, right = NULL, bottom = NULL,
@@ -80,9 +79,7 @@ absolutePanel <- function(...,
if (isTRUE(draggable)) {
divTag <- tagAppendAttributes(divTag, class='draggable')
return(tagList(
# IMPORTANT NOTE: If you update jqueryui, make sure you DON'T include the datepicker,
# as it collides with our bootstrap datepicker!
singleton(tags$head(tags$script(src='shared/jqueryui/1.10.4/jquery-ui.min.js'))),
singleton(tags$head(tags$script(src='shared/jqueryui/jquery-ui.min.js'))),
divTag,
tags$script('$(".draggable").draggable();')
))

View File

@@ -9,9 +9,11 @@ reactLogHandler <- function(req) {
return(NULL)
}
sessionToken <- parseQueryString(req$QUERY_STRING)$s
return(httpResponse(
status=200,
content=list(file=renderReactLog(), owned=TRUE)
content=list(file=renderReactLog(sessionToken), owned=TRUE)
))
}
@@ -39,35 +41,3 @@ sessionHandler <- function(req) {
shinysession$handleRequest(subreq)
})
}
dynamicHandler <- function(filePath, dependencyFiles=filePath) {
lastKnownTimestamps <- NA
metaHandler <- function(req) NULL
if (!file.exists(filePath))
return(metaHandler)
cacheContext <- CacheContext$new()
return (function(req) {
# Check if we need to rebuild
if (cacheContext$isDirty()) {
cacheContext$reset()
for (dep in dependencyFiles)
cacheContext$addDependencyFile(dep)
clearClients()
if (file.exists(filePath)) {
local({
cacheContext$with(function() {
sys.source(filePath, envir=new.env(parent=globalenv()), keep.source=TRUE)
})
})
}
metaHandler <<- joinHandlers(.globals$clients)
clearClients()
}
return(metaHandler(req))
})
}

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>"))
@@ -299,9 +299,7 @@ HandlerManager <- R6Class("HandlerManager",
if (reqSize > maxSize) {
return(list(status = 413L,
headers = list(
'Content-Type' = 'text/plain'
),
headers = list('Content-Type' = 'text/plain'),
body = 'Maximum upload size exceeded'))
}
else {
@@ -310,7 +308,18 @@ HandlerManager <- R6Class("HandlerManager",
},
call = .httpServer(
function (req) {
return(handlers$invoke(req))
withCallingHandlers(withLogErrors(handlers$invoke(req)),
error = function(cond) {
sanitizeErrors <- getOption('shiny.sanitize.errors', FALSE)
if (inherits(cond, 'shiny.custom.error') || !sanitizeErrors) {
stop(cond$message, call. = FALSE)
} else {
stop(paste("An error has occurred. Check your logs or",
"contact the app author for clarification."),
call. = FALSE)
}
}
)
},
getOption('shiny.sharedSecret')
),
@@ -333,7 +342,7 @@ HandlerManager <- R6Class("HandlerManager",
}
# Catch HEAD requests. For the purposes of handler functions, they
# should be treated like GET. The difference is that they shouldn't
# should be treated like GET. The difference is that they shouldn't
# return a body in the http response.
head_request <- FALSE
if (identical(req$REQUEST_METHOD, "HEAD")) {
@@ -342,38 +351,72 @@ HandlerManager <- R6Class("HandlerManager",
}
response <- handler(req)
if (is.null(response))
response <- httpResponse(404, content="<h1>Not Found</h1>")
if (inherits(response, "httpResponse")) {
headers <- as.list(response$headers)
headers$'Content-Type' <- response$content_type
res <- hybrid_chain(response, function(response) {
if (is.null(response))
response <- httpResponse(404, content="<h1>Not Found</h1>")
if (inherits(response, "httpResponse")) {
headers <- as.list(response$headers)
headers$'Content-Type' <- response$content_type
response <- filter(req, response)
if (head_request) {
headers$`Content-Length` <- getResponseContentLength(response, deleteOwnedContent = TRUE)
return(list(
status = response$status,
body = "",
headers = headers
))
} else {
return(list(
status = response$status,
body = response$content,
headers = headers
))
}
response <- filter(req, response)
if (head_request) {
headers$`Content-Length` <- nchar(response$content, type = "bytes")
return(list(
status = response$status,
body = "",
headers = headers
))
} else {
return(list(
status = response$status,
body = response$content,
headers = headers
))
# Assume it's a Rook-compatible response
return(response)
}
} else {
# Assume it's a Rook-compatible response
return(response)
}
})
}
}
)
)
# Safely get the Content-Length of a Rook response, or NULL if the length cannot
# be determined for whatever reason (probably malformed response$content).
# If deleteOwnedContent is TRUE, then the function should delete response
# content that is of the form list(file=..., owned=TRUE).
getResponseContentLength <- function(response, deleteOwnedContent) {
force(deleteOwnedContent)
result <- if (is.character(response$content) && length(response$content) == 1) {
nchar(response$content, type = "bytes")
} else if (is.raw(response$content)) {
length(response$content)
} else if (is.list(response$content) && !is.null(response$content$file)) {
if (deleteOwnedContent && isTRUE(response$content$owned)) {
on.exit(unlink(response$content$file, recursive = FALSE, force = FALSE), add = TRUE)
}
file.info(response$content$file)$size
} else {
warning("HEAD request for unexpected content class ", class(response$content)[[1]])
NULL
}
if (is.na(result)) {
# Mostly for missing file case
return(NULL)
} else {
return(result)
}
}
#
# ## Next steps
#

183
R/modal.R Normal file
View File

@@ -0,0 +1,183 @@
#' Show or remove a modal dialog
#'
#' This causes a modal dialog to be displayed in the client browser, and is
#' typically used with \code{\link{modalDialog}}.
#'
#' @param ui UI content to show in the modal.
#' @param session The \code{session} object passed to function given to
#' \code{shinyServer}.
#'
#' @seealso \code{\link{modalDialog}} for examples.
#' @export
showModal <- function(ui, session = getDefaultReactiveDomain()) {
res <- processDeps(ui, session)
session$sendModal("show",
list(
html = res$html,
deps = res$deps
)
)
}
#' @rdname showModal
#' @export
removeModal <- function(session = getDefaultReactiveDomain()) {
session$sendModal("remove", NULL)
}
#' Create a modal dialog UI
#'
#' This creates the UI for a modal dialog, using Bootstrap's modal class. Modals
#' are typically used for showing important messages, or for presenting UI that
#' requires input from the user, such as a username and password input.
#'
#' @param ... UI elements for the body of the modal dialog box.
#' @param title An optional title for the dialog.
#' @param footer UI for footer. Use \code{NULL} for no footer.
#' @param size One of \code{"s"} for small, \code{"m"} (the default) for medium,
#' or \code{"l"} for large.
#' @param easyClose If \code{TRUE}, the modal dialog can be dismissed by
#' clicking outside the dialog box, or be pressing the Escape key. If
#' \code{FALSE} (the default), the modal dialog can't be dismissed in those
#' ways; instead it must be dismissed by clicking on the dismiss button, or
#' from a call to \code{\link{removeModal}} on the server.
#' @param fade If \code{FALSE}, the modal dialog will have no fade-in animation
#' (it will simply appear rather than fade in to view).
#'
#' @examples
#' if (interactive()) {
#' # Display an important message that can be dismissed only by clicking the
#' # dismiss button.
#' shinyApp(
#' ui = basicPage(
#' actionButton("show", "Show modal dialog")
#' ),
#' server = function(input, output) {
#' observeEvent(input$show, {
#' showModal(modalDialog(
#' title = "Important message",
#' "This is an important message!"
#' ))
#' })
#' }
#' )
#'
#'
#' # Display a message that can be dismissed by clicking outside the modal dialog,
#' # or by pressing Esc.
#' shinyApp(
#' ui = basicPage(
#' actionButton("show", "Show modal dialog")
#' ),
#' server = function(input, output) {
#' observeEvent(input$show, {
#' showModal(modalDialog(
#' title = "Somewhat important message",
#' "This is a somewhat important message.",
#' easyClose = TRUE,
#' footer = NULL
#' ))
#' })
#' }
#' )
#'
#'
#' # Display a modal that requires valid input before continuing.
#' shinyApp(
#' ui = basicPage(
#' actionButton("show", "Show modal dialog"),
#' verbatimTextOutput("dataInfo")
#' ),
#'
#' server = function(input, output) {
#' # reactiveValues object for storing current data set.
#' vals <- reactiveValues(data = NULL)
#'
#' # Return the UI for a modal dialog with data selection input. If 'failed' is
#' # TRUE, then display a message that the previous value was invalid.
#' dataModal <- function(failed = FALSE) {
#' modalDialog(
#' textInput("dataset", "Choose data set",
#' placeholder = 'Try "mtcars" or "abc"'
#' ),
#' span('(Try the name of a valid data object like "mtcars", ',
#' 'then a name of a non-existent object like "abc")'),
#' if (failed)
#' div(tags$b("Invalid name of data object", style = "color: red;")),
#'
#' footer = tagList(
#' modalButton("Cancel"),
#' actionButton("ok", "OK")
#' )
#' )
#' }
#'
#' # Show modal when button is clicked.
#' observeEvent(input$show, {
#' showModal(dataModal())
#' })
#'
#' # When OK button is pressed, attempt to load the data set. If successful,
#' # remove the modal. If not show another modal, but this time with a failure
#' # message.
#' observeEvent(input$ok, {
#' # Check that data object exists and is data frame.
#' if (!is.null(input$dataset) && nzchar(input$dataset) &&
#' exists(input$dataset) && is.data.frame(get(input$dataset))) {
#' vals$data <- get(input$dataset)
#' removeModal()
#' } else {
#' showModal(dataModal(failed = TRUE))
#' }
#' })
#'
#' # Display information about selected data
#' output$dataInfo <- renderPrint({
#' if (is.null(vals$data))
#' "No data selected"
#' else
#' summary(vals$data)
#' })
#' }
#' )
#' }
#' @export
modalDialog <- function(..., title = NULL, footer = modalButton("Dismiss"),
size = c("m", "s", "l"), easyClose = FALSE, fade = TRUE) {
size <- match.arg(size)
cls <- if (fade) "modal fade" else "modal"
div(id = "shiny-modal", class = cls, tabindex = "-1",
`data-backdrop` = if (!easyClose) "static",
`data-keyboard` = if (!easyClose) "false",
div(
class = "modal-dialog",
class = switch(size, s = "modal-sm", m = NULL, l = "modal-lg"),
div(class = "modal-content",
if (!is.null(title)) div(class = "modal-header",
tags$h4(class = "modal-title", title)
),
div(class = "modal-body", ...),
if (!is.null(footer)) div(class = "modal-footer", footer)
)
),
tags$script("$('#shiny-modal').modal().focus();")
)
}
#' Create a button for a modal dialog
#'
#' When clicked, a \code{modalButton} will dismiss the modal dialog.
#'
#' @inheritParams actionButton
#' @seealso \code{\link{modalDialog}} for examples.
#' @export
modalButton <- function(label, icon = NULL) {
tags$button(type = "button", class = "btn btn-default",
`data-dismiss` = "modal", validateIcon(icon), label
)
}

67
R/modules.R Normal file
View File

@@ -0,0 +1,67 @@
# Creates an object whose $ and [[ pass through to the parent
# session, unless the name is matched in ..., in which case
# that value is returned instead. (See Decorator pattern.)
createSessionProxy <- function(parentSession, ...) {
e <- new.env(parent = emptyenv())
e$parent <- parentSession
e$overrides <- list(...)
structure(
e,
class = "session_proxy"
)
}
#' @export
`$.session_proxy` <- function(x, name) {
if (name %in% names(.subset2(x, "overrides")))
.subset2(x, "overrides")[[name]]
else
.subset2(x, "parent")[[name]]
}
#' @export
`[[.session_proxy` <- `$.session_proxy`
#' @export
`$<-.session_proxy` <- function(x, name, value) {
# this line allows users to write into session$userData
# (e.g. it allows something like `session$userData$x <- TRUE`,
# but not `session$userData <- TRUE`) from within a module
# without any hacks (see PR #1732)
if (identical(x[[name]], value)) return(x)
stop("Attempted to assign value on session proxy.")
}
`[[<-.session_proxy` <- `$<-.session_proxy`
#' Invoke a Shiny module
#'
#' Shiny's module feature lets you break complicated UI and server logic into
#' smaller, self-contained pieces. Compared to large monolithic Shiny apps,
#' modules are easier to reuse and easier to reason about. See the article at
#' \url{http://shiny.rstudio.com/articles/modules.html} to learn more.
#'
#' @param module A Shiny module server function
#' @param id An ID string that corresponds with the ID used to call the module's
#' UI function
#' @param ... Additional parameters to pass to module server function
#' @param session Session from which to make a child scope (the default should
#' almost always be used)
#'
#' @return The return value, if any, from executing the module server function
#' @seealso \url{http://shiny.rstudio.com/articles/modules.html}
#' @export
callModule <- function(module, id, ..., session = getDefaultReactiveDomain()) {
childScope <- session$makeScope(id)
withReactiveDomain(childScope, {
if (!is.function(module)) {
stop("module argument must be a function")
}
module(childScope$input, childScope$output, childScope, ...)
})
}

106
R/notifications.R Normal file
View File

@@ -0,0 +1,106 @@
#' Show or remove a notification
#'
#' These functions show and remove notifications in a Shiny application.
#'
#' @param ui Content of message.
#' @param action Message content that represents an action. For example, this
#' could be a link that the user can click on. This is separate from \code{ui}
#' so customized layouts can handle the main notification content separately
#' from action content.
#' @param duration Number of seconds to display the message before it
#' disappears. Use \code{NULL} to make the message not automatically
#' disappear.
#' @param closeButton If \code{TRUE}, display a button which will make the
#' notification disappear when clicked. If \code{FALSE} do not display.
#' @param id An ID string. This can be used to change the contents of an
#' existing message with \code{showNotification}, or to remove it with
#' \code{removeNotification}. If not provided, one will be generated
#' automatically. If an ID is provided and there does not currently exist a
#' notification with that ID, a new notification will be created with that ID.
#' @param type A string which controls the color of the notification. One of
#' "default" (gray), "message" (blue), "warning" (yellow), or "error" (red).
#' @param session Session object to send notification to.
#'
#' @return An ID for the notification.
#'
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' # Show a message when button is clicked
#' shinyApp(
#' ui = fluidPage(
#' actionButton("show", "Show")
#' ),
#' server = function(input, output) {
#' observeEvent(input$show, {
#' showNotification("Message text",
#' action = a(href = "javascript:location.reload();", "Reload page")
#' )
#' })
#' }
#' )
#'
#' # App with show and remove buttons
#' shinyApp(
#' ui = fluidPage(
#' actionButton("show", "Show"),
#' actionButton("remove", "Remove")
#' ),
#' server = function(input, output) {
#' # A queue of notification IDs
#' ids <- character(0)
#' # A counter
#' n <- 0
#'
#' observeEvent(input$show, {
#' # Save the ID for removal later
#' id <- showNotification(paste("Message", n), duration = NULL)
#' ids <<- c(ids, id)
#' n <<- n + 1
#' })
#'
#' observeEvent(input$remove, {
#' if (length(ids) > 0)
#' removeNotification(ids[1])
#' ids <<- ids[-1]
#' })
#' }
#' )
#' }
#' @export
showNotification <- function(ui, action = NULL, duration = 5,
closeButton = TRUE, id = NULL,
type = c("default", "message", "warning", "error"),
session = getDefaultReactiveDomain())
{
if (is.null(id))
id <- createUniqueId(8)
res <- processDeps(ui, session)
actionRes <- processDeps(action, session)
session$sendNotification("show",
list(
html = res$html,
action = actionRes$html,
deps = c(res$deps, actionRes$deps),
duration = if (!is.null(duration)) duration * 1000,
closeButton = closeButton,
id = id,
type = match.arg(type)
)
)
id
}
#' @rdname showNotification
#' @export
removeNotification <- function(id = NULL, session = getDefaultReactiveDomain()) {
if (is.null(id)) {
stop("id is required.")
}
session$sendNotification("remove", id)
id
}

View File

@@ -12,6 +12,14 @@
#' method is called. Calling \code{close} will cause the progress panel
#' to be removed.
#'
#' As of version 0.14, the progress indicators use Shiny's new notification API.
#' If you want to use the old styling (for example, you may have used customized
#' CSS), you can use \code{style="old"} each time you call
#' \code{Progress$new()}. If you don't want to set the style each time
#' \code{Progress$new} is called, you can instead call
#' \code{\link{shinyOptions}(progress.style="old")} just once, inside the server
#' function.
#'
#' \strong{Methods}
#' \describe{
#' \item{\code{initialize(session, min = 0, max = 1)}}{
@@ -47,7 +55,10 @@
#' 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
#' (this is for backward-compatibility).
#' @param amount 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.
@@ -55,11 +66,16 @@
#' progress bar.
#'
#' @examples
#' \dontrun{
#' # server.R
#' shinyServer(function(input, output, session) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' plotOutput("plot")
#' )
#'
#' server <- function(input, output, session) {
#' output$plot <- renderPlot({
#' progress <- shiny::Progress$new(session, min=1, max=15)
#' progress <- Progress$new(session, min=1, max=15)
#' on.exit(progress$close())
#'
#' progress$set(message = 'Calculation in progress',
@@ -71,7 +87,9 @@
#' }
#' plot(cars)
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @seealso \code{\link{withProgress}}
#' @format NULL
@@ -79,21 +97,24 @@
#' @export
Progress <- R6Class(
'Progress',
portable = TRUE,
public = list(
initialize = function(session = getDefaultReactiveDomain(), min = 0, max = 1) {
if (!inherits(session, "ShinySession"))
initialize = function(session = getDefaultReactiveDomain(),
min = 0, max = 1,
style = getShinyOption("progress.style", default = "notification"))
{
if (is.null(session$progressStack))
stop("'session' is not a ShinySession object.")
private$session <- session
private$id <- paste(as.character(as.raw(stats::runif(8, min=0, max=255))), collapse='')
private$id <- createUniqueId(8)
private$min <- min
private$max <- max
private$value <- NULL
private$style <- match.arg(style, choices = c("notification", "old"))
private$closed <- FALSE
session$sendProgress('open', list(id = private$id))
session$sendProgress('open', list(id = private$id, style = private$style))
},
set = function(value = NULL, message = NULL, detail = NULL) {
@@ -102,27 +123,31 @@ 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,
detail = detail,
value = value
value = value,
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)
},
@@ -130,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) {
@@ -141,17 +163,20 @@ Progress <- R6Class(
return()
}
private$session$sendProgress('close', list(id = private$id))
private$session$sendProgress('close',
list(id = private$id, style = private$style)
)
private$closed <- TRUE
}
),
private = list(
session = 'environment',
session = 'ShinySession',
id = character(0),
min = numeric(0),
max = numeric(0),
value = NULL,
style = character(0),
value = numeric(0),
closed = logical(0)
)
)
@@ -179,6 +204,14 @@ Progress <- R6Class(
#' is not common) or otherwise cannot be encapsulated by a single scope. In that
#' case, you can use the \code{Progress} reference class.
#'
#' As of version 0.14, the progress indicators use Shiny's new notification API.
#' If you want to use the old styling (for example, you may have used customized
#' CSS), you can use \code{style="old"} each time you call
#' \code{withProgress()}. If you don't want to set the style each time
#' \code{withProgress} is called, you can instead call
#' \code{\link{shinyOptions}(progress.style="old")} just once, inside the server
#' function.
#'
#' @param session The Shiny session object, as provided by \code{shinyServer} to
#' the server function. The default is to automatically find the session by
#' using the current reactive domain.
@@ -199,14 +232,23 @@ Progress <- R6Class(
#' displayed to the user, or \code{NULL} to hide the current detail message
#' (if any). The detail message will be shown with a de-emphasized appearance
#' relative to \code{message}.
#' @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
#' (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
#' \dontrun{
#' # server.R
#' shinyServer(function(input, output) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' ui <- fluidPage(
#' plotOutput("plot")
#' )
#'
#' server <- function(input, output) {
#' output$plot <- renderPlot({
#' withProgress(message = 'Calculation in progress',
#' detail = 'This may take a while...', value = 0, {
@@ -217,24 +259,30 @@ Progress <- R6Class(
#' })
#' plot(cars)
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @seealso \code{\link{Progress}}
#' @rdname withProgress
#' @export
withProgress <- function(expr, min = 0, max = 1,
value = min + (max - min) * 0.1,
message = NULL, detail = NULL,
session = getDefaultReactiveDomain(),
env = parent.frame(), quoted = FALSE) {
value = min + (max - min) * 0.1,
message = NULL, detail = NULL,
style = getShinyOption("progress.style", default = "notification"),
session = getDefaultReactiveDomain(),
env = parent.frame(), quoted = FALSE)
{
if (!quoted)
expr <- substitute(expr)
if (!inherits(session, "ShinySession"))
if (is.null(session$progressStack))
stop("'session' is not a ShinySession object.")
p <- Progress$new(session, min = min, max = max)
style <- match.arg(style, c("notification", "old"))
p <- Progress$new(session, min = min, max = max, style = style)
session$progressStack$push(p)
on.exit({
@@ -252,7 +300,7 @@ withProgress <- function(expr, min = 0, max = 1,
setProgress <- function(value = NULL, message = NULL, detail = NULL,
session = getDefaultReactiveDomain()) {
if (!inherits(session, "ShinySession"))
if (is.null(session$progressStack))
stop("'session' is not a ShinySession object.")
if (session$progressStack$size() == 0) {
@@ -269,7 +317,7 @@ setProgress <- function(value = NULL, message = NULL, detail = NULL,
incProgress <- function(amount = 0.1, message = NULL, detail = NULL,
session = getDefaultReactiveDomain()) {
if (!inherits(session, "ShinySession"))
if (is.null(session$progressStack))
stop("'session' is not a ShinySession object.")
if (session$progressStack$size() == 0) {

View File

@@ -1,3 +1,21 @@
processId <- local({
# pid is not sufficient to uniquely identify a process, because
# distributed futures span machines which could introduce pid
# collisions.
cached <- NULL
function() {
if (is.null(cached)) {
cached <<- digest::digest(list(
Sys.info(),
Sys.time()
))
}
# Sys.getpid() cannot be cached because forked children will
# then have the same processId as their parents.
paste(cached, Sys.getpid())
}
})
Context <- R6Class(
'Context',
portable = FALSE,
@@ -9,27 +27,35 @@ Context <- R6Class(
.invalidateCallbacks = list(),
.flushCallbacks = list(),
.domain = NULL,
.pid = NULL,
initialize = function(domain, label='', type='other', prevId='') {
id <<- .getReactiveEnvironment()$nextId()
.label <<- label
.domain <<- domain
.pid <<- processId()
.graphCreateContext(id, label, type, prevId, domain)
},
run = function(func) {
"Run the provided function under this context."
withReactiveDomain(.domain, {
env <- .getReactiveEnvironment()
.graphEnterContext(id)
tryCatch(
env$runWith(self, func),
finally = .graphExitContext(id)
)
promises::with_promise_domain(reactivePromiseDomain(), {
withReactiveDomain(.domain, {
env <- .getReactiveEnvironment()
.graphEnterContext(id)
on.exit(.graphExitContext(id), add = TRUE)
env$runWith(self, func)
})
})
},
invalidate = function() {
"Invalidate this context. It will immediately call the callbacks
that have been registered with onInvalidate()."
if (!identical(.pid, processId())) {
stop("Reactive context was created in one process and invalidated from another")
}
if (.invalidated)
return()
.invalidated <<- TRUE
@@ -45,6 +71,11 @@ Context <- R6Class(
"Register a function to be called when this context is invalidated.
If this context is already invalidated, the function is called
immediately."
if (!identical(.pid, processId())) {
stop("Reactive context was created in one process and accessed from another")
}
if (.invalidated)
func()
else
@@ -62,8 +93,9 @@ Context <- R6Class(
},
executeFlushCallbacks = function() {
"For internal use only."
lapply(.flushCallbacks, function(func) {
func()
lapply(.flushCallbacks, function(flushCallback) {
flushCallback()
})
}
)
@@ -98,25 +130,33 @@ ReactiveEnvironment <- R6Class(
}
return(.currentContext)
},
runWith = function(ctx, func) {
runWith = function(ctx, contextFunc) {
old.ctx <- .currentContext
.currentContext <<- ctx
on.exit(.currentContext <<- old.ctx)
shinyCallingHandlers(func())
contextFunc()
},
addPendingFlush = function(ctx, priority) {
.pendingFlush$enqueue(ctx, priority)
},
hasPendingFlush = function() {
return(!.pendingFlush$isEmpty())
},
# Returns TRUE if anything was actually called
flush = function() {
# If nothing to flush, exit early
if (!hasPendingFlush()) return(invisible(FALSE))
# If already in a flush, don't start another one
if (.inFlush) return()
if (.inFlush) return(invisible(FALSE))
.inFlush <<- TRUE
on.exit(.inFlush <<- FALSE)
while (!.pendingFlush$isEmpty()) {
while (hasPendingFlush()) {
ctx <- .pendingFlush$dequeue()
ctx$executeFlushCallbacks()
}
invisible(TRUE)
}
)
)
@@ -130,9 +170,10 @@ ReactiveEnvironment <- R6Class(
}
})
# Causes any pending invalidations to run.
# Causes any pending invalidations to run. Returns TRUE if any invalidations
# were pending (i.e. if work was actually done).
flushReact <- function() {
.getReactiveEnvironment()$flush()
return(.getReactiveEnvironment()$flush())
}
# Retrieves the current reactive context, or errors if there is no reactive
@@ -152,3 +193,31 @@ local({
return(dummyContext)
}
})
wrapForContext <- function(func, ctx) {
force(func)
force(ctx)
function(...) {
ctx$run(function() {
captureStackTraces(
func(...)
)
})
}
}
reactivePromiseDomain <- function() {
promises::new_promise_domain(
wrapOnFulfilled = function(onFulfilled) {
force(onFulfilled)
ctx <- getCurrentContext()
wrapForContext(onFulfilled, ctx)
},
wrapOnRejected = function(onRejected) {
force(onRejected)
ctx <- getCurrentContext()
wrapForContext(onRejected, ctx)
}
)
}

View File

@@ -42,11 +42,11 @@ NULL
#
## ------------------------------------------------------------------------
createMockDomain <- function() {
callbacks <- list()
callbacks <- Callbacks$new()
ended <- FALSE
domain <- new.env(parent = emptyenv())
domain$onEnded <- function(callback) {
callbacks <<- c(callbacks, callback)
return(callbacks$register(callback))
}
domain$isEnded <- function() {
ended
@@ -55,10 +55,12 @@ createMockDomain <- function() {
domain$end <- function() {
if (!ended) {
ended <<- TRUE
lapply(callbacks, do.call, list())
callbacks$invoke()
}
invisible()
}
domain$incrementBusyCount <- function() NULL
domain$decrementBusyCount <- function() NULL
return(domain)
}
@@ -93,11 +95,7 @@ getDefaultReactiveDomain <- function() {
#' @rdname domains
#' @export
withReactiveDomain <- function(domain, expr) {
oldValue <- .globals$domain
.globals$domain <- domain
on.exit(.globals$domain <- oldValue)
expr
promises::with_promise_domain(createVarPromiseDomain(.globals, "domain", domain), expr)
}
#

File diff suppressed because it is too large Load Diff

588
R/render-cached-plot.R Normal file
View File

@@ -0,0 +1,588 @@
#' Plot output with cached images
#'
#' Renders a reactive plot, with plot images cached to disk.
#'
#' \code{expr} is an expression that generates a plot, similar to that in
#' \code{renderPlot}. Unlike with \code{renderPlot}, this expression does not
#' take reactive dependencies. It is re-executed only when the cache key
#' changes.
#'
#' \code{cacheKeyExpr} is an expression which, when evaluated, returns an object
#' which will be serialized and hashed using the \code{\link[digest]{digest}}
#' function to generate a string that will be used as a cache key. This key is
#' used to identify the contents of the plot: if the cache key is the same as a
#' previous time, it assumes that the plot is the same and can be retrieved from
#' the cache.
#'
#' This \code{cacheKeyExpr} is reactive, and so it will be re-evaluated when any
#' upstream reactives are invalidated. This will also trigger re-execution of
#' the plotting expression, \code{expr}.
#'
#' The key should consist of "normal" R objects, like vectors and lists. Lists
#' should in turn contain other normal R objects. If the key contains
#' environments, external pointers, or reference objects -- or even if it has
#' such objects attached as attributes -- then it is possible that it will
#' change unpredictably even when you do not expect it to. Additionally, because
#' the entire key is serialized and hashed, if it contains a very large object
#' -- a large data set, for example -- there may be a noticeable performance
#' penalty.
#'
#' If you face these issues with the cache key, you can work around them by
#' extracting out the important parts of the objects, and/or by converting them
#' to normal R objects before returning them. Your expression could even
#' serialize and hash that information in an efficient way and return a string,
#' which will in turn be hashed (very quickly) by the
#' \code{\link[digest]{digest}} function.
#'
#' Internally, the result from \code{cacheKeyExpr} is combined with the name of
#' the output (if you assign it to \code{output$plot1}, it will be combined
#' with \code{"plot1"}) to form the actual key that is used. As a result, even
#' if there are multiple plots that have the same \code{cacheKeyExpr}, they
#' will not have cache key collisions.
#'
#' @section Cache scoping:
#'
#' There are a number of different ways you may want to scope the cache. For
#' example, you may want each user session to have their own plot cache, or
#' you may want each run of the application to have a cache (shared among
#' possibly multiple simultaneous user sessions), or you may want to have a
#' cache that persists even after the application is shut down and started
#' again.
#'
#' To control the scope of the cache, use the \code{cache} parameter. There
#' are two ways of having Shiny automatically create and clean up the disk
#' cache.
#'
#' \describe{
#' \item{1}{To scope the cache to one run of a Shiny application (shared
#' among possibly multiple user sessions), use \code{cache="app"}. This
#' is the default. The cache will be shared across multiple sessions, so
#' there is potentially a large performance benefit if there are many users
#' of the application. When the application stops running, the cache will
#' be deleted. If plots cannot be safely shared across users, this should
#' not be used.}
#' \item{2}{To scope the cache to one session, use \code{cache="session"}.
#' When a new user session starts -- in other words, when a web browser
#' visits the Shiny application -- a new cache will be created on disk
#' for that session. When the session ends, the cache will be deleted.
#' The cache will not be shared across multiple sessions.}
#' }
#'
#' If either \code{"app"} or \code{"session"} is used, the cache will be 10 MB
#' in size, and will be stored stored in memory, using a
#' \code{\link{memoryCache}} object. Note that the cache space will be shared
#' among all cached plots within a single application or session.
#'
#' In some cases, you may want more control over the caching behavior. For
#' example, you may want to use a larger or smaller cache, share a cache
#' among multiple R processes, or you may want the cache to persist across
#' multiple runs of an application, or even across multiple R processes.
#'
#' To use different settings for an application-scoped cache, you can call
#' \code{\link{shinyOptions}()} at the top of your app.R, server.R, or
#' global.R. For example, this will create a cache with 20 MB of space
#' instead of the default 10 MB:
#' \preformatted{
#' shinyOptions(cache = memoryCache(size = 20e6))
#' }
#'
#' To use different settings for a session-scoped cache, you can call
#' \code{\link{shinyOptions}()} at the top of your server function. To use
#' the session-scoped cache, you must also call \code{renderCachedPlot} with
#' \code{cache="session"}. This will create a 20 MB cache for the session:
#' \preformatted{
#' function(input, output, session) {
#' shinyOptions(cache = memoryCache(size = 20e6))
#'
#' output$plot <- renderCachedPlot(
#' ...,
#' cache = "session"
#' )
#' }
#' }
#'
#' If you want to create a cache that is shared across multiple concurrent
#' R processes, you can use a \code{\link{diskCache}}. You can create an
#' application-level shared cache by putting this at the top of your app.R,
#' server.R, or global.R:
#' \preformatted{
#' shinyOptions(cache = diskCache(file.path(dirname(tempdir()), "myapp-cache"))
#' }
#'
#' This will create a subdirectory in your system temp directory named
#' \code{myapp-cache} (replace \code{myapp-cache} with a unique name of
#' your choosing). On most platforms, this directory will be removed when
#' your system reboots. This cache will persist across multiple starts and
#' stops of the R process, as long as you do not reboot.
#'
#' To have the cache persist even across multiple reboots, you can create the
#' cache in a location outside of the temp directory. For example, it could
#' be a subdirectory of the application:
#' \preformatted{
#' shinyOptions(cache = diskCache("./myapp-cache"))
#' }
#'
#' In this case, resetting the cache will have to be done manually, by deleting
#' the directory.
#'
#' You can also scope a cache to just one plot, or selected plots. To do that,
#' create a \code{\link{memoryCache}} or \code{\link{diskCache}}, and pass it
#' as the \code{cache} argument of \code{renderCachedPlot}.
#'
#' @section Interactive plots:
#'
#' \code{renderCachedPlot} can be used to create interactive plots. See
#' \code{\link{plotOutput}} for more information and examples.
#'
#'
#' @inheritParams renderPlot
#' @param cacheKeyExpr An expression that returns a cache key. This key should
#' be a unique identifier for a plot: the assumption is that if the cache key
#' is the same, then the plot will be the same.
#' @param sizePolicy A function that takes two arguments, \code{width} and
#' \code{height}, and returns a list with \code{width} and \code{height}. The
#' purpose is to round the actual pixel dimensions from the browser to some
#' other dimensions, so that this will not generate and cache images of every
#' possible pixel dimension. See \code{\link{sizeGrowthRatio}} for more
#' information on the default sizing policy.
#' @param res The resolution of the PNG, in pixels per inch.
#' @param cache The scope of the cache, or a cache object. This can be
#' \code{"app"} (the default), \code{"session"}, or a cache object like
#' a \code{\link{diskCache}}. See the Cache Scoping section for more
#' information.
#'
#' @seealso See \code{\link{renderPlot}} for the regular, non-cached version of
#' this function. For more about configuring caches, see
#' \code{\link{memoryCache}} and \code{\link{diskCache}}.
#'
#'
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' # A basic example that uses the default app-scoped memory cache.
#' # The cache will be shared among all simultaneous users of the application.
#' shinyApp(
#' fluidPage(
#' sidebarLayout(
#' sidebarPanel(
#' sliderInput("n", "Number of points", 4, 32, value = 8, step = 4)
#' ),
#' mainPanel(plotOutput("plot"))
#' )
#' ),
#' function(input, output, session) {
#' output$plot <- renderCachedPlot({
#' Sys.sleep(2) # Add an artificial delay
#' seqn <- seq_len(input$n)
#' plot(mtcars$wt[seqn], mtcars$mpg[seqn],
#' xlim = range(mtcars$wt), ylim = range(mtcars$mpg))
#' },
#' cacheKeyExpr = { list(input$n) }
#' )
#' }
#' )
#'
#'
#'
#' # An example uses a data object shared across sessions. mydata() is part of
#' # the cache key, so when its value changes, plots that were previously
#' # stored in the cache will no longer be used (unless mydata() changes back
#' # to its previous value).
#' mydata <- reactiveVal(data.frame(x = rnorm(400), y = rnorm(400)))
#'
#' ui <- fluidPage(
#' sidebarLayout(
#' sidebarPanel(
#' sliderInput("n", "Number of points", 50, 400, 100, step = 50),
#' actionButton("newdata", "New data")
#' ),
#' mainPanel(
#' plotOutput("plot")
#' )
#' )
#' )
#'
#' server <- function(input, output, session) {
#' observeEvent(input$newdata, {
#' mydata(data.frame(x = rnorm(400), y = rnorm(400)))
#' })
#'
#' output$plot <- renderCachedPlot(
#' {
#' Sys.sleep(2)
#' d <- mydata()
#' seqn <- seq_len(input$n)
#' plot(d$x[seqn], d$y[seqn], xlim = range(d$x), ylim = range(d$y))
#' },
#' cacheKeyExpr = { list(input$n, mydata()) },
#' )
#' }
#'
#' shinyApp(ui, server)
#'
#'
#' # A basic application with two plots, where each plot in each session has
#' # a separate cache.
#' shinyApp(
#' fluidPage(
#' sidebarLayout(
#' sidebarPanel(
#' sliderInput("n", "Number of points", 4, 32, value = 8, step = 4)
#' ),
#' mainPanel(
#' plotOutput("plot1"),
#' plotOutput("plot2")
#' )
#' )
#' ),
#' function(input, output, session) {
#' output$plot1 <- renderCachedPlot({
#' Sys.sleep(2) # Add an artificial delay
#' seqn <- seq_len(input$n)
#' plot(mtcars$wt[seqn], mtcars$mpg[seqn],
#' xlim = range(mtcars$wt), ylim = range(mtcars$mpg))
#' },
#' cacheKeyExpr = { list(input$n) },
#' cache = memoryCache()
#' )
#' output$plot2 <- renderCachedPlot({
#' Sys.sleep(2) # Add an artificial delay
#' seqn <- seq_len(input$n)
#' plot(mtcars$wt[seqn], mtcars$mpg[seqn],
#' xlim = range(mtcars$wt), ylim = range(mtcars$mpg))
#' },
#' cacheKeyExpr = { list(input$n) },
#' cache = memoryCache()
#' )
#' }
#' )
#'
#' }
#'
#' \dontrun{
#' # At the top of app.R, this set the application-scoped cache to be a memory
#' # cache that is 20 MB in size, and where cached objects expire after one
#' # hour.
#' shinyOptions(cache = memoryCache(max_size = 20e6, max_age = 3600))
#'
#' # At the top of app.R, this set the application-scoped cache to be a disk
#' # cache that can be shared among multiple concurrent R processes, and is
#' # deleted when the system reboots.
#' shinyOptions(cache = diskCache(file.path(dirname(tempdir()), "myapp-cache"))
#'
#' # At the top of app.R, this set the application-scoped cache to be a disk
#' # cache that can be shared among multiple concurrent R processes, and
#' # persists on disk across reboots.
#' shinyOptions(cache = diskCache("./myapp-cache"))
#'
#' # At the top of the server function, this set the session-scoped cache to be
#' # a memory cache that is 5 MB in size.
#' server <- function(input, output, session) {
#' shinyOptions(cache = memoryCache(max_size = 5e6))
#'
#' output$plot <- renderCachedPlot(
#' ...,
#' cache = "session"
#' )
#' }
#'
#' }
#' @export
renderCachedPlot <- function(expr,
cacheKeyExpr,
sizePolicy = sizeGrowthRatio(width = 400, height = 400, growthRate = 1.2),
res = 72,
cache = "app",
...,
outputArgs = list()
) {
# This ..stacktraceon is matched by a ..stacktraceoff.. when plotFunc
# is called
installExprFunction(expr, "func", parent.frame(), quoted = FALSE, ..stacktraceon = TRUE)
# This is so that the expr doesn't re-execute by itself; it needs to be
# triggered by the cache key (or width/height) changing.
isolatedFunc <- function() isolate(func())
args <- list(...)
cacheKeyExpr <- substitute(cacheKeyExpr)
# The real cache key we'll use also includes width, height, res, pixelratio.
# This is just the part supplied by the user.
userCacheKey <- reactive(cacheKeyExpr, env = parent.frame(), quoted = TRUE, label = "userCacheKey")
ensureCacheSetup <- function() {
# For our purposes, cache objects must support these methods.
isCacheObject <- function(x) {
# Use tryCatch in case the object does not support `$`.
tryCatch(
is.function(x$get) && is.function(x$set),
error = function(e) FALSE
)
}
if (isCacheObject(cache)) {
# If `cache` is already a cache object, do nothing
return()
} else if (identical(cache, "app")) {
cache <<- getShinyOption("cache")
} else if (identical(cache, "session")) {
cache <<- session$cache
} else {
stop('`cache` must either be "app", "session", or a cache object with methods, `$get`, and `$set`.')
}
}
# The width and height of the plot to draw, given from sizePolicy. These
# values get filled by an observer below.
fitDims <- reactiveValues(width = NULL, height = NULL)
resizeObserver <- NULL
ensureResizeObserver <- function() {
if (!is.null(resizeObserver))
return()
# Given the actual width/height of the image in the browser, this gets the
# width/height from sizePolicy() and pushes those values into `fitDims`.
# It's done this way so that the `fitDims` only change (and cause
# invalidations) when the rendered image size changes, and not every time
# the browser's <img> tag changes size.
doResizeCheck <- function() {
width <- session$clientData[[paste0('output_', outputName, '_width')]]
height <- session$clientData[[paste0('output_', outputName, '_height')]]
if (is.null(width)) width <- 0
if (is.null(height)) height <- 0
rect <- sizePolicy(c(width, height))
fitDims$width <- rect[1]
fitDims$height <- rect[2]
}
# Run it once immediately, then set up the observer
isolate(doResizeCheck())
resizeObserver <<- observe(doResizeCheck())
}
# Vars to store session and output, so that they can be accessed from
# the plotObj() reactive.
session <- NULL
outputName <- NULL
drawReactive <- reactive(label = "plotObj", {
hybrid_chain(
# Depend on the user cache key, even though we don't use the value. When
# it changes, it can cause the drawReactive to re-execute. (Though
# drawReactive will not necessarily re-execute -- it must be called from
# renderFunc, which happens only if there's a cache miss.)
userCacheKey(),
function(userCacheKeyValue) {
# Get width/height, but don't depend on them.
isolate({
width <- fitDims$width
height <- fitDims$height
})
pixelratio <- session$clientData$pixelratio %OR% 1
do.call("drawPlot", c(
list(
name = outputName,
session = session,
func = isolatedFunc,
width = width,
height = height,
pixelratio = pixelratio,
res = res
),
args
))
},
catch = function(reason) {
# Non-isolating read. A common reason for errors in plotting is because
# the dimensions are too small. By taking a dependency on width/height,
# we can try again if the plot output element changes size.
fitDims$width
fitDims$height
# Propagate the error
stop(reason)
}
)
})
# This function is the one that's returned from renderPlot(), and gets
# wrapped in an observer when the output value is assigned.
renderFunc <- function(shinysession, name, ...) {
outputName <<- name
session <<- shinysession
ensureCacheSetup()
ensureResizeObserver()
hybrid_chain(
# This use of the userCacheKey() sets up the reactive dependency that
# causes plot re-draw events. These may involve pulling from the cache,
# replaying a display list, or re-executing user code.
userCacheKey(),
function(userCacheKeyResult) {
width <- fitDims$width
height <- fitDims$height
pixelratio <- session$clientData$pixelratio %OR% 1
key <- digest::digest(list(outputName, userCacheKeyResult, width, height, res, pixelratio), "xxhash64")
plotObj <- cache$get(key)
# First look in cache.
# Case 1. cache hit.
if (!is.key_missing(plotObj)) {
return(list(
cacheHit = TRUE,
key = key,
plotObj = plotObj,
width = width,
height = height,
pixelratio = pixelratio
))
}
# If not in cache, hybrid_chain call to drawReactive
#
# Two more possible cases:
# 2. drawReactive will re-execute and return a plot that's the
# correct size.
# 3. It will not re-execute, but it will return the previous value,
# which is the wrong size. It will include a valid display list
# which can be used by resizeSavedPlot.
hybrid_chain(
drawReactive(),
function(drawReactiveResult) {
# Pass along the key for caching in the next stage
list(
cacheHit = FALSE,
key = key,
plotObj = drawReactiveResult,
width = width,
height = height,
pixelratio = pixelratio
)
}
)
},
function(result) {
width <- result$width
height <- result$height
pixelratio <- result$pixelratio
# Three possibilities when we get here:
# 1. There was a cache hit. No need to set a value in the cache.
# 2. There was a cache miss, and the plotObj is already the correct
# size (because drawReactive re-executed). In this case, we need
# to cache it.
# 3. There was a cache miss, and the plotObj was not the corect size.
# In this case, we need to replay the display list, and then cache
# the result.
if (!result$cacheHit) {
# If the image is already the correct size, this just returns the
# object unchanged.
result$plotObj <- do.call("resizeSavedPlot", c(
list(
name,
shinysession,
result$plotObj,
width,
height,
pixelratio,
res
),
args
))
# Save a cached copy of the plotObj. The recorded displaylist for
# the plot can't be serialized and restored properly within the same
# R session, so we NULL it out before saving. (The image data and
# other metadata be saved and restored just fine.) Displaylists can
# also be very large (~1.5MB for a basic ggplot), and they would not
# be commonly used. Note that displaylist serialization was fixed in
# revision 74506 (2e6c669), and should be in R 3.6. A MemoryCache
# doesn't need to serialize objects, so it could actually save a
# display list, but for the reasons listed previously, it's
# generally not worth it.
# The plotResult is not the same as the recordedPlot (it is used to
# retrieve coordmap information for ggplot2 objects) but it is only
# used in conjunction with the recordedPlot, and we'll remove it
# because it can be quite large.
result$plotObj$plotResult <- NULL
result$plotObj$recordedPlot <- NULL
cache$set(result$key, result$plotObj)
}
img <- result$plotObj$img
# Replace exact pixel dimensions; instead, the max-height and
# max-width will be set to 100% from CSS.
img$class <- "shiny-scalable"
img$width <- NULL
img$height <- NULL
img
}
)
}
# If renderPlot isn't going to adapt to the height of the div, then the
# div needs to adapt to the height of renderPlot. By default, plotOutput
# sets the height to 400px, so to make it adapt we need to override it
# with NULL.
outputFunc <- plotOutput
formals(outputFunc)['height'] <- list(NULL)
markRenderFunction(outputFunc, renderFunc, outputArgs = outputArgs)
}
#' Create a sizing function that grows at a given ratio
#'
#' Returns a function which takes a two-element vector representing an input
#' width and height, and returns a two-element vector of width and height. The
#' possible widths are the base width times the growthRate to any integer power.
#' For example, with a base width of 500 and growth rate of 1.25, the possible
#' widths include 320, 400, 500, 625, 782, and so on, both smaller and larger.
#' Sizes are rounded up to the next pixel. Heights are computed the same way as
#' widths.
#'
#' @param width,height Base width and height.
#' @param growthRate Growth rate multiplier.
#'
#' @seealso This is to be used with \code{\link{renderCachedPlot}}.
#'
#' @examples
#' f <- sizeGrowthRatio(500, 500, 1.25)
#' f(c(400, 400))
#' f(c(500, 500))
#' f(c(530, 550))
#' f(c(625, 700))
#'
#' @export
sizeGrowthRatio <- function(width = 400, height = 400, growthRate = 1.2) {
round_dim_up <- function(x, base, rate) {
power <- ceiling(log(x / base, rate))
ceiling(base * rate^power)
}
function(dims) {
if (length(dims) != 2) {
stop("dims must be a vector with two numbers, for width and height.")
}
c(
round_dim_up(dims[1], width, growthRate),
round_dim_up(dims[2], height, growthRate)
)
}
}

File diff suppressed because it is too large Load Diff

228
R/render-table.R Normal file
View File

@@ -0,0 +1,228 @@
#' Table Output
#'
#' Creates a reactive table that is suitable for assigning to an \code{output}
#' slot.
#'
#' The corresponding HTML output tag should be \code{div} and have the CSS
#' class name \code{shiny-html-output}.
#'
#' @param expr An expression that returns an R object that can be used with
#' \code{\link[xtable]{xtable}}.
#' @param striped,hover,bordered Logicals: if \code{TRUE}, apply the
#' corresponding Bootstrap table format to the output table.
#' @param spacing The spacing between the rows of the table (\code{xs}
#' stands for "extra small", \code{s} for "small", \code{m} for "medium"
#' and \code{l} for "large").
#' @param width Table width. Must be a valid CSS unit (like "100%", "400px",
#' "auto") or a number, which will be coerced to a string and
#' have "px" appended.
#' @param align A string that specifies the column alignment. If equal to
#' \code{'l'}, \code{'c'} or \code{'r'}, then all columns will be,
#' respectively, left-, center- or right-aligned. Otherwise, \code{align}
#' must have the same number of characters as the resulting table (if
#' \code{rownames = TRUE}, this will be equal to \code{ncol()+1}), with
#' the \emph{i}-th character specifying the alignment for the
#' \emph{i}-th column (besides \code{'l'}, \code{'c'} and
#' \code{'r'}, \code{'?'} is also permitted - \code{'?'} is a placeholder
#' for that particular column, indicating that it should keep its default
#' alignment). If \code{NULL}, then all numeric/integer columns (including
#' the row names, if they are numbers) will be right-aligned and
#' everything else will be left-aligned (\code{align = '?'} produces the
#' same result).
#' @param rownames,colnames Logicals: include rownames? include colnames
#' (column headers)?
#' @param digits An integer specifying the number of decimal places for
#' the numeric columns (this will not apply to columns with an integer
#' class). If \code{digits} is set to a negative value, then the numeric
#' columns will be displayed in scientific format with a precision of
#' \code{abs(digits)} digits.
#' @param na The string to use in the table cells whose values are missing
#' (i.e. they either evaluate to \code{NA} or \code{NaN}).
#' @param ... Arguments to be passed through to \code{\link[xtable]{xtable}}
#' and \code{\link[xtable]{print.xtable}}.
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})?
#' This is useful if you want to save an expression in a variable.
#' @param outputArgs A list of arguments to be passed through to the
#' implicit call to \code{\link{tableOutput}} when \code{renderTable} is
#' used in an interactive R Markdown document.
#' @export
renderTable <- function(expr, striped = FALSE, hover = FALSE,
bordered = FALSE, spacing = c("s", "xs", "m", "l"),
width = "auto", align = NULL,
rownames = FALSE, colnames = TRUE,
digits = NULL, na = "NA", ...,
env = parent.frame(), quoted = FALSE,
outputArgs=list()) {
installExprFunction(expr, "func", env, quoted)
if (!is.function(spacing)) spacing <- match.arg(spacing)
# A small helper function to create a wrapper for an argument that was
# passed to renderTable()
createWrapper <- function(arg) {
if (is.function(arg)) wrapper <- arg
else wrapper <- function() arg
return(wrapper)
}
# Create wrappers for most arguments so that functions can also be passed
# in, rather than only literals (useful for shiny apps)
stripedWrapper <- createWrapper(striped)
hoverWrapper <- createWrapper(hover)
borderedWrapper <- createWrapper(bordered)
spacingWrapper <- createWrapper(spacing)
widthWrapper <- createWrapper(width)
alignWrapper <- createWrapper(align)
rownamesWrapper <- createWrapper(rownames)
colnamesWrapper <- createWrapper(colnames)
digitsWrapper <- createWrapper(digits)
naWrapper <- createWrapper(na)
dots <- list(...) ## used later (but defined here because of scoping)
createRenderFunction(
func,
function(data, session, name, ...) {
striped <- stripedWrapper()
hover <- hoverWrapper()
bordered <- borderedWrapper()
format <- c(striped = striped, hover = hover, bordered = bordered)
spacing <- spacingWrapper()
width <- widthWrapper()
align <- alignWrapper()
rownames <- rownamesWrapper()
colnames <- colnamesWrapper()
digits <- digitsWrapper()
na <- naWrapper()
spacing_choices <- c("s", "xs", "m", "l")
if (!(spacing %in% spacing_choices)) {
stop(paste("`spacing` must be one of",
paste0("'", spacing_choices, "'", collapse=", ")))
}
# For css styling
classNames <- paste0("table shiny-table",
paste0(" table-", names(format)[format], collapse = "" ),
paste0(" spacing-", spacing))
data <- as.data.frame(data)
# Return NULL if no data is provided
if (is.null(data) ||
(is.data.frame(data) && nrow(data) == 0 && ncol(data) == 0))
return(NULL)
# Separate the ... args to pass to xtable() vs print.xtable()
xtable_argnames <- setdiff(names(formals(xtable)), c("x", "..."))
xtable_args <- dots[intersect(names(dots), xtable_argnames)]
non_xtable_args <- dots[setdiff(names(dots), xtable_argnames)]
# By default, numbers are right-aligned and everything else is left-aligned.
defaultAlignment <- function(col) {
if (is.numeric(col)) "r" else "l"
}
# Figure out column alignment
## Case 1: default alignment
if (is.null(align) || align == "?") {
names <- defaultAlignment(attr(data, "row.names"))
cols <- paste(vapply(data, defaultAlignment, character(1)), collapse = "")
cols <- paste0(names, cols)
} else {
## Case 2: user-specified alignment
num_cols <- if (rownames) nchar(align) else nchar(align)+1
valid <- !grepl("[^lcr\\?]", align)
if (num_cols == ncol(data)+1 && valid) {
cols <- if (rownames) align else paste0("r", align)
defaults <- grep("\\?", strsplit(cols,"")[[1]])
if (length(defaults) != 0) {
vals <- vapply(data[,defaults-1], defaultAlignment, character(1))
for (i in seq_len(length(defaults))) {
substr(cols, defaults[i], defaults[i]) <- vals[i]
}
}
} else if (nchar(align) == 1 && valid) {
cols <- paste0(rep(align, ncol(data)+1), collapse="")
} else {
stop("`align` must contain only the characters `l`, `c`, `r` and/or `?` and",
"have length either equal to 1 or to the total number of columns")
}
}
# Call xtable with its (updated) args
xtable_args <- c(xtable_args, align = cols, digits = digits)
xtable_res <- do.call(xtable, c(list(data), xtable_args))
# Set up print args
print_args <- list(
x = xtable_res,
type = 'html',
include.rownames = {
if ("include.rownames" %in% names(dots)) dots$include.rownames
else rownames
},
include.colnames = {
if ("include.colnames" %in% names(dots)) dots$include.colnames
else colnames
},
NA.string = {
if ("NA.string" %in% names(dots)) dots$NA.string
else na
},
html.table.attributes =
paste0({
if ("html.table.attributes" %in% names(dots)) dots$html.table.attributes
else ""
}, " ",
"class = '", htmlEscape(classNames, TRUE), "' ",
"style = 'width:", validateCssUnit(width), ";'"),
comment = {
if ("comment" %in% names(dots)) dots$comment
else FALSE
}
)
print_args <- c(print_args, non_xtable_args)
print_args <- print_args[unique(names(print_args))]
# Capture the raw html table returned by print.xtable(), and store it in
# a variable for further processing
tab <- paste(utils::capture.output(do.call(print, print_args)),collapse = "\n")
# Add extra class to cells with NA value, to be able to style them separately
tab <- gsub(paste(">", na, "<"), paste(" class='NA'>", na, "<"), tab)
# All further processing concerns the table headers, so we don't need to run
# any of this if colnames=FALSE
if (colnames) {
# Make sure that the final html table has a proper header (not included
# in the print.xtable() default)
tab <- sub("<tr>", "<thead> <tr>", tab)
tab <- sub("</tr>", "</tr> </thead> <tbody>", tab)
tab <- sub("</table>$", "</tbody> </table>", tab)
# Update the `cols` string (which stores the alignment of each column) so
# that it only includes the alignment for the table variables (and not
# for the row.names)
cols <- if (rownames) cols else substr(cols, 2, nchar(cols))
# Create a vector whose i-th entry corresponds to the i-th table variable
# alignment (substituting "l" by "left", "c" by "center" and "r" by "right")
cols <- strsplit(cols, "")[[1]]
cols[cols == "l"] <- "left"
cols[cols == "r"] <- "right"
cols[cols == "c"] <- "center"
# Align each header accordingly (this guarantees that each header and its
# corresponding column have the same alignment)
for (i in seq_len(length(cols))) {
tab <- sub("<th>", paste0("<th style='text-align: ", cols[i], ";'>"), tab)
}
}
return(tab)
},
tableOutput, outputArgs
)
}

View File

@@ -22,7 +22,7 @@
#' @export
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' if (interactive()) {
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
#'
#' # Can run an app from a subdirectory in the archive

91
R/serializers.R Normal file
View File

@@ -0,0 +1,91 @@
#' Add a function for serializing an input before bookmarking application state
#'
#' @param inputId Name of the input value.
#' @param fun A function that takes the input value and returns a modified
#' value. The returned value will be used for the test snapshot.
#' @param session A Shiny session object.
#'
#' @keywords internal
#' @export
setSerializer <- function(inputId, fun, session = getDefaultReactiveDomain()) {
if (is.null(session)) {
stop("setSerializer() needs a session object.")
}
input_impl <- .subset2(session$input, "impl")
input_impl$setMeta(inputId, "shiny.serializer", fun)
}
# For most types of values, simply return the value unchanged.
serializerDefault <- function(value, stateDir) {
value
}
serializerFileInput <- function(value, stateDir = NULL) {
# File inputs can be serialized only if there's a stateDir
if (is.null(stateDir)) {
return(serializerUnserializable())
}
# value is a data frame. When persisting files, we need to copy the file to
# the persistent dir and then strip the original path before saving.
newpaths <- file.path(stateDir, basename(value$datapath))
file.copy(value$datapath, newpaths, overwrite = TRUE)
value$datapath <- basename(newpaths)
value
}
# Return a sentinel value that represents "unserializable". This is applied to
# for example, passwords and actionButtons.
serializerUnserializable <- function(value, stateDir) {
structure(
list(),
serializable = FALSE
)
}
# Is this an "unserializable" sentinel value?
isUnserializable <- function(x) {
identical(
attr(x, "serializable", exact = TRUE),
FALSE
)
}
# Given a reactiveValues object and optional directory for saving state, apply
# serializer function to each of the values, and return a list of the returned
# values. This function passes stateDir to the serializer functions, so if
# stateDir is non-NULL, it can have a side effect of writing values to disk (in
# stateDir).
serializeReactiveValues <- function(values, exclude, stateDir = NULL) {
impl <- .subset2(values, "impl")
# Get named list where keys and values are the names of inputs; we'll retrieve
# actual values later.
vals <- isolate(impl$names())
vals <- setdiff(vals, exclude)
names(vals) <- vals
# Get values and apply serializer functions
vals <- lapply(vals, function(name) {
val <- impl$get(name)
# Get the serializer function for this input value. If none specified, use
# the default.
serializer_fun <- impl$getMeta(name, "shiny.serializer")
if (is.null(serializer_fun))
serializer_fun <- serializerDefault
# Apply serializer function.
serializer_fun(val, stateDir)
})
# Filter out any values that were marked as unserializable.
vals <- Filter(Negate(isUnserializable), vals)
vals
}

View File

@@ -71,6 +71,63 @@ removeInputHandler <- function(type){
inputHandlers$remove(type)
}
# Apply input handler to a single input value
applyInputHandler <- function(name, val, shinysession) {
splitName <- strsplit(name, ':')[[1]]
if (length(splitName) > 1) {
if (!inputHandlers$containsKey(splitName[[2]])) {
# No input handler registered for this type
stop("No handler registered for type ", name)
}
inputName <- splitName[[1]]
# Get the function for processing this type of input
inputHandler <- inputHandlers$get(splitName[[2]])
return(inputHandler(val, shinysession, inputName))
} else if (is.list(val) && is.null(names(val))) {
return(unlist(val, recursive = TRUE))
} else {
return(val)
}
}
#' Apply input handlers to raw input values
#'
#' The purpose of this function is to make it possible for external packages to
#' test Shiny inputs. It takes a named list of raw input values, applies input
#' handlers to those values, and then returns a named list of the processed
#' values.
#'
#' The raw input values should be in a named list. Some values may have names
#' 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.
#'
#' @param inputs A named list of input values.
#' @param shinysession A Shiny session object.
#'
#' @seealso registerInputHandler
#' @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(
names(inputs),
function(name) { strsplit(name, ":")[[1]][1] },
FUN.VALUE = character(1)
)
inputs
}
# Takes a list-of-lists and returns a matrix. The lists
# must all be the same length. NULL is replaced by NA.
registerInputHandler("shiny.matrix", function(data, ...) {
@@ -85,14 +142,34 @@ registerInputHandler("shiny.matrix", function(data, ...) {
return(m)
})
registerInputHandler("shiny.number", function(val, ...){
ifelse(is.null(val), NA, val)
})
registerInputHandler("shiny.password", function(val, shinysession, name) {
# Mark passwords as not serializable
setSerializer(name, serializerUnserializable)
val
})
registerInputHandler("shiny.date", function(val, ...){
# First replace NULLs with NA, then convert to Date vector
datelist <- ifelse(lapply(val, is.null), NA, val)
as.Date(unlist(datelist))
res <- NULL
tryCatch({
res <- as.Date(unlist(datelist))
},
error = function(e) {
# It's possible for client to send a string like "99999-01-01", which
# as.Date can't handle.
warning(e$message)
res <<- as.Date(rep(NA, length(datelist)))
}
)
res
})
registerInputHandler("shiny.datetime", function(val, ...){
@@ -104,8 +181,61 @@ registerInputHandler("shiny.datetime", function(val, ...){
as.POSIXct(unlist(times), origin = "1970-01-01", tz = "UTC")
})
registerInputHandler("shiny.action", function(val, ...) {
registerInputHandler("shiny.action", function(val, shinysession, name) {
# mark up the action button value with a special class so we can recognize it later
class(val) <- c(class(val), "shinyActionButtonValue")
val
})
registerInputHandler("shiny.file", function(val, shinysession, name) {
# This function is only used when restoring a Shiny fileInput. When a file is
# uploaded the usual way, it takes a different code path and won't hit this
# function.
if (is.null(val))
return(NULL)
# The data will be a named list of lists; convert to a data frame.
val <- as.data.frame(lapply(val, unlist), stringsAsFactors = FALSE)
# `val$datapath` should be a filename without a path, for security reasons.
if (basename(val$datapath) != val$datapath) {
stop("Invalid '/' found in file input path.")
}
# Prepend the persistent dir
oldfile <- file.path(getCurrentRestoreContext()$dir, val$datapath)
# Copy the original file to a new temp dir, so that a restored session can't
# modify the original.
newdir <- file.path(tempdir(), createUniqueId(12))
dir.create(newdir)
val$datapath <- file.path(newdir, val$datapath)
file.copy(oldfile, val$datapath)
# Need to mark this input value with the correct serializer. When a file is
# uploaded the usual way (instead of being restored), this occurs in
# session$`@uploadEnd`.
setSerializer(name, serializerFileInput)
snapshotPreprocessInput(name, snapshotPreprocessorFileInput)
val
})
# to be used with !!!answer
registerInputHandler("shiny.symbolList", function(val, ...) {
if (is.null(val)) {
list()
} else {
lapply(val, as.symbol)
}
})
# to be used with !!answer
registerInputHandler("shiny.symbol", function(val, ...) {
if (is.null(val) || identical(val, "")) {
NULL
} else {
as.symbol(val)
}
})

View File

@@ -1,6 +1,7 @@
#' @include server-input-handlers.R
appsByToken <- Map$new()
appsNeedingFlush <- Map$new()
# Provide a character representation of the WS that can be used
# as a key in a Map.
@@ -34,7 +35,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
@@ -49,25 +50,26 @@ registerClient <- function(client) {
#'
#' @examples
#' addResourcePath('datasets', system.file('data', package='datasets'))
#'
#' @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")
}
if (prefix %in% c('shared')) {
stop("addResourcePath called with the reserved prefix '", prefix, "'; ",
"please use a different prefix")
}
directoryPath <- normalizePath(directoryPath, mustWork=TRUE)
existing <- .globals$resources[[prefix]]
.globals$resources[[prefix]] <- list(directoryPath=directoryPath,
func=staticHandler(directoryPath))
normalizedPath <- tryCatch(normalizePath(directoryPath, mustWork = TRUE),
error = function(e) {
stop("Couldn't normalize path in `addResourcePath`, with arguments: ",
"`prefix` = '", prefix, "'; `directoryPath` = '" , directoryPath, "'")
}
)
.globals$resources[[prefix]] <- list(
directoryPath = normalizedPath,
func = staticHandler(normalizedPath)
)
}
resourcePathHandler <- function(req) {
@@ -141,7 +143,6 @@ resourcePathHandler <- function(req) {
#' })
#' }
#' }
#'
#' @export
shinyServer <- function(func) {
.globals$server <- list(func)
@@ -157,7 +158,7 @@ decodeMessage <- function(data) {
# Treat message as UTF-8
charData <- rawToChar(data)
Encoding(charData) <- 'UTF-8'
return(jsonlite::fromJSON(charData, simplifyVector=FALSE))
return(safeFromJSON(charData, simplifyVector=FALSE))
}
i <- 5
@@ -202,151 +203,141 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
return(TRUE)
}
if (!is.null(getOption("shiny.observer.error", NULL))) {
warning(
call. = FALSE,
"options(shiny.observer.error) is no longer supported; please unset it!"
)
stopApp()
}
shinysession <- ShinySession$new(ws)
appsByToken$set(shinysession$token, shinysession)
shinysession$setShowcase(.globals$showcaseDefault)
ws$onMessage(function(binary, msg) {
# To ease transition from websockets-based code. Should remove once we're stable.
if (is.character(msg))
msg <- charToRaw(msg)
messageHandler <- function(binary, msg) {
withReactiveDomain(shinysession, {
# To ease transition from websockets-based code. Should remove once we're stable.
if (is.character(msg))
msg <- charToRaw(msg)
if (isTRUE(getOption('shiny.trace'))) {
if (binary)
message("RECV ", '$$binary data$$')
else
message("RECV ", rawToChar(msg))
}
traceOption <- getOption('shiny.trace', FALSE)
if (isTRUE(traceOption) || traceOption == "recv") {
if (binary)
message("RECV ", '$$binary data$$')
else
message("RECV ", rawToChar(msg))
}
if (identical(charToRaw("\003\xe9"), msg))
return()
if (isEmptyMessage(msg))
return()
msg <- decodeMessage(msg)
msg <- decodeMessage(msg)
# Do our own list simplifying here. sapply/simplify2array give names to
# character vectors, which is rarely what we want.
if (!is.null(msg$data)) {
for (name in names(msg$data)) {
val <- msg$data[[name]]
splitName <- strsplit(name, ':')[[1]]
if (length(splitName) > 1) {
msg$data[[name]] <- NULL
if (!inputHandlers$containsKey(splitName[[2]])){
# No input handler registered for this type
stop("No handler registered for for type ", name)
}
msg$data[[ splitName[[1]] ]] <-
inputHandlers$get(splitName[[2]])(
val,
shinysession,
splitName[[1]] )
}
else if (is.list(val) && is.null(names(val))) {
val_flat <- unlist(val, recursive = TRUE)
if (is.null(val_flat)) {
# This is to assign NULL instead of deleting the item
msg$data[name] <- list(NULL)
} else {
msg$data[[name]] <- val_flat
}
# Set up a restore context from .clientdata_url_search before
# handling all the input values, because the restore context may be
# used by an input handler (like the one for "shiny.file"). This
# should only happen once, when the app starts.
if (is.null(shinysession$restoreContext)) {
bookmarkStore <- getShinyOption("bookmarkStore", default = "disable")
if (bookmarkStore == "disable") {
# If bookmarking is disabled, use empty context
shinysession$restoreContext <- RestoreContext$new()
} else {
# If there's bookmarked state, save it on the session object
shinysession$restoreContext <- RestoreContext$new(msg$data$.clientdata_url_search)
shinysession$createBookmarkObservers()
}
}
}
switch(
msg$method,
init = {
serverFunc <- serverFuncSource()
if (!identicalFunctionBodies(serverFunc, appvars$server)) {
appvars$server <- serverFunc
if (!is.null(appvars$server))
{
# Tag this function as the Shiny server function. A debugger may use this
# tag to give this function special treatment.
# It's very important that it's appvars$server itself and NOT a copy that
# is invoked, otherwise new breakpoints won't be picked up.
attr(appvars$server, "shinyServerFunction") <- TRUE
registerDebugHook("server", appvars, "Server Function")
msg$data <- applyInputHandlers(msg$data)
switch(
msg$method,
init = {
serverFunc <- withReactiveDomain(NULL, serverFuncSource())
if (!identicalFunctionBodies(serverFunc, appvars$server)) {
appvars$server <- serverFunc
if (!is.null(appvars$server))
{
# Tag this function as the Shiny server function. A debugger may use this
# tag to give this function special treatment.
# It's very important that it's appvars$server itself and NOT a copy that
# is invoked, otherwise new breakpoints won't be picked up.
attr(appvars$server, "shinyServerFunction") <- TRUE
registerDebugHook("server", appvars, "Server Function")
}
}
}
# Check for switching into/out of showcase mode
if (.globals$showcaseOverride &&
exists(".clientdata_url_search", where = msg$data)) {
mode <- showcaseModeOfQuerystring(msg$data$.clientdata_url_search)
if (!is.null(mode))
shinysession$setShowcase(mode)
}
# Check for switching into/out of showcase mode
if (.globals$showcaseOverride &&
exists(".clientdata_url_search", where = msg$data)) {
mode <- showcaseModeOfQuerystring(msg$data$.clientdata_url_search)
if (!is.null(mode))
shinysession$setShowcase(mode)
}
shinysession$manageInputs(msg$data)
# In shinysession$createBookmarkObservers() above, observers may be
# created, which puts the shiny session in busyCount > 0 state. That
# prevents the manageInputs here from taking immediate effect, by
# default. The manageInputs here needs to take effect though, because
# otherwise the bookmark observers won't find the clientData they are
# looking for. So use `now = TRUE` to force the changes to be
# immediate.
#
# FIXME: break createBookmarkObservers into two separate steps, one
# before and one after manageInputs, and put the observer creation
# in the latter. Then add an assertion that busyCount == 0L when
# this manageInputs is called.
shinysession$manageInputs(msg$data, now = TRUE)
# The client tells us what singletons were rendered into
# the initial page
if (!is.null(msg$data$.clientdata_singletons)) {
shinysession$singletons <<- strsplit(
msg$data$.clientdata_singletons, ',')[[1]]
}
# The client tells us what singletons were rendered into
# the initial page
if (!is.null(msg$data$.clientdata_singletons)) {
shinysession$singletons <- strsplit(
msg$data$.clientdata_singletons, ',')[[1]]
}
local({
args <- list(
input=shinysession$input,
output=.createOutputWriter(shinysession))
local({
args <- argsForServerFunc(serverFunc, shinysession)
# The clientData and session arguments are optional; check if
# each exists
if ('clientData' %in% names(formals(serverFunc)))
args$clientData <- shinysession$clientData
if ('session' %in% names(formals(serverFunc)))
args$session <- shinysession
withReactiveDomain(shinysession, {
do.call(appvars$server, args)
withReactiveDomain(shinysession, {
do.call(
# No corresponding ..stacktraceoff; the server func is pure
# user code
wrapFunctionLabel(appvars$server, "server",
..stacktraceon = TRUE
),
args
)
})
})
})
},
update = {
shinysession$manageInputs(msg$data)
},
shinysession$dispatch(msg)
)
shinysession$manageHiddenOutputs()
},
update = {
shinysession$manageInputs(msg$data)
},
shinysession$dispatch(msg)
)
# The HTTP_GUID, if it exists, is for Shiny Server reporting purposes
shinysession$startTiming(ws$request$HTTP_GUID)
shinysession$requestFlush()
if (exists(".shiny__stdout", globalenv()) &&
exists("HTTP_GUID", ws$request)) {
# safe to assume we're in shiny-server
shiny_stdout <- get(".shiny__stdout", globalenv())
# eNter a flushReact
writeLines(paste("_n_flushReact ", get("HTTP_GUID", ws$request),
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
sep=""), con=shiny_stdout)
flush(shiny_stdout)
flushReact()
# eXit a flushReact
writeLines(paste("_x_flushReact ", get("HTTP_GUID", ws$request),
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
sep=""), con=shiny_stdout)
flush(shiny_stdout)
} else {
flushReact()
}
lapply(appsByToken$values(), function(shinysession) {
shinysession$flushOutput()
NULL
# Make httpuv return control to Shiny quickly, instead of waiting
# for the usual timeout
httpuv::interrupt()
})
}
ws$onMessage(function(binary, msg) {
# If unhandled errors occur, make sure they get properly logged
withLogErrors(messageHandler(binary, msg))
})
ws$onClose(function() {
shinysession$wsClosed()
appsByToken$remove(shinysession$token)
appsNeedingFlush$remove(shinysession$token)
})
return(TRUE)
@@ -355,10 +346,30 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
return(appHandlers)
}
# Determine what arguments should be passed to this serverFunc. All server funcs
# must take input and output, but clientData (obsolete) and session are
# optional.
argsForServerFunc <- function(serverFunc, session) {
args <- list(input = session$input, output = .createOutputWriter(session))
paramNames <- names(formals(serverFunc))
# The clientData and session arguments are optional; check if
# each exists
if ("clientData" %in% paramNames)
args$clientData <- session$clientData
if ("session" %in% paramNames)
args$session <- session
args
}
getEffectiveBody <- function(func) {
# Note: NULL values are OK. isS4(NULL) returns FALSE, body(NULL)
# returns NULL.
if (isS4(func) && class(func) == "functionWithTrace")
if (is.null(func))
NULL
else if (isS4(func) && class(func) == "functionWithTrace")
body(func@original)
else
body(func)
@@ -408,7 +419,10 @@ startApp <- function(appObj, port, host, quiet) {
if (is.numeric(port) || is.integer(port)) {
if (!quiet) {
message('\n', 'Listening on http://', host, ':', port)
hostString <- host
if (httpuv::ipFamily(host) == 6L)
hostString <- paste0("[", hostString, "]")
message('\n', 'Listening on http://', hostString, ':', port)
}
return(startServer(host, port, handlerManager$createHttpuvApp()))
} else if (is.character(port)) {
@@ -416,6 +430,12 @@ startApp <- function(appObj, port, host, quiet) {
message('\n', 'Listening on domain socket ', port)
}
mask <- attr(port, 'mask')
if (is.null(mask)) {
stop("`port` is not a valid domain socket (missing `mask` attribute). ",
"Note that if you're using the default `host` + `port` ",
"configuration (and not domain sockets), then `port` must ",
"be numeric, not a string.")
}
return(startPipeServer(port, mask, handlerManager$createHttpuvApp()))
}
}
@@ -423,28 +443,38 @@ startApp <- function(appObj, port, host, quiet) {
# Run an application that was created by \code{\link{startApp}}. This
# function should normally be called in a \code{while(TRUE)} loop.
serviceApp <- function() {
if (timerCallbacks$executeElapsed()) {
for (shinysession in appsByToken$values()) {
shinysession$manageHiddenOutputs()
}
timerCallbacks$executeElapsed()
flushReact()
for (shinysession in appsByToken$values()) {
shinysession$flushOutput()
}
}
flushReact()
flushPendingSessions()
# If this R session is interactive, then call service() with a short timeout
# to keep the session responsive to user input
maxTimeout <- ifelse(interactive(), 100, 1000)
timeout <- max(1, min(maxTimeout, timerCallbacks$timeToNextEvent()))
timeout <- max(1, min(maxTimeout, timerCallbacks$timeToNextEvent(), later::next_op_secs()))
service(timeout)
flushReact()
flushPendingSessions()
}
.shinyServerMinVersion <- '0.3.4'
# Global flag that's TRUE whenever we're inside of the scope of a call to runApp
.globals$running <- FALSE
#' Check whether a Shiny application is running
#'
#' This function tests whether a Shiny application is currently running.
#'
#' @return \code{TRUE} if a Shiny application is currently running. Otherwise,
#' \code{FALSE}.
#' @export
isRunning <- function() {
.globals$running
}
#' Run Shiny Application
#'
#' Runs a Shiny application. This function normally does not return; interrupt R
@@ -456,13 +486,16 @@ serviceApp <- function() {
#' clients to connect, use the value \code{"0.0.0.0"} instead (which was the
#' value that was hard-coded into Shiny in 0.8.0 and earlier).
#'
#' @param appDir The directory of the application. Should contain
#' \code{server.R}, plus, either \code{ui.R} or a \code{www} directory that
#' contains the file \code{index.html}. Alternately, instead of
#' \code{server.R} and \code{ui.R}, the directory may contain just
#' \code{app.R}. Defaults to the working directory. Instead of a directory,
#' this could be a list with \code{ui} and \code{server} components, or a
#' Shiny app object created by \code{\link{shinyApp}}.
#' @param appDir The application to run. Should be one of the following:
#' \itemize{
#' \item A directory containing \code{server.R}, plus, either \code{ui.R} or
#' a \code{www} directory that contains the file \code{index.html}.
#' \item A directory containing \code{app.R}.
#' \item An \code{.R} file containing a Shiny application, ending with an
#' expression that produces a Shiny app object.
#' \item A list with \code{ui} and \code{server} components.
#' \item A Shiny app object created by \code{\link{shinyApp}}.
#' }
#' @param port The TCP port that the application should listen on. If the
#' \code{port} is not specified, and the \code{shiny.port} option is set (with
#' \code{options(shiny.port = XX)}), then that port will be used. Otherwise,
@@ -483,6 +516,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{
@@ -495,6 +531,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(
@@ -526,22 +564,89 @@ 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)
if (is.null(host) || is.na(host))
host <- '0.0.0.0'
# Enable per-app Shiny options, for shinyOptions() and getShinyOption().
oldOptionSet <- .globals$options
on.exit({
.globals$options <- oldOptionSet
},add = TRUE)
# A unique identifier associated with this run of this application. It is
# shared across sessions.
shinyOptions(appToken = createUniqueId(8))
# Make warnings print immediately
ops <- options(warn = 1)
# Set pool.scheduler to support pool package
ops <- options(
# Raise warn level to 1, but don't lower it
warn = max(1, getOption("warn", default = 1)),
pool.scheduler = scheduleTask
)
on.exit(options(ops), add = TRUE)
# Set up default cache for app.
if (is.null(getShinyOption("cache"))) {
shinyOptions(cache = MemoryCache$new())
}
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 (nzchar(Sys.getenv('SHINY_PORT'))) {
if (inShinyServer()) {
# If SHINY_PORT is set, we're running under Shiny Server. Check the version
# to make sure it is compatible. Older versions of Shiny Server don't set
# SHINY_SERVER_VERSION, those will return "" which is considered less than
@@ -558,30 +663,58 @@ 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)) {
desc <- file.path.ci(appDir, "DESCRIPTION")
# if appDir specifies a .R file (single-file Shiny app), look for the
# DESCRIPTION in the parent directory
desc <- file.path.ci(
if (tolower(tools::file_ext(appDir)) == "r")
dirname(appDir)
else
appDir, "DESCRIPTION")
if (file.exists(desc)) {
con <- file(desc, encoding = checkEncoding(desc))
on.exit(close(con), add = TRUE)
settings <- read.dcf(con)
if ("DisplayMode" %in% colnames(settings)) {
mode <- settings[1,"DisplayMode"]
mode <- settings[1, "DisplayMode"]
if (mode == "Showcase") {
setShowcaseDefault(1)
if ("IncludeWWW" %in% colnames(settings)) {
.globals$IncludeWWW <- as.logical(settings[1, "IncludeWWW"])
if (is.na(.globals$IncludeWWW)) {
stop("In your Description file, `IncludeWWW` ",
"must be set to `True` (default) or `False`")
}
} else {
.globals$IncludeWWW <- TRUE
}
}
}
}
}
## default is to show the .js, .css and .html files in the www directory
## (if not in showcase mode, this variable will simply be ignored)
if (is.null(.globals$IncludeWWW) || is.na(.globals$IncludeWWW)) {
.globals$IncludeWWW <- TRUE
}
# If display mode is specified as an argument, apply it (overriding the
# value specified in DESCRIPTION, if any).
display.mode <- match.arg(display.mode)
if (display.mode == "normal")
if (display.mode == "normal") {
setShowcaseDefault(0)
else if (display.mode == "showcase")
}
else if (display.mode == "showcase") {
setShowcaseDefault(1)
}
require(shiny)
@@ -602,7 +735,15 @@ runApp <- function(appDir=getwd(),
}
else {
# Try up to 20 random ports
port <- p_randomInt(3000, 8000)
while (TRUE) {
port <- p_randomInt(3000, 8000)
# Reject ports in this range that are considered unsafe by Chrome
# http://superuser.com/questions/188058/which-ports-are-considered-unsafe-on-chrome
# https://github.com/rstudio/shiny/issues/1784
if (!port %in% c(3659, 4045, 6000, 6665:6669, 6697)) {
break
}
}
}
# Test port to see if we can use it
@@ -615,11 +756,22 @@ runApp <- function(appDir=getwd(),
}
}
appParts <- as.shiny.appobj(appDir)
# Set up the onEnd before we call onStart, so that it gets called even if an
# Invoke user-defined onStop callbacks, before the application's internal
# onStop callbacks.
on.exit({
.globals$onStopCallbacks$invoke()
.globals$onStopCallbacks <- Callbacks$new()
}, add = TRUE)
# Extract appOptions (which is a list) and store them as shinyOptions, for
# this app. (This is the only place we have to store settings that are
# accessible both the UI and server portion of the app.)
unconsumeAppOptions(appParts$appOptions)
# Set up the onStop before we call onStart, so that it gets called even if an
# error happens in onStart.
if (!is.null(appParts$onEnd))
on.exit(appParts$onEnd(), add = TRUE)
if (!is.null(appParts$onStop))
on.exit(appParts$onStop(), add = TRUE)
if (!is.null(appParts$onStart))
appParts$onStart()
@@ -630,8 +782,17 @@ runApp <- function(appDir=getwd(),
}, add = TRUE)
if (!is.character(port)) {
# http://0.0.0.0/ doesn't work on QtWebKit (i.e. RStudio viewer)
browseHost <- if (identical(host, "0.0.0.0")) "127.0.0.1" else host
browseHost <- host
if (identical(host, "0.0.0.0")) {
# http://0.0.0.0/ doesn't work on QtWebKit (i.e. RStudio viewer)
browseHost <- "127.0.0.1"
} else if (identical(host, "::")) {
browseHost <- "::1"
}
if (httpuv::ipFamily(browseHost) == 6L) {
browseHost <- paste0("[", browseHost, "]")
}
appUrl <- paste("http://", browseHost, ":", port, sep="")
if (is.function(launch.browser))
@@ -648,16 +809,27 @@ runApp <- function(appDir=getwd(),
callAppHook("onAppStop", appUrl)
}, add = TRUE)
.globals$reterror <- NULL
.globals$retval <- NULL
.globals$stopped <- FALSE
shinyCallingHandlers(
while (!.globals$stopped) {
serviceApp()
Sys.sleep(0.001)
}
# Top-level ..stacktraceoff..; matches with ..stacktraceon in observe(),
# reactive(), Callbacks$invoke(), and others
..stacktraceoff..(
captureStackTraces({
while (!.globals$stopped) {
..stacktracefloor..(serviceApp())
Sys.sleep(0.001)
}
})
)
return(.globals$retval)
if (isTRUE(.globals$reterror)) {
stop(.globals$retval)
}
else if (.globals$retval$visible)
.globals$retval$value
else
invisible(.globals$retval$value)
}
#' Stop the currently running Shiny app
@@ -667,10 +839,26 @@ runApp <- function(appDir=getwd(),
#'
#' @param returnValue The value that should be returned from
#' \code{\link{runApp}}.
#'
#' @export
stopApp <- function(returnValue = NULL) {
.globals$retval <- returnValue
stopApp <- function(returnValue = invisible()) {
# reterror will indicate whether retval is an error (i.e. it should be passed
# to stop() when the serviceApp loop stops) or a regular value (in which case
# it should simply be returned with the appropriate visibility).
.globals$reterror <- FALSE
..stacktraceoff..(
tryCatch(
{
captureStackTraces(
.globals$retval <- withVisible(..stacktraceon..(force(returnValue)))
)
},
error = function(e) {
.globals$retval <- e
.globals$reterror <- TRUE
}
)
)
.globals$stopped <- TRUE
httpuv::interrupt()
}
@@ -733,3 +921,146 @@ runExample <- function(example=NA,
display.mode = display.mode)
}
}
#' Run a gadget
#'
#' Similar to \code{runApp}, but handles \code{input$cancel} automatically, and
#' if running in RStudio, defaults to viewing the app in the Viewer pane.
#'
#' @param app Either a Shiny app object as created by
#' \code{\link[=shiny]{shinyApp}} et al, or, a UI object.
#' @param server Ignored if \code{app} is a Shiny app object; otherwise, passed
#' along to \code{shinyApp} (i.e. \code{shinyApp(ui = app, server = server)}).
#' @param port See \code{\link[=shiny]{runApp}}.
#' @param viewer Specify where the gadget should be displayed--viewer pane,
#' dialog window, or external browser--by passing in a call to one of the
#' \code{\link{viewer}} functions.
#' @param stopOnCancel If \code{TRUE} (the default), then an \code{observeEvent}
#' is automatically created that handles \code{input$cancel} by calling
#' \code{stopApp()} with an error. Pass \code{FALSE} if you want to handle
#' \code{input$cancel} yourself.
#' @return The value returned by the gadget.
#'
#' @examples
#' \dontrun{
#' library(shiny)
#'
#' ui <- fillPage(...)
#'
#' server <- function(input, output, session) {
#' ...
#' }
#'
#' # Either pass ui/server as separate arguments...
#' runGadget(ui, server)
#'
#' # ...or as a single app object
#' runGadget(shinyApp(ui, server))
#' }
#' @export
runGadget <- function(app, server = NULL, port = getOption("shiny.port"),
viewer = paneViewer(), stopOnCancel = TRUE) {
if (!is.shiny.appobj(app)) {
app <- shinyApp(app, server)
}
if (isTRUE(stopOnCancel)) {
app <- decorateServerFunc(app, function(input, output, session) {
observeEvent(input$cancel, {
stopApp(stop("User cancel", call. = FALSE))
})
})
}
if (is.null(viewer)) {
viewer <- utils::browseURL
}
shiny::runApp(app, port = port, launch.browser = viewer)
}
# Add custom functionality to a Shiny app object's server func
decorateServerFunc <- function(appobj, serverFunc) {
origServerFuncSource <- appobj$serverFuncSource
appobj$serverFuncSource <- function() {
origServerFunc <- origServerFuncSource()
function(input, output, session) {
serverFunc(input, output, session)
# The clientData and session arguments are optional; check if
# each exists
args <- argsForServerFunc(origServerFunc, session)
do.call(origServerFunc, args)
}
}
appobj
}
#' Viewer options
#'
#' Use these functions to control where the gadget is displayed in RStudio (or
#' other R environments that emulate RStudio's viewer pane/dialog APIs). If
#' viewer APIs are not available in the current R environment, then the gadget
#' will be displayed in the system's default web browser (see
#' \code{\link[utils]{browseURL}}).
#'
#' @return A function that takes a single \code{url} parameter, suitable for
#' passing as the \code{viewer} argument of \code{\link{runGadget}}.
#'
#' @rdname viewer
#' @name viewer
NULL
#' @param minHeight The minimum height (in pixels) desired to show the gadget in
#' the viewer pane. If a positive number, resize the pane if necessary to show
#' at least that many pixels. If \code{NULL}, use the existing viewer pane
#' size. If \code{"maximize"}, use the maximum available vertical space.
#' @rdname viewer
#' @export
paneViewer <- function(minHeight = NULL) {
viewer <- getOption("viewer")
if (is.null(viewer)) {
utils::browseURL
} else {
function(url) {
viewer(url, minHeight)
}
}
}
#' @param dialogName The window title to display for the dialog.
#' @param width,height The desired dialog width/height, in pixels.
#' @rdname viewer
#' @export
dialogViewer <- function(dialogName, width = 600, height = 600) {
viewer <- getOption("shinygadgets.showdialog")
if (is.null(viewer)) {
utils::browseURL
} else {
function(url) {
viewer(dialogName, url, width = width, height = height)
}
}
}
#' @param browser See \code{\link[utils]{browseURL}}.
#' @rdname viewer
#' @export
browserViewer <- function(browser = getOption("browser")) {
function(url) {
utils::browseURL(url, browser = browser)
}
}
# Returns TRUE if we're running in Shiny Server or other hosting environment,
# otherwise returns FALSE.
inShinyServer <- function() {
nzchar(Sys.getenv('SHINY_PORT'))
}
# This check was moved out of the main function body because of an issue with
# the RStudio debugger. (#1474)
isEmptyMessage <- function(msg) {
identical(charToRaw("\003\xe9"), msg)
}

83
R/shiny-options.R Normal file
View File

@@ -0,0 +1,83 @@
.globals$options <- list()
#' @param name Name of an option to get.
#' @param default Value to be returned if the option is not currently set.
#' @rdname shinyOptions
#' @export
getShinyOption <- function(name, default = NULL) {
# Make sure to use named (not numeric) indexing
name <- as.character(name)
if (name %in% names(.globals$options))
.globals$options[[name]]
else
default
}
#' Get or set Shiny options
#'
#' \code{getShinyOption} retrieves the value of a Shiny option.
#' \code{shinyOptions} sets the value of Shiny options; it can also be used to
#' return a list of all currently-set Shiny options.
#'
#' There is a global option set, which is available by default. When a Shiny
#' application is run with \code{\link{runApp}}, that option set is duplicated
#' and the new option set is available for getting or setting values. If options
#' are set from global.R, app.R, ui.R, or server.R, or if they are set from
#' inside the server function, then the options will be scoped to the
#' application. When the application exits, the new option set is discarded and
#' the global option set is restored.
#'
#' @param ... Options to set, with the form \code{name = value}.
#'
#' @examples
#' \dontrun{
#' shinyOptions(myOption = 10)
#' getShinyOption("myOption")
#' }
#' @export
shinyOptions <- function(...) {
newOpts <- list(...)
if (length(newOpts) > 0) {
.globals$options <- dropNulls(mergeVectors(.globals$options, newOpts))
invisible(.globals$options)
} else {
.globals$options
}
}
# Eval an expression with a new option set
withLocalOptions <- function(expr) {
oldOptionSet <- .globals$options
on.exit(.globals$options <- oldOptionSet)
expr
}
# Get specific shiny options and put them in a list, reset those shiny options,
# and then return the options list. This should be during the creation of a
# shiny app object, which happens before another option frame is added to the
# options stack (the new option frame is added when the app is run). This
# function "consumes" the options when the shinyApp object is created, so the
# options won't affect another app that is created later.
consumeAppOptions <- function() {
options <- list(
appDir = getwd(),
bookmarkStore = getShinyOption("bookmarkStore")
)
shinyOptions(appDir = NULL, bookmarkStore = NULL)
options
}
# Do the inverse of consumeAppOptions. This should be called once the app is
# started.
unconsumeAppOptions <- function(options) {
if (!is.null(options)) {
do.call(shinyOptions, options)
}
}

1632
R/shiny.R

File diff suppressed because it is too large Load Diff

View File

@@ -14,68 +14,51 @@ NULL
#' # now we can just write "static" content without withMathJax()
#' div("more math here $$\\sqrt{2}$$")
withMathJax <- function(...) {
path <- 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
path <- 'https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
tagList(
tags$head(
singleton(tags$script(src = path, type = 'text/javascript'))
),
...,
tags$script(HTML('MathJax.Hub.Queue(["Typeset", MathJax.Hub]);'))
tags$script(HTML('if (window.MathJax) MathJax.Hub.Queue(["Typeset", MathJax.Hub]);'))
)
}
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")) {
if (showcase > 0)
ui <- showcaseUI(ui)
if (showcase > 0)
ui <- showcaseUI(ui)
# Wrap ui in body tag if it doesn't already have a single top-level body tag.
if (!(inherits(ui, "shiny.tag") && ui$name == "body"))
ui <- tags$body(ui)
# Wrap ui in body tag if it doesn't already have a single top-level body tag.
if (!(inherits(ui, "shiny.tag") && ui$name == "body"))
ui <- tags$body(ui)
# Put the body into the default template
ui <- htmlTemplate(
system.file("template", "default.html", package = "shiny"),
body = ui
)
}
result <- renderTags(ui)
deps <- c(
list(
htmlDependency("json2", "2014.02.04", c(href="shared"), script = "json2-min.js"),
htmlDependency("jquery", "1.11.0", c(href="shared"), script = "jquery.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")
),
result$dependencies
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("shiny", utils::packageVersion("shiny"), c(href="shared"),
script = if (getOption("shiny.minified", TRUE)) "shiny.min.js" else "shiny.js",
stylesheet = "shiny.css")
)
deps <- resolveDependencies(deps)
deps <- lapply(deps, createWebDependency)
depStr <- paste(sapply(deps, function(dep) {
sprintf("%s[%s]", dep$name, dep$version)
}), collapse = ";")
depHtml <- renderDependencies(deps, "href")
# write preamble
writeLines(c('<!DOCTYPE html>',
'<html>',
'<head>',
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>',
sprintf(' <script type="application/shiny-singletons">%s</script>',
paste(result$singletons, collapse = ',')
),
sprintf(' <script type="application/html-dependencies">%s</script>',
depStr
),
depHtml
),
con = connection)
writeLines(c(result$head,
'</head>',
recursive=TRUE),
con = connection)
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")
}
writeLines(result$html, con = connection)
# write end document
writeLines('</html>',
con = connection)
html <- renderDocument(ui, shiny_deps, processDep = createWebDependency)
writeUTF8(html, con = connection)
}
#' Create a Shiny UI handler
@@ -88,7 +71,6 @@ renderPage <- function(ui, connection, showcase=0) {
#'
#' @param ui A user interace definition
#' @return The user interface definition, without modifications or side effects.
#'
#' @export
shinyUI <- function(ui) {
.globals$ui <- list(ui)
@@ -106,7 +88,7 @@ uiHttpHandler <- function(ui, uiPattern = "^/$") {
if (!isTRUE(grepl(uiPattern, req$PATH_INFO)))
return(NULL)
textConn <- textConnection(NULL, "w")
textConn <- file(open = "w+")
on.exit(close(textConn))
showcaseMode <- .globals$showcaseDefault
@@ -115,19 +97,41 @@ uiHttpHandler <- function(ui, uiPattern = "^/$") {
if (!is.null(mode))
showcaseMode <- mode
}
uiValue <- if (is.function(ui)) {
if (length(formals(ui)) > 0)
ui(req)
else
ui()
testMode <- .globals$testMode %OR% FALSE
# Create a restore context using query string
bookmarkStore <- getShinyOption("bookmarkStore", default = "disable")
if (bookmarkStore == "disable") {
# If bookmarking is disabled, use empty context
restoreContext <- RestoreContext$new()
} else {
ui
restoreContext <- RestoreContext$new(req$QUERY_STRING)
}
withRestoreContext(restoreContext, {
uiValue <- NULL
if (is.function(ui)) {
if (length(formals(ui)) > 0) {
# No corresponding ..stacktraceoff.., this is pure user code
uiValue <- ..stacktraceon..(ui(req))
} else {
# No corresponding ..stacktraceoff.., this is pure user code
uiValue <- ..stacktraceon..(ui())
}
} else {
if (getCurrentRestoreContext()$active) {
warning("Trying to restore saved app state, but UI code must be a function for this to work! See ?enableBookmarking")
}
uiValue <- ui
}
})
if (is.null(uiValue))
return(NULL)
renderPage(uiValue, textConn, showcaseMode)
html <- paste(textConnectionValue(textConn), collapse='\n')
renderPage(uiValue, textConn, showcaseMode, testMode)
html <- paste(readLines(textConn, encoding = 'UTF-8'), collapse='\n')
return(httpResponse(200, content=enc2utf8(html)))
}
}

View File

@@ -1,4 +1,4 @@
globalVariables('func')
utils::globalVariables('func')
#' Mark a function as a render function
#'
@@ -12,24 +12,121 @@ globalVariables('func')
#' an output ID.
#' @param renderFunc A function that is suitable for assigning to a Shiny output
#' slot.
#' @param outputArgs A list of arguments to pass to the \code{uiFunc}. Render
#' functions should include \code{outputArgs = list()} in their own parameter
#' list, and pass through the value to \code{markRenderFunction}, to allow
#' app authors to customize outputs. (Currently, this is only supported for
#' dynamically generated UIs, such as those created by Shiny code snippets
#' embedded in R Markdown documents).
#' @return The \code{renderFunc} function, with annotations.
#' @export
markRenderFunction <- function(uiFunc, renderFunc, outputArgs = list()) {
# a mutable object that keeps track of whether `useRenderFunction` has been
# executed (this usually only happens when rendering Shiny code snippets in
# an interactive R Markdown document); its initial value is FALSE
hasExecuted <- Mutable$new()
hasExecuted$set(FALSE)
origRenderFunc <- renderFunc
renderFunc <- function(...) {
# if the user provided something through `outputArgs` BUT the
# `useRenderFunction` was not executed, then outputArgs will be ignored,
# so throw a warning to let user know the correct usage
if (length(outputArgs) != 0 && !hasExecuted$get()) {
warning("Unused argument: outputArgs. The argument outputArgs is only ",
"meant to be used when embedding snippets of Shiny code in an ",
"R Markdown code chunk (using runtime: shiny). When running a ",
"full Shiny app, please set the output arguments directly in ",
"the corresponding output function of your UI code.")
# stop warning from happening again for the same object
hasExecuted$set(TRUE)
}
if (is.null(formals(origRenderFunc))) origRenderFunc()
else origRenderFunc(...)
}
structure(renderFunc,
class = c("shiny.render.function", "function"),
outputFunc = uiFunc,
outputArgs = outputArgs,
hasExecuted = hasExecuted)
}
#' Implement render functions
#'
#' @param func A function without parameters, that returns user data. If the
#' returned value is a promise, then the render function will proceed in async
#' mode.
#' @param transform A function that takes four arguments: \code{value},
#' \code{session}, \code{name}, and \code{...} (for future-proofing). This
#' function will be invoked each time a value is returned from \code{func},
#' and is responsible for changing the value into a JSON-ready value to be
#' JSON-encoded and sent to the browser.
#' @param outputFunc The UI function that is used (or most commonly used) with
#' this render function. This can be used in R Markdown documents to create
#' complete output widgets out of just the render function.
#' @param outputArgs A list of arguments to pass to the \code{outputFunc}.
#' Render functions should include \code{outputArgs = list()} in their own
#' parameter list, and pass through the value as this argument, to allow app
#' authors to customize outputs. (Currently, this is only supported for
#' dynamically generated UIs, such as those created by Shiny code snippets
#' embedded in R Markdown documents).
#' @return An annotated render function, ready to be assigned to an
#' \code{output} slot.
#'
#' @export
markRenderFunction <- function(uiFunc, renderFunc) {
structure(renderFunc,
class = c("shiny.render.function", "function"),
outputFunc = uiFunc)
createRenderFunction <- function(
func, transform = function(value, session, name, ...) value,
outputFunc = NULL, outputArgs = NULL
) {
renderFunc <- function(shinysession, name, ...) {
hybrid_chain(
func(),
function(value, .visible) {
transform(setVisible(value, .visible), shinysession, name, ...)
}
)
}
if (!is.null(outputFunc))
markRenderFunction(outputFunc, renderFunc, outputArgs = outputArgs)
else
renderFunc
}
useRenderFunction <- function(renderFunc, inline = FALSE) {
outputFunction <- attr(renderFunc, "outputFunc")
outputArgs <- attr(renderFunc, "outputArgs")
hasExecuted <- attr(renderFunc, "hasExecuted")
hasExecuted$set(TRUE)
for (arg in names(outputArgs)) {
if (!arg %in% names(formals(outputFunction))) {
stop(paste0("Unused argument: in 'outputArgs', '",
arg, "' is not an valid argument for ",
"the output function"))
outputArgs[[arg]] <- NULL
}
}
id <- createUniqueId(8, "out")
o <- getDefaultReactiveDomain()$output
if (!is.null(o))
if (!is.null(o)) {
o[[id]] <- renderFunc
if (is.logical(formals(outputFunction)[["inline"]])) {
outputFunction(id, inline = inline)
} else outputFunction(id)
# If there's a namespace, we must respect it
id <- getDefaultReactiveDomain()$ns(id)
}
# Make the id the first positional argument
outputArgs <- c(list(id), outputArgs)
if (is.logical(formals(outputFunction)[["inline"]]) && !("inline" %in% names(outputArgs))) {
outputArgs[["inline"]] <- inline
}
do.call(outputFunction, outputArgs)
}
#' @export
@@ -38,6 +135,34 @@ 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.
#' @param snapshotPreprocess A function for preprocessing the value before
#' taking a test snapshot.
#'
#' @keywords internal
markOutputAttrs <- function(renderFunc, snapshotExclude = NULL,
snapshotPreprocess = 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
}
if (!is.null(snapshotPreprocess)) {
attr(renderFunc, "outputAttrs")$snapshotPreprocess <- snapshotPreprocess
}
renderFunc
}
#' Image file output
#'
#' Renders a reactive image that is suitable for assigning to an \code{output}
@@ -69,13 +194,24 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
#' it is sent to the client browser? Generally speaking, if the image is a
#' temp file generated within \code{func}, then this should be \code{TRUE};
#' if the image is not a temp file, this should be \code{FALSE}.
#'
#' @param outputArgs A list of arguments to be passed through to the implicit
#' call to \code{\link{imageOutput}} when \code{renderImage} is used in an
#' interactive R Markdown document.
#' @export
#'
#' @examples
#' \dontrun{
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' shinyServer(function(input, output, clientData) {
#' ui <- fluidPage(
#' sliderInput("n", "Number of observations", 2, 1000, 500),
#' plotOutput("plot1"),
#' plotOutput("plot2"),
#' plotOutput("plot3")
#' )
#'
#' server <- function(input, output, session) {
#'
#' # A plot of fixed size
#' output$plot1 <- renderImage({
@@ -97,14 +233,14 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
#' output$plot2 <- renderImage({
#' # Read plot2's width and height. These are reactive values, so this
#' # expression will re-run whenever these values change.
#' width <- clientData$output_plot2_width
#' height <- clientData$output_plot2_height
#' width <- session$clientData$output_plot2_width
#' height <- session$clientData$output_plot2_height
#'
#' # A temp file to save the output.
#' outfile <- tempfile(fileext='.png')
#'
#' png(outfile, width=width, height=height)
#' hist(rnorm(input$obs))
#' hist(rnorm(input$n))
#' dev.off()
#'
#' # Return a list containing the filename
@@ -115,6 +251,8 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
#' }, deleteFile = TRUE)
#'
#' # Send a pre-rendered image, and don't delete the image after sending it
#' # NOTE: For this example to work, it would require files in a subdirectory
#' # named images/
#' output$plot3 <- renderImage({
#' # When input$n is 1, filename is ./images/image1.jpeg
#' filename <- normalizePath(file.path('./images',
@@ -123,84 +261,41 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
#' # Return a list containing the filename
#' list(src = filename)
#' }, deleteFile = FALSE)
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
deleteFile=TRUE) {
deleteFile=TRUE, outputArgs=list()) {
installExprFunction(expr, "func", env, quoted)
return(markRenderFunction(imageOutput, function(shinysession, name, ...) {
imageinfo <- func()
# Should the file be deleted after being sent? If .deleteFile not set or if
# TRUE, then delete; otherwise don't delete.
if (deleteFile) {
on.exit(unlink(imageinfo$src))
}
createRenderFunction(func,
transform = function(imageinfo, session, name, ...) {
# Should the file be deleted after being sent? If .deleteFile not set or if
# TRUE, then delete; otherwise don't delete.
if (deleteFile) {
on.exit(unlink(imageinfo$src))
}
# If contentType not specified, autodetect based on extension
contentType <- imageinfo$contentType %OR% getContentType(imageinfo$src)
# If contentType not specified, autodetect based on extension
contentType <- imageinfo$contentType %OR% getContentType(imageinfo$src)
# Extra values are everything in imageinfo except 'src' and 'contentType'
extra_attr <- imageinfo[!names(imageinfo) %in% c('src', 'contentType')]
# Extra values are everything in imageinfo except 'src' and 'contentType'
extra_attr <- imageinfo[!names(imageinfo) %in% c('src', 'contentType')]
# Return a list with src, and other img attributes
c(src = shinysession$fileUrl(name, file=imageinfo$src, contentType=contentType),
extra_attr)
}))
# Return a list with src, and other img attributes
c(src = session$fileUrl(name, file=imageinfo$src, contentType=contentType),
extra_attr)
},
imageOutput, outputArgs)
}
#' Table Output
#'
#' Creates a reactive table that is suitable for assigning to an \code{output}
#' slot.
#'
#' The corresponding HTML output tag should be \code{div} and have the CSS class
#' name \code{shiny-html-output}.
#'
#' @param expr An expression that returns an R object that can be used with
#' \code{\link[xtable]{xtable}}.
#' @param ... Arguments to be passed through to \code{\link[xtable]{xtable}} and
#' \code{\link[xtable]{print.xtable}}.
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' is useful if you want to save an expression in a variable.
#' @param func A function that returns an R object that can be used with
#' \code{\link[xtable]{xtable}} (deprecated; use \code{expr} instead).
#'
#' @export
renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL) {
if (!is.null(func)) {
shinyDeprecated(msg="renderTable: argument 'func' is deprecated. Please use 'expr' instead.")
} else {
installExprFunction(expr, "func", env, quoted)
}
markRenderFunction(tableOutput, function() {
classNames <- getOption('shiny.table.class') %OR% 'data table table-bordered table-condensed'
data <- func()
if (is.null(data) || identical(data, data.frame()))
return("")
return(paste(
utils::capture.output(
print(xtable(data, ...),
type='html',
html.table.attributes=paste('class="',
htmlEscape(classNames, TRUE),
'"',
sep=''), ...)),
collapse="\n"))
})
}
#' Printable Output
#'
#' 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
@@ -212,34 +307,92 @@ renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL)
#'
#' 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
#' @param func A function that may print output and/or return a printable R
#' object (deprecated; use \code{expr} instead).
#' @param width The value for \code{\link{options}('width')}.
#' is useful if you want to save an expression in a variable.
#' @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.
#' @seealso \code{\link{renderText}} for displaying the value returned from a
#' function, instead of the printed output.
#'
#' @example res/text-example.R
#'
#' @export
renderPrint <- function(expr, env = parent.frame(), quoted = FALSE, func = NULL,
width = getOption('width')) {
if (!is.null(func)) {
shinyDeprecated(msg="renderPrint: argument 'func' is deprecated. Please use 'expr' instead.")
} else {
installExprFunction(expr, "func", env, quoted)
renderPrint <- function(expr, env = parent.frame(), quoted = FALSE,
width = getOption('width'), outputArgs=list()) {
installExprFunction(expr, "func", env, quoted)
# Set a promise domain that sets the console width
# and captures output
# op <- options(width = width)
# on.exit(options(op), add = TRUE)
renderFunc <- function(shinysession, name, ...) {
domain <- createRenderPrintPromiseDomain(width)
hybrid_chain(
{
promises::with_promise_domain(domain, func())
},
function(value, .visible) {
if (.visible) {
cat(file = domain$conn, paste(utils::capture.output(value, append = TRUE), collapse = "\n"))
}
res <- paste(readLines(domain$conn, warn = FALSE), collapse = "\n")
res
},
finally = function() {
close(domain$conn)
}
)
}
markRenderFunction(verbatimTextOutput, function() {
op <- options(width = width)
on.exit(options(op), add = TRUE)
paste(utils::capture.output(func()), collapse = "\n")
})
markRenderFunction(verbatimTextOutput, renderFunc, outputArgs = outputArgs)
}
createRenderPrintPromiseDomain <- function(width) {
f <- file()
promises::new_promise_domain(
wrapOnFulfilled = function(onFulfilled) {
force(onFulfilled)
function(...) {
op <- options(width = width)
on.exit(options(op), add = TRUE)
sink(f, append = TRUE)
on.exit(sink(NULL), add = TRUE)
onFulfilled(...)
}
},
wrapOnRejected = function(onRejected) {
force(onRejected)
function(...) {
op <- options(width = width)
on.exit(options(op), add = TRUE)
sink(f, append = TRUE)
on.exit(sink(NULL), add = TRUE)
onRejected(...)
}
},
wrapSync = function(expr) {
op <- options(width = width)
on.exit(options(op), add = TRUE)
sink(f, append = TRUE)
on.exit(sink(NULL), add = TRUE)
force(expr)
},
conn = f
)
}
#' Text Output
@@ -260,32 +413,31 @@ renderPrint <- function(expr, env = parent.frame(), quoted = FALSE, func = NULL,
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' is useful if you want to save an expression in a variable.
#' @param func A function that returns an R object that can be used as an
#' argument to \code{cat}.(deprecated; use \code{expr} instead).
#' @param outputArgs A list of arguments to be passed through to the implicit
#' call to \code{\link{textOutput}} when \code{renderText} is used in an
#' interactive R Markdown document.
#'
#' @seealso \code{\link{renderPrint}} for capturing the print output of a
#' function, rather than the returned text value.
#'
#' @example res/text-example.R
#'
#' @export
renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
if (!is.null(func)) {
shinyDeprecated(msg="renderText: argument 'func' is deprecated. Please use 'expr' instead.")
} else {
installExprFunction(expr, "func", env, quoted)
}
renderText <- function(expr, env=parent.frame(), quoted=FALSE,
outputArgs=list()) {
installExprFunction(expr, "func", env, quoted)
markRenderFunction(textOutput, function() {
value <- func()
return(paste(utils::capture.output(cat(value)), collapse="\n"))
})
createRenderFunction(
func,
function(value, session, name, ...) {
paste(utils::capture.output(cat(value)), collapse="\n")
},
textOutput, outputArgs
)
}
#' UI Output
#'
#' \bold{Experimental feature.} Makes a reactive version of a function that
#' generates HTML using the Shiny UI library.
#' Renders reactive HTML using the Shiny UI library.
#'
#' The corresponding HTML output tag should be \code{div} and have the CSS class
#' name \code{shiny-html-output} (or use \code{\link{uiOutput}}).
@@ -295,46 +447,45 @@ renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' is useful if you want to save an expression in a variable.
#' @param func A function that returns a Shiny tag object, \code{\link{HTML}},
#' or a list of such objects (deprecated; use \code{expr} instead).
#'
#' @seealso conditionalPanel
#' @param outputArgs A list of arguments to be passed through to the implicit
#' call to \code{\link{uiOutput}} when \code{renderUI} is used in an
#' interactive R Markdown document.
#'
#' @seealso \code{\link{uiOutput}}
#' @export
#' @examples
#' \dontrun{
#' output$moreControls <- renderUI({
#' list(
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' uiOutput("moreControls")
#' )
#'
#' server <- function(input, output) {
#' output$moreControls <- renderUI({
#' tagList(
#' sliderInput("n", "N", 1, 1000, 500),
#' textInput("label", "Label")
#' )
#' })
#' }
renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
if (!is.null(func)) {
shinyDeprecated(msg="renderUI: argument 'func' is deprecated. Please use 'expr' instead.")
} else {
installExprFunction(expr, "func", env, quoted)
}
#' shinyApp(ui, server)
#' }
#'
renderUI <- function(expr, env=parent.frame(), quoted=FALSE,
outputArgs=list()) {
installExprFunction(expr, "func", env, quoted)
markRenderFunction(uiOutput, function(shinysession, name, ...) {
result <- func()
if (is.null(result) || length(result) == 0)
return(NULL)
createRenderFunction(
func,
function(result, shinysession, name, ...) {
if (is.null(result) || length(result) == 0)
return(NULL)
result <- takeSingletons(result, shinysession$singletons, desingleton=FALSE)$ui
result <- surroundSingletons(result)
dependencies <- lapply(resolveDependencies(findDependencies(result)),
createWebDependency)
names(dependencies) <- NULL
# renderTags returns a list with head, singletons, and html
output <- list(
html = doRenderTags(result),
deps = dependencies
)
return(output)
})
processDeps(result, shinysession)
},
uiOutput, outputArgs
)
}
#' File Downloads
@@ -360,28 +511,42 @@ renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
#' example \code{"text/csv"} or \code{"image/png"}. If \code{NULL} or
#' \code{NA}, the content type will be guessed based on the filename
#' extension, or \code{application/octet-stream} if the extension is unknown.
#' @param outputArgs A list of arguments to be passed through to the implicit
#' call to \code{\link{downloadButton}} when \code{downloadHandler} is used
#' in an interactive R Markdown document.
#'
#' @examples
#' \dontrun{
#' # In server.R:
#' output$downloadData <- downloadHandler(
#' filename = function() {
#' paste('data-', Sys.Date(), '.csv', sep='')
#' },
#' content = function(file) {
#' write.csv(data, file)
#' }
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' downloadLink("downloadData", "Download")
#' )
#'
#' # In ui.R:
#' downloadLink('downloadData', 'Download')
#' server <- function(input, output) {
#' # Our dataset
#' data <- mtcars
#'
#' output$downloadData <- downloadHandler(
#' filename = function() {
#' paste("data-", Sys.Date(), ".csv", sep="")
#' },
#' content = function(file) {
#' write.csv(data, file)
#' }
#' )
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
downloadHandler <- function(filename, content, contentType=NA) {
return(markRenderFunction(downloadButton, function(shinysession, name, ...) {
downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()) {
renderFunc <- function(shinysession, name, ...) {
shinysession$registerDownload(name, filename, contentType, content)
}))
}
snapshotExclude(
markRenderFunction(downloadButton, renderFunc, outputArgs = outputArgs)
)
}
#' Table output with the JavaScript library DataTables
@@ -392,7 +557,7 @@ downloadHandler <- function(filename, content, contentType=NA) {
#' 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
@@ -412,6 +577,10 @@ downloadHandler <- function(filename, content, contentType=NA) {
#' indicate which columns to escape, e.g. \code{1:5} (the first 5 columns),
#' \code{c(1, 3, 4)}, or \code{c(-1, -3)} (all columns except the first and
#' third), or \code{c('Species', 'Sepal.Length')}.
#' @param outputArgs A list of arguments to be passed through to the implicit
#' call to \code{\link{dataTableOutput}} when \code{renderDataTable} is used
#' in an interactive R Markdown document.
#'
#' @references \url{http://datatables.net}
#' @note This function only provides the server-side version of DataTables
#' (using R to process the data object on the server side). There is a
@@ -446,36 +615,54 @@ downloadHandler <- function(filename, content, contentType=NA) {
#' }
renderDataTable <- function(expr, options = NULL, searchDelay = 500,
callback = 'function(oTable) {}', escape = TRUE,
env = parent.frame(), quoted = FALSE) {
env = parent.frame(), quoted = FALSE,
outputArgs=list()) {
installExprFunction(expr, "func", env, quoted)
markRenderFunction(dataTableOutput, function(shinysession, name, ...) {
renderFunc <- function(shinysession, name, ...) {
if (is.function(options)) options <- options()
options <- checkDT9(options)
res <- checkAsIs(options)
data <- func()
if (length(dim(data)) != 2) return() # expects a rectangular data object
if (is.data.frame(data)) data <- as.data.frame(data)
action <- shinysession$registerDataObj(name, data, dataTablesJSON)
colnames <- colnames(data)
# if escape is column names, turn names to numeric indices
if (is.character(escape)) {
escape <- stats::setNames(seq_len(ncol(data)), colnames)[escape]
if (any(is.na(escape)))
stop("Some column names in the 'escape' argument not found in data")
}
colnames[escape] <- htmlEscape(colnames[escape])
if (!is.logical(escape)) {
if (!is.numeric(escape))
stop("'escape' must be TRUE, FALSE, or a numeric vector, or column names")
escape <- paste(escape, collapse = ',')
}
list(
colnames = colnames, action = action, options = res$options,
evalOptions = if (length(res$eval)) I(res$eval), searchDelay = searchDelay,
callback = paste(callback, collapse = '\n'), escape = escape
hybrid_chain(
func(),
function(data) {
if (length(dim(data)) != 2) return() # expects a rectangular data object
if (is.data.frame(data)) data <- as.data.frame(data)
action <- shinysession$registerDataObj(name, data, dataTablesJSON)
colnames <- colnames(data)
# if escape is column names, turn names to numeric indices
if (is.character(escape)) {
escape <- stats::setNames(seq_len(ncol(data)), colnames)[escape]
if (any(is.na(escape)))
stop("Some column names in the 'escape' argument not found in data")
}
colnames[escape] <- htmlEscape(colnames[escape])
if (!is.logical(escape)) {
if (!is.numeric(escape))
stop("'escape' must be TRUE, FALSE, or a numeric vector, or column names")
escape <- paste(escape, collapse = ',')
}
list(
colnames = colnames, action = action, options = res$options,
evalOptions = if (length(res$eval)) I(res$eval), searchDelay = searchDelay,
callback = paste(callback, collapse = '\n'), escape = escape
)
}
)
}
renderFunc <- markRenderFunction(dataTableOutput, renderFunc, outputArgs = outputArgs)
renderFunc <- snapshotPreprocessOutput(renderFunc, function(value) {
# Remove the action field so that it's not saved in test snapshots. It
# contains a value that changes every time an app is run, and shouldn't be
# stored for test snapshots. It will be something like:
# "session/e0d14d3fe97f672f9655a127f2a1e079/dataobj/table?w=&nonce=7f5d6d54e22450a3"
value$action <- NULL
value
})
renderFunc
}
# a data frame containing the DataTables 1.9 and 1.10 names

View File

@@ -18,7 +18,8 @@ licenseLink <- function(licenseName) {
"Artistic-2.0" = "http://www.r-project.org/Licenses/Artistic-2.0",
"BSD_2_clause" = "http://www.r-project.org/Licenses/BSD_2_clause",
"BSD_3_clause" = "http://www.r-project.org/Licenses/BSD_3_clause",
"MIT" = "http://www.r-project.org/Licenses/MIT")
"MIT" = "http://www.r-project.org/Licenses/MIT",
"CC-BY-SA-4.0" = "https://www.r-project.org/Licenses/CC-BY-SA-4.0")
if (exists(licenseName, where = licenses)) {
tags$a(href=licenses[[licenseName]], licenseName)
} else {
@@ -31,7 +32,7 @@ licenseLink <- function(licenseName) {
showcaseHead <- function() {
deps <- list(
htmlDependency("jqueryui", "1.10.4", c(href="shared/jqueryui/1.10.4"),
htmlDependency("jqueryui", "1.12.1", c(href="shared/jqueryui"),
script = "jquery-ui.min.js"),
htmlDependency("showdown", "0.3.1", c(href="shared/showdown/compressed"),
script = "showdown.js"),
@@ -77,10 +78,60 @@ appMetadata <- function(desc) {
else ""
}
navTabsHelper <- function(files, prefix = "") {
lapply(files, function(file) {
with(tags,
li(class=if (tolower(file) %in% c("app.r", "server.r")) "active" else "",
a(href=paste("#", gsub(".", "_", file, fixed=TRUE), "_code", sep=""),
"data-toggle"="tab", paste0(prefix, file)))
)
})
}
navTabsDropdown <- function(files) {
if (length(files) > 0) {
with(tags,
li(role="presentation", class="dropdown",
a(class="dropdown-toggle", `data-toggle`="dropdown", href="#",
role="button", `aria-haspopup`="true", `aria-expanded`="false",
"www", span(class="caret")
),
ul(class="dropdown-menu", navTabsHelper(files))
)
)
}
}
tabContentHelper <- function(files, path, language) {
lapply(files, function(file) {
with(tags,
div(class=paste("tab-pane",
if (tolower(file) %in% c("app.r", "server.r")) " active"
else "",
sep=""),
id=paste(gsub(".", "_", file, fixed=TRUE),
"_code", sep=""),
pre(class="shiny-code",
# we need to prevent the indentation of <code> ... </code>
HTML(format(tags$code(
class=paste0("language-", language),
paste(readUTF8(file.path.ci(path, file)), collapse="\n")
), indent = FALSE))))
)
})
}
# Returns tags containing the application's code in Bootstrap-style tabs in
# showcase mode.
showcaseCodeTabs <- function(codeLicense) {
rFiles <- list.files(pattern = "\\.[rR]$")
wwwFiles <- list()
if (isTRUE(.globals$IncludeWWW)) {
path <- file.path(getwd(), "www")
wwwFiles$jsFiles <- list.files(path, pattern = "\\.js$")
wwwFiles$cssFiles <- list.files(path, pattern = "\\.css$")
wwwFiles$htmlFiles <- list.files(path, pattern = "\\.html$")
}
with(tags, div(id="showcase-code-tabs",
a(id="showcase-code-position-toggle",
class="btn btn-default btn-sm",
@@ -88,27 +139,21 @@ showcaseCodeTabs <- function(codeLicense) {
icon("level-up"),
"show with app"),
ul(class="nav nav-tabs",
lapply(rFiles, function(rFile) {
li(class=if (tolower(rFile) %in% c("app.r", "server.r")) "active" else "",
a(href=paste("#", gsub(".", "_", rFile, fixed=TRUE),
"_code", sep=""),
"data-toggle"="tab", rFile))
})),
navTabsHelper(rFiles),
navTabsDropdown(unlist(wwwFiles))
),
div(class="tab-content", id="showcase-code-content",
lapply(rFiles, function(rFile) {
div(class=paste("tab-pane",
if (tolower(rFile) %in% c("app.r", "server.r")) " active"
else "",
sep=""),
id=paste(gsub(".", "_", rFile, fixed=TRUE),
"_code", sep=""),
pre(class="shiny-code",
# we need to prevent the indentation of <code> ... </code>
HTML(format(tags$code(
class="language-r",
paste(readUTF8(file.path.ci(getwd(), rFile)), collapse="\n")
), indent = FALSE))))
})),
tabContentHelper(rFiles, path = getwd(), language = "r"),
tabContentHelper(wwwFiles$jsFiles,
path = paste0(getwd(), "/www"),
language = "javascript"),
tabContentHelper(wwwFiles$cssFiles,
path = paste0(getwd(), "/www"),
language = "css"),
tabContentHelper(wwwFiles$htmlFiles,
path = paste0(getwd(), "/www"),
language = "xml")
),
codeLicense))
}
@@ -177,3 +222,4 @@ showcaseUI <- function(ui) {
showcaseBody(ui)
)
}

44
R/snapshot.R Normal file
View File

@@ -0,0 +1,44 @@
#' Mark an output to be excluded from test snapshots
#'
#' @param x A reactive which will be assigned to an output.
#'
#' @export
snapshotExclude <- function(x) {
markOutputAttrs(x, snapshotExclude = TRUE)
}
#' Add a function for preprocessing an output before taking a test snapshot
#'
#' @param x A reactive which will be assigned to an output.
#' @param fun A function that takes the output value as an input and returns a
#' modified value. The returned value will be used for the test snapshot.
#'
#' @export
snapshotPreprocessOutput <- function(x, fun) {
markOutputAttrs(x, snapshotPreprocess = fun)
}
#' Add a function for preprocessing an input before taking a test snapshot
#'
#' @param inputId Name of the input value.
#' @param fun A function that takes the input value and returns a modified
#' value. The returned value will be used for the test snapshot.
#' @param session A Shiny session object.
#'
#' @export
snapshotPreprocessInput <- function(inputId, fun, session = getDefaultReactiveDomain()) {
if (is.null(session)) {
stop("snapshotPreprocessInput() needs a session object.")
}
input_impl <- .subset2(session$input, "impl")
input_impl$setMeta(inputId, "shiny.snapshot.preprocess", fun)
}
# Strip out file path from fileInput value
snapshotPreprocessorFileInput <- function(value) {
value$datapath <- basename(value$datapath)
value
}

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

@@ -22,6 +22,11 @@ TimerCallbacks <- R6Class(
.times <<- data.frame()
},
schedule = function(millis, func) {
# If args could fail to evaluate, let's make them do that before
# we change any state
force(millis)
force(func)
id <- .nextId
.nextId <<- .nextId + 1L
@@ -37,6 +42,17 @@ TimerCallbacks <- R6Class(
return(id)
},
unschedule = function(id) {
toRemoveIndices <- .times$id %in% id
toRemoveIds <- .times[toRemoveIndices, "id", drop = TRUE]
if (length(toRemoveIds) > 0) {
.times <<- .times[!toRemoveIndices,]
for (toRemoveId in as.character(toRemoveIds)) {
.funcs$remove(toRemoveId)
}
}
return(id %in% toRemoveIds)
},
timeToNextEvent = function() {
if (dim(.times)[1] == 0)
return(Inf)
@@ -56,7 +72,7 @@ TimerCallbacks <- R6Class(
},
executeElapsed = function() {
elapsed <- takeElapsed()
if (length(elapsed) == 0)
if (nrow(elapsed) == 0)
return(FALSE)
for (id in elapsed$id) {
@@ -71,3 +87,12 @@ TimerCallbacks <- R6Class(
)
timerCallbacks <- TimerCallbacks$new()
scheduleTask <- function(millis, callback) {
cancelled <- FALSE
id <- timerCallbacks$schedule(millis, callback)
function() {
invisible(timerCallbacks$unschedule(id))
}
}

View File

@@ -2,13 +2,21 @@
#'
#' @template update-input
#' @param value The value to set for the input object.
#' @param placeholder The placeholder to set for the input object.
#'
#' @seealso \code{\link{textInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' sliderInput("controller", "Controller", 0, 20, 10),
#' textInput("inText", "Input text"),
#' textInput("inText2", "Input text 2")
#' )
#'
#' server <- function(input, output, session) {
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
@@ -22,14 +30,54 @@
#' label = paste("New label", x),
#' value = paste("New text", x))
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
updateTextInput <- function(session, inputId, label = NULL, value = NULL) {
message <- dropNulls(list(label=label, value=value))
updateTextInput <- function(session, inputId, label = NULL, value = NULL, placeholder = NULL) {
message <- dropNulls(list(label=label, value=value, placeholder=placeholder))
session$sendInputMessage(inputId, message)
}
#' Change the value of a textarea input on the client
#'
#' @template update-input
#' @inheritParams updateTextInput
#'
#' @seealso \code{\link{textAreaInput}}
#'
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' sliderInput("controller", "Controller", 0, 20, 10),
#' textAreaInput("inText", "Input textarea"),
#' textAreaInput("inText2", "Input textarea 2")
#' )
#'
#' server <- function(input, output, session) {
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#'
#' # This will change the value of input$inText, based on x
#' updateTextAreaInput(session, "inText", value = paste("New text", x))
#'
#' # Can also set the label, this time for input$inText2
#' updateTextAreaInput(session, "inText2",
#' label = paste("New label", x),
#' value = paste("New text", x))
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
updateTextAreaInput <- updateTextInput
#' Change the value of a checkbox input on the client
#'
@@ -39,26 +87,90 @@ updateTextInput <- function(session, inputId, label = NULL, value = NULL) {
#' @seealso \code{\link{checkboxInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' sliderInput("controller", "Controller", 0, 1, 0, step = 1),
#' checkboxInput("inCheckbox", "Input checkbox")
#' )
#'
#' server <- function(input, output, session) {
#' observe({
#' # TRUE if input$controller is even, FALSE otherwise.
#' x_even <- input$controller %% 2 == 0
#' # TRUE if input$controller is odd, FALSE if even.
#' x_even <- input$controller %% 2 == 1
#'
#' updateCheckboxInput(session, "inCheckbox", value = x_even)
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
updateCheckboxInput <- updateTextInput
updateCheckboxInput <- function(session, inputId, label = NULL, value = NULL) {
message <- dropNulls(list(label=label, value=value))
session$sendInputMessage(inputId, message)
}
#' Change the label or icon of an action button on the client
#'
#' @template update-input
#' @param icon The icon to set for the input object. To remove the
#' current icon, use \code{icon=character(0)}.
#'
#' @seealso \code{\link{actionButton}}
#'
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' actionButton("update", "Update other buttons"),
#' br(),
#' actionButton("goButton", "Go"),
#' br(),
#' actionButton("goButton2", "Go 2", icon = icon("area-chart")),
#' br(),
#' actionButton("goButton3", "Go 3")
#' )
#'
#' server <- function(input, output, session) {
#' observe({
#' req(input$update)
#'
#' # Updates goButton's label and icon
#' updateActionButton(session, "goButton",
#' label = "New label",
#' icon = icon("calendar"))
#'
#' # Leaves goButton2's label unchaged and
#' # removes its icon
#' updateActionButton(session, "goButton2",
#' icon = character(0))
#'
#' # Leaves goButton3's icon, if it exists,
#' # unchaged and changes its label
#' updateActionButton(session, "goButton3",
#' label = "New label 3")
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
updateActionButton <- function(session, inputId, label = NULL, icon = NULL) {
if (!is.null(icon)) icon <- as.character(validateIcon(icon))
message <- dropNulls(list(label=label, icon=icon))
session$sendInputMessage(inputId, message)
}
#' Change the value of a date input on the client
#'
#' @template update-input
#' @param value The desired date value. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' \code{yyyy-mm-dd} format. Supply \code{NA} to clear the date.
#' @param min The minimum allowed date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param max The maximum allowed date. Either a Date object, or a string in
@@ -67,32 +179,44 @@ updateCheckboxInput <- updateTextInput
#' @seealso \code{\link{dateInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' sliderInput("n", "Day of month", 1, 30, 10),
#' dateInput("inDate", "Input date")
#' )
#'
#' server <- function(input, output, session) {
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#'
#' date <- as.Date(paste0("2013-04-", input$n))
#' updateDateInput(session, "inDate",
#' label = paste("Date label", x),
#' value = paste("2013-04-", x, sep=""),
#' min = paste("2013-04-", x-1, sep=""),
#' max = paste("2013-04-", x+1, sep="")
#' label = paste("Date label", input$n),
#' value = date,
#' min = date - 3,
#' max = date + 3
#' )
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
updateDateInput <- function(session, inputId, label = NULL, value = NULL,
min = NULL, max = NULL) {
# If value is a date object, convert it to a string with yyyy-mm-dd format
# Same for min and max
if (inherits(value, "Date")) value <- format(value, "%Y-%m-%d")
if (inherits(min, "Date")) min <- format(min, "%Y-%m-%d")
if (inherits(max, "Date")) max <- format(max, "%Y-%m-%d")
# Make sure values are NULL or Date objects. This is so we can ensure that
# they will be formatted correctly. For example, the string "2016-08-9" is not
# correctly formatted, but the conversion to Date and back to string will fix
# it.
formatDate <- function(x) {
if (is.null(x))
return(NULL)
format(as.Date(x), "%Y-%m-%d")
}
value <- formatDate(value)
min <- formatDate(min)
max <- formatDate(max)
message <- dropNulls(list(label=label, value=value, min=min, max=max))
session$sendInputMessage(inputId, message)
@@ -103,9 +227,9 @@ updateDateInput <- function(session, inputId, label = NULL, value = NULL,
#'
#' @template update-input
#' @param start The start date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' \code{yyyy-mm-dd} format. Supplying \code{NA} clears the start date.
#' @param end The end date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' \code{yyyy-mm-dd} format. Supplying \code{NA} clears the end date.
#' @param min The minimum allowed date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param max The maximum allowed date. Either a Date object, or a string in
@@ -114,20 +238,29 @@ updateDateInput <- function(session, inputId, label = NULL, value = NULL,
#' @seealso \code{\link{dateRangeInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' sliderInput("n", "Day of month", 1, 30, 10),
#' dateRangeInput("inDateRange", "Input date range")
#' )
#'
#' server <- function(input, output, session) {
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#' date <- as.Date(paste0("2013-04-", input$n))
#'
#' updateDateRangeInput(session, "inDateRange",
#' label = paste("Date range label", x),
#' start = paste("2013-01-", x, sep=""))
#' end = paste("2013-12-", x, sep=""))
#' label = paste("Date range label", input$n),
#' start = date - 1,
#' end = date + 1,
#' min = date - 5,
#' max = date + 5
#' )
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
updateDateRangeInput <- function(session, inputId, label = NULL,
@@ -142,7 +275,7 @@ updateDateRangeInput <- function(session, inputId, label = NULL,
message <- dropNulls(list(
label = label,
value = c(start, end),
value = dropNulls(list(start = start, end = end)),
min = min,
max = max
))
@@ -162,22 +295,31 @@ updateDateRangeInput <- function(session, inputId, label = NULL,
#' \code{\link{navbarPage}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' observe({
#' # TRUE if input$controller is even, FALSE otherwise.
#' x_even <- input$controller %% 2 == 0
#' ui <- fluidPage(sidebarLayout(
#' sidebarPanel(
#' sliderInput("controller", "Controller", 1, 3, 1)
#' ),
#' mainPanel(
#' tabsetPanel(id = "inTabset",
#' tabPanel(title = "Panel 1", value = "panel1", "Panel 1 content"),
#' tabPanel(title = "Panel 2", value = "panel2", "Panel 2 content"),
#' tabPanel(title = "Panel 3", value = "panel3", "Panel 3 content")
#' )
#' )
#' ))
#'
#' # Change the selected tab.
#' # Note that the tabset container must have been created with an 'id' argument
#' if (x_even) {
#' updateTabsetPanel(session, "inTabset", selected = "panel2")
#' } else {
#' updateTabsetPanel(session, "inTabset", selected = "panel1")
#' }
#' server <- function(input, output, session) {
#' observeEvent(input$controller, {
#' updateTabsetPanel(session, "inTabset",
#' selected = paste0("panel", input$controller)
#' )
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
updateTabsetPanel <- function(session, inputId, selected = NULL) {
@@ -204,10 +346,18 @@ updateNavlistPanel <- updateTabsetPanel
#' @seealso \code{\link{numericInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' observe({
#' ui <- fluidPage(
#' sliderInput("controller", "Controller", 0, 20, 10),
#' numericInput("inNumber", "Input number", 0),
#' numericInput("inNumber2", "Input number 2", 0)
#' )
#'
#' server <- function(input, output, session) {
#'
#' observeEvent(input$controller, {
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
@@ -218,7 +368,9 @@ updateNavlistPanel <- updateTabsetPanel
#' label = paste("Number label ", x),
#' value = x, min = x-10, max = x+10, step = 5)
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
@@ -231,13 +383,17 @@ updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
session$sendInputMessage(inputId, message)
}
#' Change the value of a slider input on the client
#' Update Slider Input Widget
#'
#' Change the value of a slider input on the client.
#'
#' @template update-input
#' @param value The value to set for the input object.
#' @param min Minimum value.
#' @param max Maximum value.
#' @param step Step size.
#' @param timeFormat Date and POSIXt formatting.
#' @param timezone The timezone offset for POSIXt objects.
#'
#' @seealso \code{\link{sliderInput}}
#'
@@ -270,22 +426,15 @@ updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
#' }
#' @export
updateSliderInput <- function(session, inputId, label = NULL, value = NULL,
min = NULL, max = NULL, step = NULL)
min = NULL, max = NULL, step = NULL, timeFormat = NULL, timezone = NULL)
{
# Make sure that value, min, max all have the same type, because we need
# special handling for dates and datetimes.
vals <- dropNulls(list(value, min, max))
dataType <- getSliderType(min, max, value)
type <- unique(lapply(vals, function(x) {
if (inherits(x, "Date")) "date"
else if (inherits(x, "POSIXt")) "datetime"
else "number"
}))
if (length(type) > 1) {
stop("Type mismatch for value, min, and max")
if (is.null(timeFormat)) {
timeFormat <- switch(dataType, date = "%F", datetime = "%F %T", number = NULL)
}
if (type == "date" || type == "datetime") {
if (dataType == "date" || dataType == "datetime") {
to_ms <- function(x) 1000 * as.numeric(as.POSIXct(x))
if (!is.null(min)) min <- to_ms(min)
if (!is.null(max)) max <- to_ms(max)
@@ -297,24 +446,30 @@ updateSliderInput <- function(session, inputId, label = NULL, value = NULL,
value = formatNoSci(value),
min = formatNoSci(min),
max = formatNoSci(max),
step = formatNoSci(step)
step = formatNoSci(step),
`data-type` = dataType,
`time-format` = timeFormat,
timezone = timezone
))
session$sendInputMessage(inputId, message)
}
updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
selected = NULL, inline = FALSE,
type = 'checkbox') {
selected = NULL, inline = FALSE, type = NULL,
choiceNames = NULL, choiceValues = NULL) {
if (is.null(type)) stop("Please specify the type ('checkbox' or 'radio')")
choices <- choicesWithNames(choices)
if (!is.null(selected))
selected <- validateSelected(selected, choices, inputId)
args <- normalizeChoicesArgs(choices, choiceNames, choiceValues, mustExist = FALSE)
options <- if (length(choices))
if (!is.null(selected)) selected <- as.character(selected)
options <- if (!is.null(args$choiceValues)) {
format(tagList(
generateOptions(inputId, choices, selected, inline, type = type)
generateOptions(session$ns(inputId), selected, inline, type,
args$choiceNames, args$choiceValues)
))
}
message <- dropNulls(list(label = label, options = options, value = selected))
@@ -329,37 +484,42 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
#' @seealso \code{\link{checkboxGroupInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' p("The first checkbox group controls the second"),
#' checkboxGroupInput("inCheckboxGroup", "Input checkbox",
#' c("Item A", "Item B", "Item C")),
#' checkboxGroupInput("inCheckboxGroup2", "Input checkbox 2",
#' c("Item A", "Item B", "Item C"))
#' )
#'
#' server <- function(input, output, session) {
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#' x <- input$inCheckboxGroup
#'
#' # Create a list of new options, where the name of the items is something
#' # like 'option label x 1', and the values are 'option-x-1'.
#' cb_options <- list()
#' cb_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
#' cb_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
#'
#' # Change values for input$inCheckboxGroup
#' updateCheckboxGroupInput(session, "inCheckboxGroup", choices = cb_options)
#' # Can use character(0) to remove all choices
#' if (is.null(x))
#' x <- character(0)
#'
#' # Can also set the label and select items
#' updateCheckboxGroupInput(session, "inCheckboxGroup2",
#' label = paste("checkboxgroup label", x),
#' choices = cb_options,
#' selected = sprintf("option-%d-2", x)
#' label = paste("Checkboxgroup label", length(x)),
#' choices = x,
#' selected = x
#' )
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @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)
}
@@ -371,36 +531,43 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL,
#' @seealso \code{\link{radioButtons}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' p("The first radio button group controls the second"),
#' radioButtons("inRadioButtons", "Input radio buttons",
#' c("Item A", "Item B", "Item C")),
#' radioButtons("inRadioButtons2", "Input radio buttons 2",
#' c("Item A", "Item B", "Item C"))
#' )
#'
#' server <- function(input, output, session) {
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#' x <- input$inRadioButtons
#'
#' r_options <- list()
#' r_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
#' r_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
#'
#' # Change values for input$inRadio
#' updateRadioButtons(session, "inRadio", choices = r_options)
#'
#' # Can also set the label and select an item
#' updateRadioButtons(session, "inRadio2",
#' label = paste("Radio label", x),
#' choices = r_options,
#' selected = sprintf("option-%d-2", x)
#' # Can also set the label and select items
#' updateRadioButtons(session, "inRadioButtons2",
#' label = paste("radioButtons label", x),
#' choices = x,
#' selected = x
#' )
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @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)
}
@@ -409,42 +576,44 @@ updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
#' @template update-input
#' @inheritParams selectInput
#'
#' @seealso \code{\link{selectInput}}
#' @seealso \code{\link{selectInput}} \code{\link{varSelectInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' p("The checkbox group controls the select input"),
#' checkboxGroupInput("inCheckboxGroup", "Input checkbox",
#' c("Item A", "Item B", "Item C")),
#' selectInput("inSelect", "Select input",
#' c("Item A", "Item B", "Item C"))
#' )
#'
#' server <- function(input, output, session) {
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#' x <- input$inCheckboxGroup
#'
#' # Create a list of new options, where the name of the items is something
#' # like 'option label x 1', and the values are 'option-x-1'.
#' s_options <- list()
#' s_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
#' s_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
#' # Can use character(0) to remove all choices
#' if (is.null(x))
#' x <- character(0)
#'
#' # Change values for input$inSelect
#' updateSelectInput(session, "inSelect", choices = s_options)
#'
#' # Can also set the label and select an item (or more than one if it's a
#' # multi-select)
#' updateSelectInput(session, "inSelect2",
#' label = paste("Select label", x),
#' choices = s_options,
#' selected = sprintf("option-%d-2", x)
#' # Can also set the label and select items
#' updateSelectInput(session, "inSelect",
#' label = paste("Select input label", length(x)),
#' choices = x,
#' selected = tail(x, 1)
#' )
#' })
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
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)
@@ -464,7 +633,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))
)
@@ -473,51 +642,171 @@ updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
if (!server) {
return(updateSelectInput(session, inputId, label, choices, selected))
}
noOptGroup <- TRUE
if (is.list(choices)) {
# check if list is nested
for (i in seq_along(choices)) {
if (is.list(choices[[i]]) || length(choices[[i]]) > 1) {
noOptGroup <- FALSE
break()
}
}
}
# convert choices to a data frame so it returns [{label: , value: , group: },...]
choices <- if (is.atomic(choices) || noOptGroup) {
# fast path for vectors and flat lists
if (is.list(choices)) {
choices <- unlist(choices)
}
if (is.null(names(choices))) {
lab <- as.character(choices)
} else {
lab <- names(choices)
# replace empty names like: choices = c(a = 1, 2)
# in this case: names(choices) = c("a", "")
# with replacement below choices will be: lab = c("a", "2")
empty_names_indices <- lab == ""
lab[empty_names_indices] <- as.character(choices[empty_names_indices])
}
data.frame(label = lab, value = choices, stringsAsFactors = FALSE)
} else {
# slow path for nested lists/optgroups
list_names <- names(choices)
if (is.null(list_names)) {
list_names <- rep("", length(choices))
}
choice_list <- mapply(choices, list_names, FUN = function (choice, name) {
group <- ""
lab <- name
if (lab == "") lab <- as.character(choice)
if (is.list(choice) || length(choice) > 1) {
group <- rep(name, length(choice))
choice <- unlist(choice)
if (is.null(names(choice))) {
lab <- as.character(choice)
} else {
lab <- names(choice)
# replace empty names like: choices = c(a = 1, 2)
# in this case: names(choices) = c("a", "")
# with replacement below choices will be: lab = c("a", "2")
empty_names_indices <- lab == ""
lab[empty_names_indices] <- as.character(choice[empty_names_indices])
}
}
list(
label = lab,
value = as.character(choice),
group = group
)
}, SIMPLIFY = FALSE)
extract_vector <- function(x, name) {
vecs <- lapply(x, `[[`, name)
do.call(c, vecs)
}
data.frame(
label = extract_vector(choice_list, "label"),
value = extract_vector(choice_list, "value"),
group = extract_vector(choice_list, "group"),
stringsAsFactors = FALSE, row.names = NULL
)
}
value <- unname(selected)
selected <- choicesWithNames(selected)
attr(choices, 'selected_value') <- value
message <- dropNulls(list(
label = label,
value = value,
selected = if (length(selected)) {
columnToRowData(list(label = names(selected), value = selected))
},
url = session$registerDataObj(inputId, choices, selectizeJSON)
))
session$sendInputMessage(inputId, message)
}
#' @rdname updateSelectInput
#' @inheritParams varSelectInput
#' @export
updateVarSelectInput <- function(session, inputId, label = NULL, data = NULL, selected = NULL) {
if (is.null(data)) {
choices <- NULL
} else {
choices <- colnames(data)
}
updateSelectInput(
session = session,
inputId = inputId,
label = label,
choices = choices,
selected = selected
)
}
#' @rdname updateSelectInput
#' @export
updateVarSelectizeInput <- function(session, inputId, label = NULL, data = NULL, selected = NULL, options = list(), server = FALSE) {
if (is.null(data)) {
choices <- NULL
} else {
choices <- colnames(data)
}
updateSelectizeInput(
session = session,
inputId = inputId,
label = label,
choices = choices,
selected = selected,
options = options,
server = server
)
}
selectizeJSON <- function(data, req) {
query <- parseQueryString(req$QUERY_STRING)
# extract the query variables, conjunction (and/or), search string, maximum options
var <- c(jsonlite::fromJSON(query$field))
cjn <- if (query$conju == 'and') all else any
var <- c(safeFromJSON(query$field))
# all keywords in lower-case, for case-insensitive matching
key <- unique(strsplit(tolower(query$query), '\\s+')[[1]])
if (identical(key, '')) key <- character(0)
mop <- query$maxop
# convert a single vector to a data frame so it returns {label: , value: }
# later in JSON; other objects return arbitrary JSON {x: , y: , foo: , ...}
data <- if (is.atomic(data)) {
data.frame(label = names(choicesWithNames(data)), value = data,
stringsAsFactors = FALSE)
} else as.data.frame(data, stringsAsFactors = FALSE)
if (identical(key, '')) key <- character(0)
mop <- as.numeric(query$maxop)
vfd <- query$value # the value field name
sel <- attr(data, 'selected_value', exact = TRUE)
# start searching for keywords in all specified columns
idx <- logical(nrow(data))
if (length(key)) for (v in var) {
matches <- do.call(
cbind,
lapply(key, function(k) {
grepl(k, tolower(as.character(data[[v]])), fixed = TRUE)
})
)
# merge column matches using OR, and match multiple keywords in one column
# using the conjunction setting (AND or OR)
idx <- idx | apply(matches, 1, cjn)
if (length(key)) {
for (v in var) {
matches <- do.call(
cbind,
lapply(key, function(k) {
grepl(k, tolower(as.character(data[[v]])), fixed = TRUE)
})
)
# merge column matches using OR, and match multiple keywords in one column
# using the conjunction setting (AND or OR)
matches <- rowSums(matches)
if (query$conju == 'and')
idx <- idx | (matches == length(key))
else
idx <- idx | matches
}
}
# only return the first n rows (n = maximum options in configuration)
idx <- utils::head(if (length(key)) which(idx) else seq_along(idx), mop)
# make sure the selected value is in the data
if (length(sel)) {
i <- stats::na.omit(match(sel, data[, vfd]))
if (length(i)) idx <- sort(utils::head(unique(c(i, idx)), mop))
}
data <- data[idx, ]
res <- toJSON(columnToRowData(data))

972
R/utils.R

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,24 @@
# Shiny
[![Build Status](https://travis-ci.org/rstudio/shiny.svg?branch=master)](https://travis-ci.org/rstudio/shiny)
*Travis:* [![Travis Build Status](https://travis-ci.org/rstudio/shiny.svg?branch=master)](https://travis-ci.org/rstudio/shiny)
*AppVeyor:* [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/rstudio/shiny?branch=master&svg=true)](https://ci.appveyor.com/project/rstudio/shiny)
Shiny is a new package from RStudio that makes it incredibly easy to build interactive web applications with R.
For an introduction and examples, visit the [Shiny Dev Center](http://shiny.rstudio.com/).
If you have general questions about using Shiny, please use the [RStudio Community website](https://community.rstudio.com). For bug reports, please use the [issue tracker](https://github.com/rstudio/shiny/issues).
## Features
* Build useful web applications with only a few lines of code&mdash;no JavaScript required.
* Shiny applications are automatically "live" in the same way that spreadsheets are live. Outputs change instantly as users modify inputs, without requiring a reload of the browser.
* Shiny user interfaces can be built entirely using R, or can be written directly in HTML, CSS, and JavaScript for more flexibility.
* Works in any R environment (Console R, Rgui for Windows or Mac, ESS, StatET, RStudio, etc.)
* Attractive default UI theme based on [Bootstrap](http://getbootstrap.com/2.3.2/).
* Works in any R environment (Console R, Rgui for Windows or Mac, ESS, StatET, RStudio, etc.).
* Attractive default UI theme based on [Bootstrap](http://getbootstrap.com/).
* A highly customizable slider widget with built-in support for animation.
* Pre-built output widgets for displaying plots, tables, and printed output of R objects.
* Prebuilt output widgets for displaying plots, tables, and printed output of R objects.
* Fast bidirectional communication between the web browser and R using the [httpuv](https://github.com/rstudio/httpuv) package.
* Uses a [reactive](http://en.wikipedia.org/wiki/Reactive_programming) programming model that eliminates messy event handling code, so you can focus on the code that really matters.
* Develop and redistribute your own Shiny widgets that other developers can easily drop into their own applications (coming soon!).
@@ -39,8 +43,6 @@ devtools::install_github("rstudio/shiny")
To learn more we highly recommend you check out the [Shiny Tutorial](http://shiny.rstudio.com/tutorial/). The tutorial explains the framework in-depth, walks you through building a simple application, and includes extensive annotated examples.
We hope you enjoy using Shiny. If you have general questions about using Shiny, please use the Shiny [mailing list](https://groups.google.com/forum/#!forum/shiny-discuss). For bug reports, please use the [issue tracker](https://github.com/rstudio/shiny/issues).
## Bootstrap 3 migration
Shiny versions 0.10.2.2 and below used the Bootstrap 2 web framework. After 0.10.2.2, Shiny switched to Bootstrap 3. For most users, the upgrade should be seamless. However, if you have have customized your HTML-generating code to use features specific to Bootstrap 2, you may need to update your code to work with Bootstrap 3.
@@ -57,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:

54
TODO-promises.md Normal file
View File

@@ -0,0 +1,54 @@
# Promises TODO
## Documentation
- [x] Motivation -- why should I care about async? Why shouldn't I (what are the limitations)?
- [x] High level technical overview
- [ ] Cookbook-style examples
- [ ] Top-down porting of a sync app to async
## Core API
- [x] Should as.promise() convert regular values to promises? Or throw?
- [x] If as.promise() doesn't convert regular values to promises, add promise_resolved(value) and promise_rejected(err) functions?
## later
- [ ] Add support for multiple event loops
- [x] Add timeout to run_now
## Error handling/debugging
- [ ] ..stacktraceon../..stacktraceoff.. and stack traces in general
- [x] long stack traces
- [x] require opt-in
- [ ] options(shiny.error) should work in promise handlers
- [x] Detect when reactives are used across process boundaries, and error
## Render functions
- [x] Non-async render functions should have their code all execute on the current tick. Otherwise order of execution will be surprising if they have side effects and explicit priorities.
- [x] Promise domains should maybe have an onExecute, for the "sync" part that kicks off async operations to also have wrapping behavior (like capturing output). Right now, I have to start off renderPrint with promise(~resolve(TRUE)) and then execute the user code in a then(), just to get the promise behavior. Same will be true when we tackle error handling (stack trace capture).
- [x] invisible() doesn't seem to be working correctly with renderPrint. .visible doesn't survive promise chaining, e.g. promise(~resolve(promise(~resolve(invisible("Hi"))))) %>% then(function(x, .visible) { cat(.visible) }) will print TRUE, not FALSE.
- [x] renderDataTable should support async
- [x] Support downloadHandler
- [ ] Support async filename?
- [x] Should prevent session from continuing until download completes (ref count)
## Flush lifecycle
- [x] While async operations are running in a session, hold off on any further processing of inputs and scheduled task items until all operations are complete.
- [x] Hold all outputs/errors until async operations are complete.
- [ ] Allow both sync and async outputs to be displayed before all outputs are done. (opt-in)
## Testing
- [x] App that tests that all built-in render functions support async
- [x] Apps that test flush lifecycle, including onFlushed(once = FALSE)
- [x] Apps that test invisible() behavior for renderPrint, both sync and async
- [x] Apps that ensure all render functions execute synchronous code before tick is over
- [x] App that tests async downloadHandler
- [x] App that verifies inputs/timers don't fire for a session while it has async operations pending
- [x] App that verifies req(FALSE), req(FALSE, cancelOutput = TRUE), validate/need, etc. all work in async
## External packages
- [x] DT
- [x] htmlwidgets: Don't require async-aware version of Shiny if not using async
- [x] Plotly
## Bugs
- [x] req(FALSE, cancelOutput = TRUE) shows grey (even without async)

49
appveyor.yml Normal file
View File

@@ -0,0 +1,49 @@
# DO NOT CHANGE the "init" and "install" sections below
# Download script file from GitHub
init:
ps: |
$ErrorActionPreference = "Stop"
Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1"
Import-Module '..\appveyor-tool.ps1'
install:
ps: Bootstrap
cache:
- C:\RLibrary
# Adapt as necessary starting from here
build_script:
- travis-tool.sh install_deps
test_script:
- travis-tool.sh run_tests
on_failure:
- 7z a failure.zip *.Rcheck\*
- appveyor PushArtifact failure.zip
artifacts:
- path: '*.Rcheck\**\*.log'
name: Logs
- path: '*.Rcheck\**\*.out'
name: Logs
- path: '*.Rcheck\**\*.fail'
name: Logs
- path: '*.Rcheck\**\*.Rout'
name: Logs
- path: '\*_*.tar.gz'
name: Bits
- path: '\*_*.zip'
name: Bits
environment:
global:
USE_RTOOLS: true

View File

@@ -1,4 +1,3 @@
This small Shiny application demonstrates Shiny's automatic UI updates. Move
the *Number of bins* slider and notice how the `renderPlot` expression is
automatically re-evaluated when its dependant, `input$bins`, changes,
causing a histogram with a new number of bins to be rendered.
This small Shiny application demonstrates Shiny's automatic UI updates.
Move the *Number of bins* slider and notice how the `renderPlot` expression is automatically re-evaluated when its dependant, `input$bins`, changes, causing a histogram with a new number of bins to be rendered.

View File

@@ -0,0 +1,59 @@
library(shiny)
# Define UI for app that draws a histogram ----
ui <- fluidPage(
# App title ----
titlePanel("Hello Shiny!"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input: Slider for the number of bins ----
sliderInput(inputId = "bins",
label = "Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Histogram ----
plotOutput(outputId = "distPlot")
)
)
)
# Define server logic required to draw a histogram ----
server <- function(input, output) {
# Histogram of the Old Faithful Geyser Data ----
# with requested number of bins
# This expression that generates a histogram is wrapped in a call
# to renderPlot to indicate that:
#
# 1. It is "reactive" and therefore should be automatically
# re-executed when inputs (input$bins) change
# 2. Its output type is a plot
output$distPlot <- renderPlot({
x <- faithful$waiting
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = "#75AADB", border = "white",
xlab = "Waiting time to next eruption (in mins)",
main = "Histogram of waiting times")
})
}
# Create Shiny app ----
shinyApp(ui = ui, server = server)

View File

@@ -1,6 +0,0 @@
name: 01_hello
account: admin
server: localhost
bundleId: 1
url: http://localhost:3939/admin/01_hello/
when: 1436550957.65385

View File

@@ -1,21 +0,0 @@
library(shiny)
# Define server logic required to draw a histogram
shinyServer(function(input, output) {
# Expression that generates a histogram. The expression is
# wrapped in a call to renderPlot to indicate that:
#
# 1) It is "reactive" and therefore should be automatically
# re-executed when inputs change
# 2) Its output type is a plot
output$distPlot <- renderPlot({
x <- faithful[, 2] # Old Faithful Geyser data
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
})

View File

@@ -1,24 +0,0 @@
library(shiny)
# Define UI for application that draws a histogram
shinyUI(fluidPage(
# Application title
titlePanel("Hello Shiny!"),
# Sidebar with a slider input for the number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
))

View File

@@ -1 +1 @@
This example demonstrates output of raw text from R using the `renderPrint` function in `server.R` and the `verbatimTextOutput` function in `ui.R`. In this case, a textual summary of the data is shown using R's built-in `summary` function.
This example demonstrates output of raw text from R using the `renderPrint` function in `server` and the `verbatimTextOutput` function in `ui`. In this case, a textual summary of the data is shown using R's built-in `summary` function.

View File

@@ -0,0 +1,64 @@
library(shiny)
# Define UI for dataset viewer app ----
ui <- fluidPage(
# App title ----
titlePanel("Shiny Text"),
# Sidebar layout with a input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input: Selector for choosing dataset ----
selectInput(inputId = "dataset",
label = "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
# Input: Numeric entry for number of obs to view ----
numericInput(inputId = "obs",
label = "Number of observations to view:",
value = 10)
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Verbatim text for data summary ----
verbatimTextOutput("summary"),
# Output: HTML table with requested number of observations ----
tableOutput("view")
)
)
)
# Define server logic to summarize and view selected dataset ----
server <- function(input, output) {
# Return the requested dataset ----
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# Generate a summary of the dataset ----
output$summary <- renderPrint({
dataset <- datasetInput()
summary(dataset)
})
# Show the first "n" observations ----
output$view <- renderTable({
head(datasetInput(), n = input$obs)
})
}
# Create Shiny app ----
shinyApp(ui = ui, server = server)

View File

@@ -1,26 +0,0 @@
library(shiny)
library(datasets)
# Define server logic required to summarize and view the selected
# dataset
shinyServer(function(input, output) {
# Return the requested dataset
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# Generate a summary of the dataset
output$summary <- renderPrint({
dataset <- datasetInput()
summary(dataset)
})
# Show the first "n" observations
output$view <- renderTable({
head(datasetInput(), n = input$obs)
})
})

View File

@@ -1,27 +0,0 @@
library(shiny)
# Define UI for dataset viewer application
shinyUI(fluidPage(
# Application title
titlePanel("Shiny Text"),
# Sidebar with controls to select a dataset and specify the
# number of observations to view
sidebarLayout(
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Number of observations to view:", 10)
),
# Show a summary of the dataset and an HTML table with the
# requested number of observations
mainPanel(
verbatimTextOutput("summary"),
tableOutput("view")
)
)
))

View File

@@ -1,5 +1,5 @@
This example demonstrates a core feature of Shiny: **reactivity**. In `server.R`, a reactive called `datasetInput` is declared.
This example demonstrates a core feature of Shiny: **reactivity**. In the `server` function, a reactive called `datasetInput` is declared.
Notice that the reactive expression depends on the input expression `input$dataset`, and that it's used by both the output expression `output$summary` and `output$view`. Try changing the dataset (using *Choose a dataset*) while looking at the reactive and then at the outputs; you will see first the reactive and then its dependencies flash.
Notice that the reactive expression depends on the input expression `input$dataset`, and that it's used by two output expressions: `output$summary` and `output$view`. Try changing the dataset (using *Choose a dataset*) while looking at the reactive and then at the outputs; you will see first the reactive and then its dependencies flash.
Notice also that the reactive expression doesn't just update whenever anything changes--only the inputs it depends on will trigger an update. Change the "Caption" field and notice how only the `output$caption` expression is re-evaluated; the reactive and its dependents are left alone.

View File

@@ -0,0 +1,102 @@
library(shiny)
# Define UI for dataset viewer app ----
ui <- fluidPage(
# App title ----
titlePanel("Reactivity"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input: Text for providing a caption ----
# Note: Changes made to the caption in the textInput control
# are updated in the output area immediately as you type
textInput(inputId = "caption",
label = "Caption:",
value = "Data Summary"),
# Input: Selector for choosing dataset ----
selectInput(inputId = "dataset",
label = "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
# Input: Numeric entry for number of obs to view ----
numericInput(inputId = "obs",
label = "Number of observations to view:",
value = 10)
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Formatted text for caption ----
h3(textOutput("caption", container = span)),
# Output: Verbatim text for data summary ----
verbatimTextOutput("summary"),
# Output: HTML table with requested number of observations ----
tableOutput("view")
)
)
)
# Define server logic to summarize and view selected dataset ----
server <- function(input, output) {
# Return the requested dataset ----
# By declaring datasetInput as a reactive expression we ensure
# that:
#
# 1. It is only called when the inputs it depends on changes
# 2. The computation and result are shared by all the callers,
# i.e. it only executes a single time
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# Create caption ----
# The output$caption is computed based on a reactive expression
# that returns input$caption. When the user changes the
# "caption" field:
#
# 1. This function is automatically called to recompute the output
# 2. New caption is pushed back to the browser for re-display
#
# Note that because the data-oriented reactive expressions
# below don't depend on input$caption, those expressions are
# NOT called when input$caption changes
output$caption <- renderText({
input$caption
})
# Generate a summary of the dataset ----
# The output$summary depends on the datasetInput reactive
# expression, so will be re-executed whenever datasetInput is
# invalidated, i.e. whenever the input$dataset changes
output$summary <- renderPrint({
dataset <- datasetInput()
summary(dataset)
})
# Show the first "n" observations ----
# The output$view depends on both the databaseInput reactive
# expression and input$obs, so it will be re-executed whenever
# input$dataset or input$obs is changed
output$view <- renderTable({
head(datasetInput(), n = input$obs)
})
}
# Create Shiny app ----
shinyApp(ui, server)

View File

@@ -1,53 +0,0 @@
library(shiny)
library(datasets)
# Define server logic required to summarize and view the selected
# dataset
shinyServer(function(input, output) {
# By declaring datasetInput as a reactive expression we ensure
# that:
#
# 1) It is only called when the inputs it depends on changes
# 2) The computation and result are shared by all the callers
# (it only executes a single time)
#
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# The output$caption is computed based on a reactive expression
# that returns input$caption. When the user changes the
# "caption" field:
#
# 1) This function is automatically called to recompute the
# output
# 2) The new caption is pushed back to the browser for
# re-display
#
# Note that because the data-oriented reactive expressions
# below don't depend on input$caption, those expressions are
# NOT called when input$caption changes.
output$caption <- renderText({
input$caption
})
# The output$summary depends on the datasetInput reactive
# expression, so will be re-executed whenever datasetInput is
# invalidated
# (i.e. whenever the input$dataset changes)
output$summary <- renderPrint({
dataset <- datasetInput()
summary(dataset)
})
# The output$view depends on both the databaseInput reactive
# expression and input$obs, so will be re-executed whenever
# input$dataset or input$obs is changed.
output$view <- renderTable({
head(datasetInput(), n = input$obs)
})
})

View File

@@ -1,34 +0,0 @@
library(shiny)
# Define UI for dataset viewer application
shinyUI(fluidPage(
# Application title
titlePanel("Reactivity"),
# Sidebar with controls to provide a caption, select a dataset,
# and specify the number of observations to view. Note that
# changes made to the caption in the textInput control are
# updated in the output area immediately as you type
sidebarLayout(
sidebarPanel(
textInput("caption", "Caption:", "Data Summary"),
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Number of observations to view:", 10)
),
# Show the caption, a summary of the dataset and an HTML
# table with the requested number of observations
mainPanel(
h3(textOutput("caption", container = span)),
verbatimTextOutput("summary"),
tableOutput("view")
)
)
))

View File

@@ -1,4 +1,4 @@
This example demonstrates the following concepts:
* **Global variables**: The `mpgData` variable is declared outside the `shinyServer` function. This makes it available anywhere inside `shinyServer`. The code in `server.R` outside `shinyServer` is only run once when the app starts up, so it can't contain user input.
* **Reactive expressions**: `formulaText` is a reactive expression. Note how it re-evaluates when the Variable field is changed, but not when the Show Outliers box is ticked.
- **Global variables**: The `mpgData` variable is declared outside of the `ui` and `server` function definitions. This makes it available anywhere inside `app.R`. The code in `app.R` outside of `ui` and `server` function definitions is only run once when the app starts up, so it can't contain user input.
- **Reactive expressions**: `formulaText` is a reactive expression. Note how it re-evaluates when the Variable field is changed, but not when the Show Outliers box is unchecked.

View File

@@ -0,0 +1,75 @@
library(shiny)
library(datasets)
# Data pre-processing ----
# Tweak the "am" variable to have nicer factor labels -- since this
# doesn't rely on any user inputs, we can do this once at startup
# and then use the value throughout the lifetime of the app
mpgData <- mtcars
mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
# Define UI for miles per gallon app ----
ui <- fluidPage(
# App title ----
titlePanel("Miles Per Gallon"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input: Selector for variable to plot against mpg ----
selectInput("variable", "Variable:",
c("Cylinders" = "cyl",
"Transmission" = "am",
"Gears" = "gear")),
# Input: Checkbox for whether outliers should be included ----
checkboxInput("outliers", "Show outliers", TRUE)
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Formatted text for caption ----
h3(textOutput("caption")),
# Output: Plot of the requested variable against mpg ----
plotOutput("mpgPlot")
)
)
)
# Define server logic to plot various variables against mpg ----
server <- function(input, output) {
# Compute the formula text ----
# This is in a reactive expression since it is shared by the
# output$caption and output$mpgPlot functions
formulaText <- reactive({
paste("mpg ~", input$variable)
})
# Return the formula text for printing as a caption ----
output$caption <- renderText({
formulaText()
})
# Generate a plot of the requested variable against mpg ----
# and only exclude outliers if requested
output$mpgPlot <- renderPlot({
boxplot(as.formula(formulaText()),
data = mpgData,
outline = input$outliers,
col = "#75AADB", pch = 19)
})
}
# Create Shiny app ----
shinyApp(ui, server)

View File

@@ -1,34 +0,0 @@
library(shiny)
library(datasets)
# We tweak the "am" field to have nicer factor labels. Since
# this doesn't rely on any user inputs we can do this once at
# startup and then use the value throughout the lifetime of the
# application
mpgData <- mtcars
mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
# Define server logic required to plot various variables against
# mpg
shinyServer(function(input, output) {
# Compute the formula text in a reactive expression since it is
# shared by the output$caption and output$mpgPlot functions
formulaText <- reactive({
paste("mpg ~", input$variable)
})
# Return the formula text for printing as a caption
output$caption <- renderText({
formulaText()
})
# Generate a plot of the requested variable against mpg and
# only include outliers if requested
output$mpgPlot <- renderPlot({
boxplot(as.formula(formulaText()),
data = mpgData,
outline = input$outliers)
})
})

View File

@@ -1,29 +0,0 @@
library(shiny)
# Define UI for miles per gallon application
shinyUI(fluidPage(
# Application title
titlePanel("Miles Per Gallon"),
# Sidebar with controls to select the variable to plot against
# mpg and to specify whether outliers should be included
sidebarLayout(
sidebarPanel(
selectInput("variable", "Variable:",
c("Cylinders" = "cyl",
"Transmission" = "am",
"Gears" = "gear")),
checkboxInput("outliers", "Show outliers", FALSE)
),
# Show the caption and plot of the requested variable against
# mpg
mainPanel(
h3(textOutput("caption")),
plotOutput("mpgPlot")
)
)
))

View File

@@ -0,0 +1,86 @@
library(shiny)
# Define UI for slider demo app ----
ui <- fluidPage(
# App title ----
titlePanel("Sliders"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar to demonstrate various slider options ----
sidebarPanel(
# Input: Simple integer interval ----
sliderInput("integer", "Integer:",
min = 0, max = 1000,
value = 500),
# Input: Decimal interval with step value ----
sliderInput("decimal", "Decimal:",
min = 0, max = 1,
value = 0.5, step = 0.1),
# Input: Specification of range within an interval ----
sliderInput("range", "Range:",
min = 1, max = 1000,
value = c(200,500)),
# Input: Custom currency format for with basic animation ----
sliderInput("format", "Custom Format:",
min = 0, max = 10000,
value = 0, step = 2500,
pre = "$", sep = ",",
animate = TRUE),
# Input: Animation with custom interval (in ms) ----
# to control speed, plus looping
sliderInput("animation", "Looping Animation:",
min = 1, max = 2000,
value = 1, step = 10,
animate =
animationOptions(interval = 300, loop = TRUE))
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Table summarizing the values entered ----
tableOutput("values")
)
)
)
# Define server logic for slider examples ----
server <- function(input, output) {
# Reactive expression to create data frame of all input values ----
sliderValues <- reactive({
data.frame(
Name = c("Integer",
"Decimal",
"Range",
"Custom Format",
"Animation"),
Value = as.character(c(input$integer,
input$decimal,
paste(input$range, collapse = " "),
input$format,
input$animation)),
stringsAsFactors = FALSE)
})
# Show the values in an HTML table ----
output$values <- renderTable({
sliderValues()
})
}
# Create Shiny app ----
shinyApp(ui, server)

View File

@@ -1,29 +0,0 @@
library(shiny)
# Define server logic for slider examples
shinyServer(function(input, output) {
# Reactive expression to compose a data frame containing all of
# the values
sliderValues <- reactive({
# Compose data frame
data.frame(
Name = c("Integer",
"Decimal",
"Range",
"Custom Format",
"Animation"),
Value = as.character(c(input$integer,
input$decimal,
paste(input$range, collapse=' '),
input$format,
input$animation)),
stringsAsFactors=FALSE)
})
# Show the values using an HTML table
output$values <- renderTable({
sliderValues()
})
})

View File

@@ -1,43 +0,0 @@
library(shiny)
# Define UI for slider demo application
shinyUI(fluidPage(
# Application title
titlePanel("Sliders"),
# Sidebar with sliders that demonstrate various available
# options
sidebarLayout(
sidebarPanel(
# Simple integer interval
sliderInput("integer", "Integer:",
min=0, max=1000, value=500),
# Decimal interval with step value
sliderInput("decimal", "Decimal:",
min = 0, max = 1, value = 0.5, step= 0.1),
# Specification of range within an interval
sliderInput("range", "Range:",
min = 1, max = 1000, value = c(200,500)),
# Provide a custom currency format for value display,
# with basic animation
sliderInput("format", "Custom Format:",
min = 0, max = 10000, value = 0, step = 2500,
pre = "$", sep = ",", animate=TRUE),
# Animation with custom interval (in ms) to control speed,
# plus looping
sliderInput("animation", "Looping Animation:", 1, 2000, 1,
step = 10, animate=
animationOptions(interval=300, loop=TRUE))
),
# Show a table summarizing the values entered
mainPanel(
tableOutput("values")
)
)
))

View File

@@ -2,7 +2,7 @@ This example demonstrates the `tabsetPanel` and `tabPanel` widgets.
Notice that outputs that are not visible are not re-evaluated until they become visible. Try this:
1. Scroll to the bottom of `server.R`
1. Scroll to the bottom of the `server` function. You might need to use the *show with app* option so you can easily view the code and interact with the app at the same time.
2. Change the number of observations, and observe that only `output$plot` is evaluated.
3. Click the Summary tab, and observe that `output$summary` is evaluated.
4. Change the number of observations again, and observe that now only `output$summary` is evaluated.

View File

@@ -0,0 +1,92 @@
library(shiny)
# Define UI for random distribution app ----
ui <- fluidPage(
# App title ----
titlePanel("Tabsets"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input: Select the random distribution type ----
radioButtons("dist", "Distribution type:",
c("Normal" = "norm",
"Uniform" = "unif",
"Log-normal" = "lnorm",
"Exponential" = "exp")),
# br() element to introduce extra vertical spacing ----
br(),
# Input: Slider for the number of observations to generate ----
sliderInput("n",
"Number of observations:",
value = 500,
min = 1,
max = 1000)
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Tabset w/ plot, summary, and table ----
tabsetPanel(type = "tabs",
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Table", tableOutput("table"))
)
)
)
)
# Define server logic for random distribution app ----
server <- function(input, output) {
# Reactive expression to generate the requested distribution ----
# This is called whenever the inputs change. The output functions
# defined below then use the value computed from this expression
d <- reactive({
dist <- switch(input$dist,
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
dist(input$n)
})
# Generate a plot of the data ----
# Also uses the inputs to build the plot label. Note that the
# dependencies on the inputs and the data reactive expression are
# both tracked, and all expressions are called in the sequence
# implied by the dependency graph.
output$plot <- renderPlot({
dist <- input$dist
n <- input$n
hist(d(),
main = paste("r", dist, "(", n, ")", sep = ""),
col = "#75AADB", border = "white")
})
# Generate a summary of the data ----
output$summary <- renderPrint({
summary(d())
})
# Generate an HTML table view of the data ----
output$table <- renderTable({
d()
})
}
# Create Shiny app ----
shinyApp(ui, server)

View File

@@ -1,44 +0,0 @@
library(shiny)
# Define server logic for random distribution application
shinyServer(function(input, output) {
# Reactive expression to generate the requested distribution.
# This is called whenever the inputs change. The output
# functions defined below then all use the value computed from
# this expression
data <- reactive({
dist <- switch(input$dist,
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
dist(input$n)
})
# Generate a plot of the data. Also uses the inputs to build
# the plot label. Note that the dependencies on both the inputs
# and the data reactive expression are both tracked, and
# all expressions are called in the sequence implied by the
# dependency graph
output$plot <- renderPlot({
dist <- input$dist
n <- input$n
hist(data(),
main=paste('r', dist, '(', n, ')', sep=''))
})
# Generate a summary of the data
output$summary <- renderPrint({
summary(data())
})
# Generate an HTML table view of the data
output$table <- renderTable({
data.frame(x=data())
})
})

View File

@@ -1,38 +0,0 @@
library(shiny)
# Define UI for random distribution application
shinyUI(fluidPage(
# Application title
titlePanel("Tabsets"),
# Sidebar with controls to select the random distribution type
# and number of observations to generate. Note the use of the
# br() element to introduce extra vertical spacing
sidebarLayout(
sidebarPanel(
radioButtons("dist", "Distribution type:",
c("Normal" = "norm",
"Uniform" = "unif",
"Log-normal" = "lnorm",
"Exponential" = "exp")),
br(),
sliderInput("n",
"Number of observations:",
value = 500,
min = 1,
max = 1000)
),
# Show a tabset that includes a plot, summary, and table view
# of the generated distribution
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Table", tableOutput("table"))
)
)
)
))

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