mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 16:08:19 -05:00
Compare commits
1485 Commits
cache-even
...
barret/deb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2acaea1444 | ||
|
|
1120cfdfd7 | ||
|
|
d95560db09 | ||
|
|
98f64df738 | ||
|
|
8634e372da | ||
|
|
cbda7633e0 | ||
|
|
8f00cf50ca | ||
|
|
acca28075b | ||
|
|
9a563463dd | ||
|
|
1ede94b09e | ||
|
|
7f41a54c4e | ||
|
|
6cfab79ce9 | ||
|
|
f33b3c7eef | ||
|
|
93d78ae2b0 | ||
|
|
c498b02289 | ||
|
|
30b62e6f18 | ||
|
|
ec18ef651b | ||
|
|
aad23686fa | ||
|
|
1c85ecd7c0 | ||
|
|
a1ff765235 | ||
|
|
a30ba9226d | ||
|
|
980a1e53a7 | ||
|
|
00092cd2a8 | ||
|
|
53ddb54936 | ||
|
|
ea1e307a51 | ||
|
|
17bc1e2e06 | ||
|
|
ed8f3b730b | ||
|
|
0e109d5237 | ||
|
|
f672226a3d | ||
|
|
481dccd085 | ||
|
|
9612f1c3c8 | ||
|
|
9e1e5f61a3 | ||
|
|
99a566f473 | ||
|
|
1012307467 | ||
|
|
b729f45eaf | ||
|
|
cab799e6ee | ||
|
|
a06322d155 | ||
|
|
d836cb2a2c | ||
|
|
2249c7a28a | ||
|
|
0d0422c0a7 | ||
|
|
5ea556ee77 | ||
|
|
a34496663b | ||
|
|
abeaa71d8d | ||
|
|
281a427718 | ||
|
|
03ab966cdc | ||
|
|
3dcb810346 | ||
|
|
d6eef8b3e1 | ||
|
|
a770f1cbf2 | ||
|
|
a5687df9b4 | ||
|
|
202881cbbd | ||
|
|
24ac3b9d8b | ||
|
|
47c1fb88b9 | ||
|
|
170b143b17 | ||
|
|
3854b49c35 | ||
|
|
bae4f604b5 | ||
|
|
36f32e14d3 | ||
|
|
9e521e6927 | ||
|
|
4176f541fc | ||
|
|
18f2afbf85 | ||
|
|
f9a94d9758 | ||
|
|
d1e672e3e3 | ||
|
|
202b924e63 | ||
|
|
4c3342aa99 | ||
|
|
b1e5dd1d1d | ||
|
|
d43ebfbdb9 | ||
|
|
cfadd8307b | ||
|
|
fa6cf9832d | ||
|
|
0075b0da33 | ||
|
|
59c6367cb5 | ||
|
|
194323a9ee | ||
|
|
1ca437e4ee | ||
|
|
77e43b9f50 | ||
|
|
a23f4b0224 | ||
|
|
0541c90980 | ||
|
|
b73a263de8 | ||
|
|
d7ffee68cf | ||
|
|
89cd42b904 | ||
|
|
404185eb8c | ||
|
|
8c29a81b12 | ||
|
|
9b42c6c379 | ||
|
|
03c2dd9e4b | ||
|
|
d8274c3d8c | ||
|
|
85b5fb090f | ||
|
|
2adef311ed | ||
|
|
7050d0b8ad | ||
|
|
8358144a4f | ||
|
|
44e083e0a9 | ||
|
|
a5418cf6ee | ||
|
|
c74630d6eb | ||
|
|
157d4ac9a9 | ||
|
|
8228613c01 | ||
|
|
b907e17b70 | ||
|
|
aa7000427c | ||
|
|
8562c90454 | ||
|
|
5a9a04cd72 | ||
|
|
6b32611356 | ||
|
|
770ebc394f | ||
|
|
c0d35e84b1 | ||
|
|
5e74478864 | ||
|
|
46852e2051 | ||
|
|
b9dded0bef | ||
|
|
6d05f403a5 | ||
|
|
8368634f85 | ||
|
|
8d57d909b4 | ||
|
|
9b7855d597 | ||
|
|
6a5e1b9998 | ||
|
|
af6e558699 | ||
|
|
26d4dddffd | ||
|
|
e2765b4881 | ||
|
|
9796b25f33 | ||
|
|
01b8d3a314 | ||
|
|
50c48de0de | ||
|
|
bfc90da054 | ||
|
|
9d8d6fd6b1 | ||
|
|
43344d9a78 | ||
|
|
01a593c857 | ||
|
|
1b2dd11a4c | ||
|
|
d90a2c4801 | ||
|
|
f065c21ee6 | ||
|
|
d0324bd497 | ||
|
|
e57fba07db | ||
|
|
5cb279cf4e | ||
|
|
4f728b0387 | ||
|
|
927ae08a47 | ||
|
|
a28dc47e30 | ||
|
|
b43ee13dd8 | ||
|
|
ad5ad5a675 | ||
|
|
198f7d171e | ||
|
|
549425cb81 | ||
|
|
6023165268 | ||
|
|
2a7273c254 | ||
|
|
8640934410 | ||
|
|
20bc4e7caa | ||
|
|
9f83058b78 | ||
|
|
ffedf29db4 | ||
|
|
00219d342a | ||
|
|
753400144d | ||
|
|
854a732f47 | ||
|
|
03eaf07526 | ||
|
|
d04c12d8cb | ||
|
|
afddd3543e | ||
|
|
d9459a855d | ||
|
|
efbcfca126 | ||
|
|
916675a9bd | ||
|
|
7b43617954 | ||
|
|
09b89bccfd | ||
|
|
1190ee07a9 | ||
|
|
c4dcf405bb | ||
|
|
c844ea6f07 | ||
|
|
19704c151a | ||
|
|
1281ba18cd | ||
|
|
f1f2fae420 | ||
|
|
5809070b05 | ||
|
|
2c492540ce | ||
|
|
61556b505d | ||
|
|
d6a4bc87e8 | ||
|
|
e49f4696e6 | ||
|
|
4219f50141 | ||
|
|
19da003291 | ||
|
|
f0765e3d6a | ||
|
|
8dac345512 | ||
|
|
ce101843f0 | ||
|
|
d56dc3a237 | ||
|
|
28cffb2e25 | ||
|
|
d2d169fea3 | ||
|
|
a5eb1b15d2 | ||
|
|
23dbb0b41c | ||
|
|
c72ae68de5 | ||
|
|
7c1f87aed3 | ||
|
|
6ec0ac1651 | ||
|
|
9dc84e5c2b | ||
|
|
1e17b55f49 | ||
|
|
6a4c8556a3 | ||
|
|
488f1c8b83 | ||
|
|
e2537d8e93 | ||
|
|
6d35cb6c77 | ||
|
|
8ce7f64679 | ||
|
|
5cd6250f05 | ||
|
|
4872cd91a5 | ||
|
|
08e84e2ca0 | ||
|
|
40889c9637 | ||
|
|
010ba6f214 | ||
|
|
2f8dc860ff | ||
|
|
88f61f0d45 | ||
|
|
16a5aa7489 | ||
|
|
89fd2b2ed3 | ||
|
|
a16769061e | ||
|
|
3be76145b7 | ||
|
|
e9d27fa632 | ||
|
|
be706e4bb7 | ||
|
|
b87c4d5623 | ||
|
|
03a260f26a | ||
|
|
ae3e007a5f | ||
|
|
cfbf76d898 | ||
|
|
0b82b121cb | ||
|
|
643ebb4946 | ||
|
|
8693eed3ec | ||
|
|
766b910150 | ||
|
|
545843ffe6 | ||
|
|
39e7b23d5b | ||
|
|
9d0db6f74c | ||
|
|
78fb25329c | ||
|
|
7db6a7b57a | ||
|
|
76f70179c0 | ||
|
|
d6aecfe9ae | ||
|
|
1354d3dec1 | ||
|
|
955ae817d8 | ||
|
|
aee9589c1a | ||
|
|
48ac0f55c3 | ||
|
|
831c0a340c | ||
|
|
210d297d18 | ||
|
|
afbcf9039e | ||
|
|
e8eadc1a09 | ||
|
|
f234b7015c | ||
|
|
8de38b3415 | ||
|
|
0f132fc180 | ||
|
|
e597c24f35 | ||
|
|
1d7a913d29 | ||
|
|
f89131205d | ||
|
|
abc6a98d0f | ||
|
|
9415e79ff3 | ||
|
|
6269022536 | ||
|
|
6ad2125ee7 | ||
|
|
f5d7523a4f | ||
|
|
ce31b9af7e | ||
|
|
fc5d980a52 | ||
|
|
9ea726732a | ||
|
|
db5f9cca73 | ||
|
|
06fb4f6972 | ||
|
|
f045f9cf1b | ||
|
|
1752f57c7d | ||
|
|
7eb4bc15b8 | ||
|
|
707b5ea851 | ||
|
|
fe9f679051 | ||
|
|
368a49be36 | ||
|
|
80f0c5f5d7 | ||
|
|
a5a7224228 | ||
|
|
d616cf045b | ||
|
|
8186ae060d | ||
|
|
c46e80c711 | ||
|
|
0735ebd7a8 | ||
|
|
391bbaa73b | ||
|
|
fed96c0e45 | ||
|
|
f97f89a371 | ||
|
|
6352a5322b | ||
|
|
3473427484 | ||
|
|
d6c1733f0c | ||
|
|
d641ac197b | ||
|
|
6a0e41b05e | ||
|
|
e21a9a095e | ||
|
|
56e1a0b939 | ||
|
|
f9f9127a64 | ||
|
|
e0628c4ae3 | ||
|
|
43be342dea | ||
|
|
9cc7419700 | ||
|
|
f559caf4d0 | ||
|
|
9f55cd46d8 | ||
|
|
5956f6b123 | ||
|
|
41e42b8a53 | ||
|
|
f6f5fbd6fb | ||
|
|
c7618e3991 | ||
|
|
da6df5da9e | ||
|
|
480cc79de4 | ||
|
|
231c13d9a5 | ||
|
|
000406ec0b | ||
|
|
44e0a8bcb2 | ||
|
|
d2e88c7a2f | ||
|
|
638cddcd5e | ||
|
|
a3924f4ab1 | ||
|
|
5798c396ec | ||
|
|
b1983f0a83 | ||
|
|
aca9f562e1 | ||
|
|
8c6a830521 | ||
|
|
9142cf19c0 | ||
|
|
887b7fb34a | ||
|
|
1392547783 | ||
|
|
735b9b8c7a | ||
|
|
cba974ec34 | ||
|
|
421d588a2f | ||
|
|
8b848277d2 | ||
|
|
8ae19c7243 | ||
|
|
1e5051ef79 | ||
|
|
d293dbc10f | ||
|
|
703f481a9a | ||
|
|
68f0c12cab | ||
|
|
8c7598f45d | ||
|
|
192c4f239e | ||
|
|
64e09315fc | ||
|
|
db0c4155b8 | ||
|
|
f971bfd80a | ||
|
|
948244b45c | ||
|
|
c7fecbed7a | ||
|
|
f22cae98ef | ||
|
|
4ba02c97a7 | ||
|
|
0581dc7763 | ||
|
|
77261d4872 | ||
|
|
cfd14ef169 | ||
|
|
c7cc76b044 | ||
|
|
44e1096753 | ||
|
|
dc1c48ad4e | ||
|
|
d9d29220cc | ||
|
|
6f744ef311 | ||
|
|
f7071f2231 | ||
|
|
ef75c9a35f | ||
|
|
4ca3c6c96a | ||
|
|
82e98410ed | ||
|
|
dcd92f03db | ||
|
|
ba6c747e55 | ||
|
|
b53b766ee5 | ||
|
|
cd737fccb5 | ||
|
|
7fb3acea96 | ||
|
|
867052f974 | ||
|
|
038e010819 | ||
|
|
7f5e42fdd5 | ||
|
|
2fe9b3dcbe | ||
|
|
89dbfcecbf | ||
|
|
f4bda6b91f | ||
|
|
dba72ac8a7 | ||
|
|
aedbfb11af | ||
|
|
43fd380e25 | ||
|
|
2872100ff0 | ||
|
|
afe81048c9 | ||
|
|
9d44857d77 | ||
|
|
4a7d186f27 | ||
|
|
5b1fd12edd | ||
|
|
8309a2aed9 | ||
|
|
e15d6a2239 | ||
|
|
abf04ac96f | ||
|
|
8ec6275f9a | ||
|
|
3e2bfb20f5 | ||
|
|
ee13087d57 | ||
|
|
f2fd7de9db | ||
|
|
dcfd7e05ce | ||
|
|
e47b69c33a | ||
|
|
383f78c8ca | ||
|
|
54042d5150 | ||
|
|
90e6ffc928 | ||
|
|
be65f49bbd | ||
|
|
d81b8ff98f | ||
|
|
9b5201e33c | ||
|
|
588c1b91b9 | ||
|
|
eb63734792 | ||
|
|
22cc585180 | ||
|
|
bd1631a649 | ||
|
|
e12bde6cdb | ||
|
|
7763ceefc0 | ||
|
|
05a7d998b9 | ||
|
|
4b676ac327 | ||
|
|
6ad1322734 | ||
|
|
03248735ac | ||
|
|
8e5651490c | ||
|
|
0d7aa2a101 | ||
|
|
83669ced3d | ||
|
|
2a33d23165 | ||
|
|
b7ae915784 | ||
|
|
9fcfa25460 | ||
|
|
946eae00bd | ||
|
|
e8ef33c9a1 | ||
|
|
8c8654f2d8 | ||
|
|
4b744791f2 | ||
|
|
5327cb33f9 | ||
|
|
d9ddc6fd90 | ||
|
|
a94c2cfa1e | ||
|
|
614bc6b480 | ||
|
|
47585174d8 | ||
|
|
5dd11bcc9b | ||
|
|
160d2123b2 | ||
|
|
ca55ed3a21 | ||
|
|
34fe820a26 | ||
|
|
c7a4d23662 | ||
|
|
432a7120f2 | ||
|
|
7e8b5d28f7 | ||
|
|
079871df38 | ||
|
|
c95d3ef07d | ||
|
|
acad455ccb | ||
|
|
fcc7df32ad | ||
|
|
c7f0484c37 | ||
|
|
3dac31a771 | ||
|
|
16357963d5 | ||
|
|
7d7492b9aa | ||
|
|
26dff7e00e | ||
|
|
cf410e310f | ||
|
|
5d4855f86c | ||
|
|
6d7e2b8a06 | ||
|
|
d37be0d059 | ||
|
|
d419ec5776 | ||
|
|
c01f100858 | ||
|
|
d8e380b53f | ||
|
|
6881c39c8d | ||
|
|
e7fa540403 | ||
|
|
e92ba27893 | ||
|
|
210792397d | ||
|
|
91385967c1 | ||
|
|
0c8d27964b | ||
|
|
ed4fcb71f1 | ||
|
|
0738f6a2d6 | ||
|
|
1e2a874067 | ||
|
|
9d2f8cbd8a | ||
|
|
dd1c653365 | ||
|
|
1a0a53a26f | ||
|
|
97ea4e2a26 | ||
|
|
b408d9348d | ||
|
|
44cfde7a0c | ||
|
|
db3d7ee436 | ||
|
|
a05f713e26 | ||
|
|
dfb492493c | ||
|
|
7ddf4169b8 | ||
|
|
89d6a3d91a | ||
|
|
16196eeaaa | ||
|
|
393d4163c8 | ||
|
|
5855aa2689 | ||
|
|
4e59f55f11 | ||
|
|
b269487a47 | ||
|
|
dce4028786 | ||
|
|
819ad4c770 | ||
|
|
b10b6d4833 | ||
|
|
a3d224beaf | ||
|
|
164ad8c521 | ||
|
|
4c8ec8befe | ||
|
|
0692334a27 | ||
|
|
be912cf2ce | ||
|
|
f942c088ec | ||
|
|
330da2dcbb | ||
|
|
afad0395ff | ||
|
|
ecd72f1bc0 | ||
|
|
867daeead7 | ||
|
|
a20c3a397e | ||
|
|
a1a22e811f | ||
|
|
3fbbabd68a | ||
|
|
47c1202535 | ||
|
|
83a5feaaa8 | ||
|
|
6e767fc71d | ||
|
|
303f264326 | ||
|
|
f73671845c | ||
|
|
82c04caf3a | ||
|
|
d8080d1336 | ||
|
|
fc09d1c09a | ||
|
|
65a47c01ec | ||
|
|
e9f2e0d7d7 | ||
|
|
ddcb31897d | ||
|
|
eecdc0e24c | ||
|
|
f642bcc954 | ||
|
|
7dedac5880 | ||
|
|
ee5362f81a | ||
|
|
1475137d4d | ||
|
|
8ba028ebbb | ||
|
|
7cd385e8c2 | ||
|
|
41694b3666 | ||
|
|
25314f370e | ||
|
|
664b88c1bc | ||
|
|
d6adffa273 | ||
|
|
5bd039a335 | ||
|
|
0782cc3c21 | ||
|
|
c73628cca1 | ||
|
|
8ffc5aa20c | ||
|
|
89c2f09864 | ||
|
|
ee3115653c | ||
|
|
48115fc150 | ||
|
|
d804a363ae | ||
|
|
867c084990 | ||
|
|
8ffbfca97b | ||
|
|
ca9a72d25c | ||
|
|
acdbe8ef5e | ||
|
|
5cc3a5b71c | ||
|
|
bd587fd21b | ||
|
|
0f580ff23d | ||
|
|
b0b105babc | ||
|
|
3b0cc5f3a8 | ||
|
|
e50981ccc0 | ||
|
|
24f3c20f26 | ||
|
|
ca5d71a491 | ||
|
|
a022a2b4a4 | ||
|
|
0cb618b9b1 | ||
|
|
1f4927683e | ||
|
|
7c74399a5d | ||
|
|
52903b6ecd | ||
|
|
a43244916b | ||
|
|
35be892e69 | ||
|
|
536e8ffb28 | ||
|
|
0241f07105 | ||
|
|
3570af90ab | ||
|
|
fa3fa9e2ef | ||
|
|
83e2bb028f | ||
|
|
f50b7c4301 | ||
|
|
41c9a0c395 | ||
|
|
12401b6588 | ||
|
|
8edf8905a5 | ||
|
|
d5cb8d187c | ||
|
|
328a066f0f | ||
|
|
42d314d592 | ||
|
|
d89d546e53 | ||
|
|
1a558143c7 | ||
|
|
ad7ffa2245 | ||
|
|
717ac420d9 | ||
|
|
abff323eb6 | ||
|
|
03bc1ccd4a | ||
|
|
da408eeaff | ||
|
|
a2ba9bb26a | ||
|
|
16c41ed046 | ||
|
|
aeb3c9f094 | ||
|
|
2562cc8220 | ||
|
|
0647cd85e9 | ||
|
|
d57e7389d2 | ||
|
|
3cb3316a95 | ||
|
|
8ba03e1205 | ||
|
|
6a69d3c07b | ||
|
|
c054b8c9ab | ||
|
|
db6f7cceea | ||
|
|
0898ee1fba | ||
|
|
6366c0a684 | ||
|
|
f56eb42c90 | ||
|
|
6f3f21921e | ||
|
|
b8c016c3e9 | ||
|
|
e5d3b1c1d5 | ||
|
|
fe140b6319 | ||
|
|
4e1e0aad8a | ||
|
|
84a5515a3d | ||
|
|
0d5073f8ff | ||
|
|
05a4a101db | ||
|
|
848f18be2b | ||
|
|
21c9079087 | ||
|
|
2935192eec | ||
|
|
f896db033f | ||
|
|
b197afe1a0 | ||
|
|
dd07f7f580 | ||
|
|
8376f9093b | ||
|
|
38b8ed7bf9 | ||
|
|
aa74ea0d0a | ||
|
|
e5d3f62043 | ||
|
|
d2d0e70678 | ||
|
|
aceb7d0467 | ||
|
|
c7ac1fa630 | ||
|
|
5855a5b26c | ||
|
|
0301af62b8 | ||
|
|
32e9757bf7 | ||
|
|
d2b883c4b5 | ||
|
|
816f40a2d5 | ||
|
|
7e7f38005a | ||
|
|
fb834f7207 | ||
|
|
5a3e5296d0 | ||
|
|
a0e8d8f2d8 | ||
|
|
9c6dfff531 | ||
|
|
84d9580bae | ||
|
|
8d6de642ea | ||
|
|
b20b812cfe | ||
|
|
9b23ff6a19 | ||
|
|
cc5278a117 | ||
|
|
ca6459afe4 | ||
|
|
f8477f007d | ||
|
|
82d1ad278c | ||
|
|
761fb608d3 | ||
|
|
af328eee90 | ||
|
|
0fde11ae72 | ||
|
|
73919b1943 | ||
|
|
1433439215 | ||
|
|
4c8dc09f67 | ||
|
|
80b43942b0 | ||
|
|
b709b53b6a | ||
|
|
f4e3e5b618 | ||
|
|
bac7299359 | ||
|
|
fc6f535edd | ||
|
|
7e2ffab62c | ||
|
|
214d721380 | ||
|
|
2f8227e652 | ||
|
|
c0c02d290f | ||
|
|
bc2aa71888 | ||
|
|
7f187d1553 | ||
|
|
81b1f4fdc1 | ||
|
|
15f088f10a | ||
|
|
286f12522b | ||
|
|
9d8a6d0142 | ||
|
|
a2dd97cc74 | ||
|
|
1d9a6ea3c0 | ||
|
|
3ca8b1017b | ||
|
|
ecd7c76aee | ||
|
|
70edcd62b9 | ||
|
|
90f531888c | ||
|
|
953de733e7 | ||
|
|
e0ed443319 | ||
|
|
1487720fd8 | ||
|
|
828567e0ce | ||
|
|
78da4c7fce | ||
|
|
7f80bfd2cb | ||
|
|
7e3deb5e3f | ||
|
|
5475ec4f0c | ||
|
|
58b4585b57 | ||
|
|
cf9ab1c47b | ||
|
|
65233cdd5c | ||
|
|
9d13cb644d | ||
|
|
dd9e0343e8 | ||
|
|
bb4aaa2a78 | ||
|
|
0023418b94 | ||
|
|
ec2c9ecea0 | ||
|
|
59759398a6 | ||
|
|
c4852cb451 | ||
|
|
99880d6e8a | ||
|
|
b005799d92 | ||
|
|
72f86dac27 | ||
|
|
83628facb3 | ||
|
|
f6e171823a | ||
|
|
9b743a319f | ||
|
|
eedf2a6cc8 | ||
|
|
e1e738f772 | ||
|
|
182ff3df88 | ||
|
|
23fde95f9e | ||
|
|
78f9132eb3 | ||
|
|
84b7211588 | ||
|
|
2793e15c26 | ||
|
|
36bd76607a | ||
|
|
e17f416bb0 | ||
|
|
a577b1e22e | ||
|
|
2d324c77c1 | ||
|
|
88374eca74 | ||
|
|
386135788b | ||
|
|
a943d955dd | ||
|
|
15476ac32e | ||
|
|
17fb5b9eae | ||
|
|
fd27a0dfa2 | ||
|
|
5ffe69ec6c | ||
|
|
f5723b2a4d | ||
|
|
9e959a88f1 | ||
|
|
09abac41c5 | ||
|
|
1dbf013c1b | ||
|
|
a637d5b126 | ||
|
|
d409183751 | ||
|
|
e8feef1ce0 | ||
|
|
01491cc696 | ||
|
|
568a3f28cf | ||
|
|
02219df480 | ||
|
|
e006ca51ee | ||
|
|
86f651f3ec | ||
|
|
212b33a0ce | ||
|
|
6b7a121161 | ||
|
|
c89da718b1 | ||
|
|
eef3ae8387 | ||
|
|
0975a61725 | ||
|
|
0c53d54347 | ||
|
|
cbbb04cf69 | ||
|
|
120baf0a6e | ||
|
|
685dc7cc3a | ||
|
|
2fbb2ac77b | ||
|
|
2832db7aba | ||
|
|
18f2471d7c | ||
|
|
ea28f5a61b | ||
|
|
fe9cc6038e | ||
|
|
5ed335c499 | ||
|
|
fd04b97496 | ||
|
|
4c9d281b59 | ||
|
|
a26d66b424 | ||
|
|
cfb683419f | ||
|
|
97887bdf02 | ||
|
|
38ea693e73 | ||
|
|
582a0ea6a5 | ||
|
|
71b9f0907e | ||
|
|
82b82b714d | ||
|
|
6356228053 | ||
|
|
18fd677550 | ||
|
|
d9698df721 | ||
|
|
63839fe045 | ||
|
|
2ee06a7cbf | ||
|
|
cf2ba90b1d | ||
|
|
8124b2143b | ||
|
|
5361573051 | ||
|
|
1d377c868d | ||
|
|
b0a855a326 | ||
|
|
fa35f29596 | ||
|
|
f429d23b6e | ||
|
|
eeeb903b70 | ||
|
|
78f12c4a75 | ||
|
|
c69f34d1e2 | ||
|
|
ccfcc5d8b4 | ||
|
|
210c248264 | ||
|
|
e3258657d0 | ||
|
|
dbc518bf53 | ||
|
|
cdbdb4510e | ||
|
|
e7ec5e5ba4 | ||
|
|
03d8a7f296 | ||
|
|
480035c065 | ||
|
|
b32c18cf72 | ||
|
|
337a6b276a | ||
|
|
06cf1f9477 | ||
|
|
190cfd2b7a | ||
|
|
63035b4d66 | ||
|
|
6a11c8fcb1 | ||
|
|
33ffb006e3 | ||
|
|
162e7f63a9 | ||
|
|
bb581eeec4 | ||
|
|
272c555bc5 | ||
|
|
fb64caab23 | ||
|
|
6f2a74a46d | ||
|
|
ec65a74492 | ||
|
|
ba791c42fa | ||
|
|
5896667c36 | ||
|
|
003c949d38 | ||
|
|
d31394254c | ||
|
|
1a497e246c | ||
|
|
d24276aa54 | ||
|
|
6ed21a3e6b | ||
|
|
8066f9ce96 | ||
|
|
a0276ec1ce | ||
|
|
2ab925a24c | ||
|
|
78fbad7d8d | ||
|
|
6652ae3042 | ||
|
|
aa12ab7d76 | ||
|
|
89be4bdce9 | ||
|
|
d09a064471 | ||
|
|
2b18ca5a6c | ||
|
|
6bc2f18bbf | ||
|
|
fbb892d84e | ||
|
|
4efb7c20e4 | ||
|
|
4beb1f07a6 | ||
|
|
45e640e5f9 | ||
|
|
e84beffee3 | ||
|
|
e07c7483a7 | ||
|
|
34ec7bf5eb | ||
|
|
01b20a4829 | ||
|
|
45ea898da4 | ||
|
|
fd34c5070f | ||
|
|
6c409d96c1 | ||
|
|
0cbe4bb3d4 | ||
|
|
d04a990235 | ||
|
|
4747c87632 | ||
|
|
f57452c7bf | ||
|
|
9a8e2eb675 | ||
|
|
8ef7f3cbe2 | ||
|
|
de30a65f01 | ||
|
|
0bcf613195 | ||
|
|
89fd9004d0 | ||
|
|
b2be108db1 | ||
|
|
6102c44b70 | ||
|
|
327cdc8e41 | ||
|
|
0bc3613989 | ||
|
|
30cea871f9 | ||
|
|
5f332fe4db | ||
|
|
7ee7f2716b | ||
|
|
5e8c39cb1e | ||
|
|
ee355200b3 | ||
|
|
986fbe2254 | ||
|
|
32f93a2be1 | ||
|
|
ab79065c13 | ||
|
|
77171b7894 | ||
|
|
cce8ddb84f | ||
|
|
648b7e5911 | ||
|
|
67a66fdc93 | ||
|
|
5fbaa26d05 | ||
|
|
1f4a3c4fd2 | ||
|
|
959dc7ffd4 | ||
|
|
0e34221cac | ||
|
|
0cad13b3a3 | ||
|
|
0776f71ca3 | ||
|
|
5a74e369ce | ||
|
|
799c5ac662 | ||
|
|
1080cf0ef4 | ||
|
|
867d49e3fb | ||
|
|
c7be406099 | ||
|
|
37257e77ce | ||
|
|
270d9ff0fc | ||
|
|
34b48598d9 | ||
|
|
5105ecb148 | ||
|
|
f47b151458 | ||
|
|
d3f15a58fc | ||
|
|
42f6adb7fa | ||
|
|
263f8a8e7d | ||
|
|
3a42d30cfd | ||
|
|
9275217a5a | ||
|
|
1fed19ad68 | ||
|
|
6a8a78abd1 | ||
|
|
de69f51084 | ||
|
|
c81a3f39fd | ||
|
|
6fcb925e34 | ||
|
|
8823b7280a | ||
|
|
ebadad97a8 | ||
|
|
a095c39626 | ||
|
|
fb9bcb44c3 | ||
|
|
38f593450a | ||
|
|
6d44f2c5cb | ||
|
|
d1786a64c4 | ||
|
|
616ae99c0b | ||
|
|
4d2ff80788 | ||
|
|
005295fd4c | ||
|
|
d6b46f8243 | ||
|
|
bac35e8f1b | ||
|
|
a003c4da85 | ||
|
|
0ae8e4fe8a | ||
|
|
d3667dfc77 | ||
|
|
54c5467dc6 | ||
|
|
d01f0300a5 | ||
|
|
bff207008f | ||
|
|
ed739f95ff | ||
|
|
bb4de1336c | ||
|
|
f7205558d2 | ||
|
|
1318544ecf | ||
|
|
a81c161434 | ||
|
|
73acdc755f | ||
|
|
dd84ea8fda | ||
|
|
a2a4e40821 | ||
|
|
509f54d68c | ||
|
|
27ce460ea4 | ||
|
|
116794ad77 | ||
|
|
89feba870d | ||
|
|
2a980601c0 | ||
|
|
e1fd8ae910 | ||
|
|
9cb415008c | ||
|
|
26ba9bf94a | ||
|
|
fb091ca195 | ||
|
|
99a7dca3ce | ||
|
|
a1a03d94be | ||
|
|
85a2d41a72 | ||
|
|
89bd7e9011 | ||
|
|
ececdf42a7 | ||
|
|
2cf03de8b8 | ||
|
|
c8daa1730b | ||
|
|
d195b595dd | ||
|
|
ff3f7adff2 | ||
|
|
37781a9df7 | ||
|
|
ca1c60e00e | ||
|
|
649f382291 | ||
|
|
103a35c81b | ||
|
|
5af341bfdb | ||
|
|
7c7110cd83 | ||
|
|
c4ea489bff | ||
|
|
60b3b6ff03 | ||
|
|
1510dca065 | ||
|
|
2c49375928 | ||
|
|
f9fc22c48b | ||
|
|
8d14e7ab04 | ||
|
|
8f2a28a1f2 | ||
|
|
e8fb1faec0 | ||
|
|
0e4874c412 | ||
|
|
933630af28 | ||
|
|
ff87098102 | ||
|
|
6513a86bbd | ||
|
|
97e296c5d5 | ||
|
|
9f87adf4e8 | ||
|
|
6470b3f08c | ||
|
|
d1ba84525e | ||
|
|
05ad66c464 | ||
|
|
c41d38bf61 | ||
|
|
b155e8480b | ||
|
|
e94f687573 | ||
|
|
5883082d01 | ||
|
|
75b53ffda1 | ||
|
|
a8057b96f3 | ||
|
|
a89e809498 | ||
|
|
02f7a4fdc9 | ||
|
|
7c7c22a597 | ||
|
|
860fa525a2 | ||
|
|
9f0e38a28a | ||
|
|
f834b7befb | ||
|
|
7f3a45fb5b | ||
|
|
b0953e810b | ||
|
|
52a86012e5 | ||
|
|
2a06fe6baf | ||
|
|
6e688d2175 | ||
|
|
b610fd1f56 | ||
|
|
a4730096f4 | ||
|
|
6a02439944 | ||
|
|
b889b0d2b0 | ||
|
|
ba5733e4a4 | ||
|
|
2e0221ecfd | ||
|
|
aeded79544 | ||
|
|
c0a7958e77 | ||
|
|
431b194ec2 | ||
|
|
29d24d7e08 | ||
|
|
3b04c642ae | ||
|
|
609fc5b0c0 | ||
|
|
2a8c79b577 | ||
|
|
043316e40f | ||
|
|
c9d8b987d4 | ||
|
|
33c5a5c665 | ||
|
|
29c90ba163 | ||
|
|
8c19450b10 | ||
|
|
89c97458c4 | ||
|
|
02be516902 | ||
|
|
47ada300ea | ||
|
|
6f9c621774 | ||
|
|
324d9195c3 | ||
|
|
0310fe3b68 | ||
|
|
7144a6e4b7 | ||
|
|
1c8071a96f | ||
|
|
4ad115e024 | ||
|
|
f11d754cfe | ||
|
|
65019ce96f | ||
|
|
90e8fb2a57 | ||
|
|
ff5377da9e | ||
|
|
7aee84eb05 | ||
|
|
c0a7a6d0d6 | ||
|
|
29c48471f2 | ||
|
|
229e56464b | ||
|
|
769c32fd38 | ||
|
|
d05b89cfb3 | ||
|
|
f1f18a2334 | ||
|
|
afc556f801 | ||
|
|
7f240839fc | ||
|
|
8d0a6274cb | ||
|
|
91cab10ff8 | ||
|
|
5828ea7426 | ||
|
|
80ba147168 | ||
|
|
f85479ba11 | ||
|
|
a23c5f151f | ||
|
|
cab3601474 | ||
|
|
cf330fcd58 | ||
|
|
eb0162dccf | ||
|
|
a415aed7e6 | ||
|
|
9f6014dc0b | ||
|
|
21b0d38b57 | ||
|
|
1ec7f22b5f | ||
|
|
346c5e4a4c | ||
|
|
c9a0f0a713 | ||
|
|
8bbc38dc8a | ||
|
|
96494a22f9 | ||
|
|
0813789e2a | ||
|
|
98ca820ab1 | ||
|
|
81ca9d9f29 | ||
|
|
16fe0019f9 | ||
|
|
5fa650ab75 | ||
|
|
564c2a0f16 | ||
|
|
1685e1c310 | ||
|
|
332f5a1266 | ||
|
|
99ac85f06a | ||
|
|
fc30ad0935 | ||
|
|
aadf2eb609 | ||
|
|
68f778e423 | ||
|
|
0066cff652 | ||
|
|
f872a0c80a | ||
|
|
68d67a8194 | ||
|
|
756ac1514c | ||
|
|
d9478142b1 | ||
|
|
5eced59961 | ||
|
|
3e1862cd51 | ||
|
|
7271609850 | ||
|
|
f24337bb3b | ||
|
|
6167247ea2 | ||
|
|
0332e52501 | ||
|
|
0c23f78ab7 | ||
|
|
7624449644 | ||
|
|
97309e8c4c | ||
|
|
a1e78214db | ||
|
|
1a57b3296b | ||
|
|
7c10fc3514 | ||
|
|
494ef42aa8 | ||
|
|
8a54d216c6 | ||
|
|
896a20d76d | ||
|
|
a26510b02f | ||
|
|
1465f1d237 | ||
|
|
21b18d107a | ||
|
|
cc2173c587 | ||
|
|
71fe821ae9 | ||
|
|
3ffab69ad6 | ||
|
|
58a662bd35 | ||
|
|
eb55c256c7 | ||
|
|
b07e553b9e | ||
|
|
2d61709de3 | ||
|
|
1352e1d92d | ||
|
|
b595c3b902 | ||
|
|
76efb01c4c | ||
|
|
0078945b79 | ||
|
|
70d8ef0b8e | ||
|
|
9a1f7cba68 | ||
|
|
39e14acffe | ||
|
|
a6149390a0 | ||
|
|
33cdc75810 | ||
|
|
13f229089d | ||
|
|
2dbb0fca85 | ||
|
|
c7a8a4e30f | ||
|
|
dc6f1a0c10 | ||
|
|
178872d651 | ||
|
|
e3c15493a2 | ||
|
|
3f22e5da2d | ||
|
|
39ee4513c6 | ||
|
|
598898f0a1 | ||
|
|
052e783638 | ||
|
|
d2deda238a | ||
|
|
7317a8304f | ||
|
|
5ea9d70fb4 | ||
|
|
a73e0998bc | ||
|
|
51befe3e27 | ||
|
|
37569a291b | ||
|
|
7fe973145d | ||
|
|
da3fc276fd | ||
|
|
4c0af8b1c0 | ||
|
|
f65f7b2f1b | ||
|
|
33c86ed6a7 | ||
|
|
545b6c1247 | ||
|
|
1b0e37f371 | ||
|
|
97e00721e9 | ||
|
|
3c43301edb | ||
|
|
51cbb67a96 | ||
|
|
2e2bd80416 | ||
|
|
86389ff7a3 | ||
|
|
c2dfea18c4 | ||
|
|
4be6bbc681 | ||
|
|
cfc0ff9cc7 | ||
|
|
b4c6ba6962 | ||
|
|
dc3ed2f79b | ||
|
|
5d95c7a9cb | ||
|
|
6821ca6238 | ||
|
|
167dc0a259 | ||
|
|
fa9fa68693 | ||
|
|
353615da89 | ||
|
|
51de558675 | ||
|
|
174fc1dda1 | ||
|
|
7caeb60c47 | ||
|
|
e3aba1b5ff | ||
|
|
1a8b36f06d | ||
|
|
250303790c | ||
|
|
e20544659a | ||
|
|
ad7692ed34 | ||
|
|
f9144a4be3 | ||
|
|
0fc3b90efb | ||
|
|
25ccc8a77a | ||
|
|
da18390f3e | ||
|
|
b392bf8298 | ||
|
|
73c42ebeaf | ||
|
|
eb45f7fcba | ||
|
|
5fdca29448 | ||
|
|
048c4006e4 | ||
|
|
6d10a2dafb | ||
|
|
d6c421f8de | ||
|
|
ac4adcc62c | ||
|
|
bc8465d284 | ||
|
|
7fc497eeb8 | ||
|
|
633817e3d5 | ||
|
|
09dee9670a | ||
|
|
631debbec4 | ||
|
|
4e57bc2161 | ||
|
|
a111e36867 | ||
|
|
152bd5841c | ||
|
|
ecefdcd951 | ||
|
|
a976cfa98d | ||
|
|
df70d7708d | ||
|
|
c558d95e3b | ||
|
|
3bd4825d4b | ||
|
|
67cdcedd4e | ||
|
|
384116a76f | ||
|
|
12e91ae643 | ||
|
|
7c56d277da | ||
|
|
579a4592b8 | ||
|
|
9dad5e6362 | ||
|
|
f51b5421f2 | ||
|
|
387907ea32 | ||
|
|
b5ca1d48e0 | ||
|
|
396f170738 | ||
|
|
5514039d42 | ||
|
|
c90c4f3673 | ||
|
|
41758858cf | ||
|
|
3ff507e6b8 | ||
|
|
5199371025 | ||
|
|
26ad773f77 | ||
|
|
9e133e0ecc | ||
|
|
c4e7099229 | ||
|
|
56062628f2 | ||
|
|
48a3a1dabb | ||
|
|
ca3c2b3e26 | ||
|
|
d35c5c8320 | ||
|
|
749a582296 | ||
|
|
6310406430 | ||
|
|
d26d339f97 | ||
|
|
17afce6fa1 | ||
|
|
d3aa601798 | ||
|
|
8f24d667d6 | ||
|
|
5cd4588ef2 | ||
|
|
b0a1821d95 | ||
|
|
6b835f70e6 | ||
|
|
308bc76ac6 | ||
|
|
fd843509a1 | ||
|
|
7691cfdadb | ||
|
|
1aa9368e54 | ||
|
|
180e852fee | ||
|
|
547edd7e32 | ||
|
|
0b46c63c31 | ||
|
|
9b69ce1988 | ||
|
|
57cc44f662 | ||
|
|
4eaa9c7ea9 | ||
|
|
0b6cdcc826 | ||
|
|
7bc0a0ca39 | ||
|
|
1ef2074a10 | ||
|
|
0747b2a72a | ||
|
|
64b3095f2c | ||
|
|
ab82af122f | ||
|
|
54fccf2e7c | ||
|
|
05e953db3a | ||
|
|
f726835850 | ||
|
|
38d2809131 | ||
|
|
d7718991a6 | ||
|
|
32c2bff6eb | ||
|
|
555ede03ed | ||
|
|
2a6f218700 | ||
|
|
b087c19b52 | ||
|
|
6fed1c60ac | ||
|
|
b10f2a5291 | ||
|
|
a4a49a354e | ||
|
|
ead23528ca | ||
|
|
b8644949cc | ||
|
|
b88e3a64f2 | ||
|
|
2871b423fd | ||
|
|
562fafbc39 | ||
|
|
191e0874f8 | ||
|
|
fa5ff7bfa5 | ||
|
|
82e80ccdeb | ||
|
|
ff84cf5a18 | ||
|
|
44843a7768 | ||
|
|
68eeb338da | ||
|
|
ea54c17902 | ||
|
|
d5ad7eed40 | ||
|
|
c2430cd3f4 | ||
|
|
8a0731493f | ||
|
|
07e2b80b5d | ||
|
|
1311e1fca2 | ||
|
|
e6c2133520 | ||
|
|
3d6f734ff2 | ||
|
|
e0eaa58779 | ||
|
|
ced6622b25 | ||
|
|
2d2cf96f5e | ||
|
|
370f1b51ee | ||
|
|
67d3a504ae | ||
|
|
34ee48ef93 | ||
|
|
c61a585e79 | ||
|
|
09388c9f07 | ||
|
|
b1bc78dad3 | ||
|
|
a5a0f23c3a | ||
|
|
4c50c064d3 | ||
|
|
a63f271300 | ||
|
|
08b22ff550 | ||
|
|
b04133bf65 | ||
|
|
3602358d2c | ||
|
|
67b0416eba | ||
|
|
f8d69ecb1f | ||
|
|
5e8bc204c1 | ||
|
|
938332d646 | ||
|
|
386078d441 | ||
|
|
4d778faaf4 | ||
|
|
3055cf5602 | ||
|
|
36373ba28b | ||
|
|
1415b57181 | ||
|
|
65d4a4e906 | ||
|
|
0abe221227 | ||
|
|
1b8d822226 | ||
|
|
bc8fbd60d7 | ||
|
|
4c332eac9a | ||
|
|
f5392d77dc | ||
|
|
1e88990a0b | ||
|
|
de4c7567d0 | ||
|
|
aff33dd023 | ||
|
|
a287ebe324 | ||
|
|
583a8d1001 | ||
|
|
36a808add0 | ||
|
|
f651d4a274 | ||
|
|
f6e8e645f2 | ||
|
|
b4d2f88b74 | ||
|
|
c524a736bd | ||
|
|
cdf3bf18f0 | ||
|
|
b21bdacb4f | ||
|
|
92019b5ba3 | ||
|
|
908d635063 | ||
|
|
20329feb7f | ||
|
|
4cd92a1cd9 | ||
|
|
8ca3397c5d | ||
|
|
05cd79481e | ||
|
|
c0f1905785 | ||
|
|
9afc06028d | ||
|
|
7b6cc50238 | ||
|
|
722b1d0258 | ||
|
|
93d3b78ac1 | ||
|
|
69e82f6e0e | ||
|
|
1f83a6db7b | ||
|
|
8f37951e14 | ||
|
|
e1f4d43926 | ||
|
|
eb6139276f | ||
|
|
f18c426151 | ||
|
|
e46debb6d1 | ||
|
|
d8b8739cb8 | ||
|
|
9fd8eefa59 | ||
|
|
fd2af06a53 | ||
|
|
48f945ba7f | ||
|
|
6d59f88a76 | ||
|
|
8b94d4626d | ||
|
|
d7d8e78e42 | ||
|
|
9755f86f53 | ||
|
|
599a3ee82f | ||
|
|
c790346490 | ||
|
|
68cf3a9111 | ||
|
|
59221dfcf2 | ||
|
|
020413a206 | ||
|
|
a343e9ebdf | ||
|
|
c304efee36 | ||
|
|
95173f676d | ||
|
|
87d1db1f2b | ||
|
|
d445f384c7 | ||
|
|
59dd4b0721 | ||
|
|
d73c91d4a7 | ||
|
|
665a66522e | ||
|
|
ba1efa65fa | ||
|
|
64a74692b9 | ||
|
|
46cd285dd0 | ||
|
|
bcac115c3d | ||
|
|
77ddb2c8c2 | ||
|
|
8ae31eb998 | ||
|
|
7551a6ae1d | ||
|
|
93be659b1b | ||
|
|
3327878fc2 | ||
|
|
0b25c7f3c1 | ||
|
|
b606ba4dd7 | ||
|
|
0269bc810c | ||
|
|
f2775f2c1d | ||
|
|
f06274aec6 | ||
|
|
dfa686a3e0 | ||
|
|
fe679b5de5 | ||
|
|
aa1eb0410c | ||
|
|
1b06bab7ee | ||
|
|
0f13056aa2 | ||
|
|
beecf60db7 | ||
|
|
160a2013bc | ||
|
|
b8c636e87e | ||
|
|
add40e5926 | ||
|
|
960e7f3b24 | ||
|
|
3e749f36e8 | ||
|
|
8198d99309 | ||
|
|
81de1c8ed4 | ||
|
|
3eb55e9d9b | ||
|
|
6b6ac86aea | ||
|
|
1b45e70cbb | ||
|
|
929f7ec235 | ||
|
|
cf28d7e470 | ||
|
|
b0a00108f3 | ||
|
|
01151fc7f8 | ||
|
|
bf8dbc38c7 | ||
|
|
ae0d4d9353 | ||
|
|
43ec4ae238 | ||
|
|
c568a8cabe | ||
|
|
423bdd8b6b | ||
|
|
1e19ff65e6 | ||
|
|
a9cf632f53 | ||
|
|
fddf94a341 | ||
|
|
203168d261 | ||
|
|
0e3c3536f8 | ||
|
|
45b2b7e24f | ||
|
|
88f177b065 | ||
|
|
ea7a8dd3ad | ||
|
|
dda8f92494 | ||
|
|
26211802cd | ||
|
|
b4bef0d32c | ||
|
|
a8bf203067 | ||
|
|
624dd2e99d | ||
|
|
26a136a6e8 | ||
|
|
2d57ffa546 | ||
|
|
428b81a6d9 | ||
|
|
f24c12fdfb | ||
|
|
9a345d191b | ||
|
|
fec706d134 | ||
|
|
c338448997 | ||
|
|
956c1cb1a7 | ||
|
|
8831b4da9e | ||
|
|
f8bd60dcd7 | ||
|
|
6a373b585c | ||
|
|
54480e2510 | ||
|
|
83f73603db | ||
|
|
2b10f192ba | ||
|
|
775d5289cb | ||
|
|
e6c66352a7 | ||
|
|
77afd73ee1 | ||
|
|
5ac96a40aa | ||
|
|
2fea0e2598 | ||
|
|
2b64949cbe | ||
|
|
918d57f25e | ||
|
|
5e2b40d3a9 | ||
|
|
979ef4bd43 | ||
|
|
914baf594b | ||
|
|
02b0802886 | ||
|
|
0725239397 | ||
|
|
d72e8a06a7 | ||
|
|
cf79fec720 | ||
|
|
31dda45d1c | ||
|
|
9836b72661 | ||
|
|
6ede0194c6 | ||
|
|
5ec38581ca | ||
|
|
2629e59ace | ||
|
|
f3eb770e20 | ||
|
|
0683b79fac | ||
|
|
fcd09e2bae | ||
|
|
b25cb0f2d5 | ||
|
|
0704aec01b | ||
|
|
d38b939c63 | ||
|
|
112466de1e | ||
|
|
1d0edd2ad0 | ||
|
|
37736119be | ||
|
|
c5df150acb | ||
|
|
49a346334b | ||
|
|
e7c4656e8f | ||
|
|
85bed0582a | ||
|
|
b9e6f867c6 | ||
|
|
a5b80168bd | ||
|
|
3cea5fb2d0 | ||
|
|
c89d782048 | ||
|
|
1fd4179e07 | ||
|
|
3b62400298 | ||
|
|
ba0fe938a1 | ||
|
|
d4560171a8 | ||
|
|
9963ba6cf5 | ||
|
|
f5a23826c8 | ||
|
|
21ff005c1a | ||
|
|
206b9135f1 | ||
|
|
5449de1a67 | ||
|
|
47c61756e6 | ||
|
|
ef63679ff0 | ||
|
|
ef7e1c385a | ||
|
|
3a0a6cdbbb | ||
|
|
340df3e956 | ||
|
|
eeb264da8e | ||
|
|
00f08b8ec6 | ||
|
|
67ae2a39ba | ||
|
|
72dda25835 | ||
|
|
8c9ce1994a | ||
|
|
606b05fdaf | ||
|
|
420ba9549f | ||
|
|
51fbb5cfac | ||
|
|
ca2c2b60f2 | ||
|
|
d6064636d4 | ||
|
|
9646c9b0a0 | ||
|
|
f28900f8ca | ||
|
|
e0c15c42d7 | ||
|
|
7177618c25 | ||
|
|
3bdd4af75c | ||
|
|
98d4b5e487 | ||
|
|
8b5639bfdb | ||
|
|
1c70b8b1bf | ||
|
|
b5a7e03879 | ||
|
|
a6dade846e | ||
|
|
32913f9d95 | ||
|
|
cbabf9a2a3 | ||
|
|
03e92c3336 | ||
|
|
997c39fdc0 | ||
|
|
bba2d1ee18 | ||
|
|
a60301810f | ||
|
|
6b261f76b1 | ||
|
|
3db5f21d90 | ||
|
|
121bfcb984 | ||
|
|
265de66946 | ||
|
|
79c5c9f95e | ||
|
|
3354a47e8a | ||
|
|
a1e1416d7a | ||
|
|
24b7a9907f | ||
|
|
214abd0cd4 | ||
|
|
0bb53e8ca5 | ||
|
|
ec12caaeba | ||
|
|
5bbf2aa57a | ||
|
|
84ad9997da | ||
|
|
9f6ce87443 | ||
|
|
1ff6c382bf | ||
|
|
c366c10ae1 | ||
|
|
950df1e25c | ||
|
|
909bfa8c14 | ||
|
|
4c7b7f236a | ||
|
|
896c5b41cb | ||
|
|
facef1d23c | ||
|
|
cdb446375c | ||
|
|
6f7b2887aa | ||
|
|
bc8ae063dd | ||
|
|
003dc39d76 | ||
|
|
0b04c28011 | ||
|
|
31854ad9e8 | ||
|
|
4304e92f0d | ||
|
|
44736cefbf | ||
|
|
a807449f28 | ||
|
|
ae9d38b59c | ||
|
|
05e50c1b98 | ||
|
|
e11004da7b | ||
|
|
97ee7b5d96 | ||
|
|
6c6e2573aa | ||
|
|
8992827f21 | ||
|
|
893b9c1b38 | ||
|
|
6f8166ca0f | ||
|
|
64db035d77 | ||
|
|
cb051e4254 | ||
|
|
20e9c2901d | ||
|
|
ce4b391495 | ||
|
|
7d932f5b18 | ||
|
|
56c8c08e08 | ||
|
|
13ef25c0b5 | ||
|
|
6abfa5bf80 | ||
|
|
20ae8e4f8b | ||
|
|
f595c5d504 | ||
|
|
972779253c | ||
|
|
9179a241e9 | ||
|
|
85e7e89ad9 | ||
|
|
9f5bc00c89 | ||
|
|
0ab842e3c5 | ||
|
|
3a0a3e49dc | ||
|
|
438b1c043e | ||
|
|
6d13b65e7c | ||
|
|
423d41ee0e | ||
|
|
1b61d9bc51 | ||
|
|
bf0c3d42db | ||
|
|
5394a68314 | ||
|
|
b0063399bb | ||
|
|
724c6b7656 | ||
|
|
0530cbcd0f | ||
|
|
6e2bba1513 | ||
|
|
89ac5d7c42 | ||
|
|
dd68722b66 | ||
|
|
933d5db2ab | ||
|
|
0386ed6409 | ||
|
|
d3c14bf416 | ||
|
|
2a224ce9fb | ||
|
|
78322525b7 | ||
|
|
5b7c9c205e | ||
|
|
07ac70a460 | ||
|
|
3629f806a2 | ||
|
|
72fc43c738 | ||
|
|
df38f0be3f | ||
|
|
808684c2a8 | ||
|
|
69ed3a7751 | ||
|
|
68556caa9a | ||
|
|
bb8ea8053b | ||
|
|
6f01e6edf1 | ||
|
|
66a74d16ff | ||
|
|
0e525f5aeb | ||
|
|
86007c466d | ||
|
|
7b39b79183 | ||
|
|
7f453aa6f6 | ||
|
|
f36052ffeb | ||
|
|
d35db11f43 | ||
|
|
173e5d3f97 | ||
|
|
bcebf737c3 | ||
|
|
5280b72b85 | ||
|
|
a4dfe7138e | ||
|
|
b9960bad1a | ||
|
|
e1d7805396 | ||
|
|
ce6f993f0e | ||
|
|
aa1d94e6c9 | ||
|
|
00a6092836 | ||
|
|
f6372faa23 | ||
|
|
1a5e266d26 | ||
|
|
2e4a107201 | ||
|
|
d4688db31c | ||
|
|
c49a289619 | ||
|
|
2559496ded | ||
|
|
d3aa82fc5d | ||
|
|
704605918d | ||
|
|
7e8116888b | ||
|
|
e0f4bbd20d | ||
|
|
5ae2d5a24b | ||
|
|
8648737a7a | ||
|
|
6e090d5112 | ||
|
|
2207e561f2 | ||
|
|
b9cd5b572b | ||
|
|
344c6f3ee7 | ||
|
|
f6f2c0ed56 | ||
|
|
ec7a66a966 | ||
|
|
23ca428a01 | ||
|
|
eb9f251e34 | ||
|
|
394d875eb4 | ||
|
|
4cc6403867 | ||
|
|
9d5fa773f3 | ||
|
|
075ca49a1f | ||
|
|
9564f1d871 | ||
|
|
cf546a47b6 | ||
|
|
d3a4f35170 | ||
|
|
f450aea449 | ||
|
|
aed308b259 | ||
|
|
714dffc943 | ||
|
|
f8a173efbd | ||
|
|
70e7822dd1 | ||
|
|
452631550a | ||
|
|
a14266b452 | ||
|
|
ceb19c7573 | ||
|
|
6b605804d2 | ||
|
|
7336d327b3 | ||
|
|
c9c5225a6a | ||
|
|
e1060bf537 | ||
|
|
392e42a55d | ||
|
|
b974e41148 | ||
|
|
aa3e2a0b64 | ||
|
|
3df89dd9a3 | ||
|
|
6ef751422a | ||
|
|
05d49ee45e | ||
|
|
3e4783c454 | ||
|
|
ce93201843 | ||
|
|
f9fc3a46b5 | ||
|
|
0467d6666a | ||
|
|
1f26b076a3 | ||
|
|
7944f21925 | ||
|
|
e91eda8eca | ||
|
|
d8ac84a5da | ||
|
|
3098a02b72 |
@@ -20,3 +20,4 @@
|
||||
^revdep$
|
||||
^TODO-promises.md$
|
||||
^manualtests$
|
||||
^\.github$
|
||||
|
||||
8
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
8
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
@@ -2,13 +2,15 @@ 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](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.
|
||||
2. Submit a [pull request](https://help.github.com/articles/using-pull-requests).
|
||||
|
||||
3. Submit a [pull request](https://help.github.com/articles/using-pull-requests).
|
||||
3. Ensure that you have signed the contributor license agreement. It will appear as a "Check"
|
||||
on your PR and a comment from "CLAassistant" will also appear explaining whether you have
|
||||
yet to sign. After you sign, you can click the "Recheck" link in that comment and the check
|
||||
will flip to reflect that you've signed.
|
||||
|
||||
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:
|
||||
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name : Bug report
|
||||
about : Report a bug in Shiny.
|
||||
---
|
||||
|
||||
<!--
|
||||
This issue tracker is for bugs and feature requests in the Shiny package. If you're having trouble with Shiny Server or a related package, please file an issue in the appropriate repository.
|
||||
|
||||
If you're having trouble with shinyapps.io, and you have a paid account (Starter, Basic, Standard, or Pro), please file a support ticket via https://support.rstudio.com. If you have a Free account, please post to the RStudio Community with the shinyappsio tag: https://community.rstudio.com/tags/shinyappsio.
|
||||
|
||||
Finally, if you are an RStudio customer and are having trouble with one of our Pro products, get in touch with our support team at support@rstudio.com.
|
||||
|
||||
Before you file an issue, please upgrade to the latest version of Shiny from CRAN and confirm that the problem persists.
|
||||
|
||||
# First, restart R.
|
||||
# To install latest shiny from CRAN:
|
||||
install.packages("shiny")
|
||||
|
||||
See our guide to writing good bug reports for further guidance: https://github.com/rstudio/shiny/wiki/Writing-Good-Bug-Reports. The better your report is, the likelier we are to be able to reproduce and ultimately solve it.
|
||||
-->
|
||||
|
||||
### System details
|
||||
|
||||
Browser Version: <!-- If applicable -->
|
||||
|
||||
Output of `sessionInfo()`:
|
||||
|
||||
```
|
||||
# sessionInfo() output goes here
|
||||
```
|
||||
|
||||
### Example application *or* steps to reproduce the problem
|
||||
|
||||
<!-- If you're able to create one, a reproducible example is extremely helpful to us. For instructions on how to create one, please see: https://github.com/rstudio/shiny/wiki/Creating-a-Reproducible-Example -->
|
||||
|
||||
```R
|
||||
# Minimal, self-contained example app code goes here
|
||||
```
|
||||
|
||||
### Describe the problem in detail
|
||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name : Feature request
|
||||
about : Request a new feature.
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Thanks for taking the time to file a feature request! Please take the time to search for an existing feature request, to avoid creating duplicate requests. If you find an existing feature request, please give it a thumbs-up reaction, as we'll use these reactions to help prioritize the implementation of these features in the future.
|
||||
|
||||
If the feature has not yet been filed, then please describe the feature you'd like to see become a part of Shiny. See:
|
||||
|
||||
https://github.com/rstudio/shiny/wiki/Writing-Good-Feature-Requests
|
||||
|
||||
for a guide on how to write good feature requests.
|
||||
|
||||
-->
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
7
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
name : Ask a Question
|
||||
about : The issue tracker is not for questions -- please ask questions at https://community.rstudio.com/c/shiny.
|
||||
---
|
||||
|
||||
The issue tracker is not for questions. If you have a question, please feel free to ask it on our community site, at https://community.rstudio.com/c/shiny.
|
||||
|
||||
198
.github/workflows/R-CMD-check.yaml
vendored
Normal file
198
.github/workflows/R-CMD-check.yaml
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
name: R-CMD-check
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
R-CMD-check:
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
|
||||
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {os: macOS-latest, r: 'devel'}
|
||||
- {os: macOS-latest, r: '4.0'}
|
||||
- {os: windows-latest, r: '4.0'}
|
||||
- {os: ubuntu-16.04, r: '4.0', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
|
||||
- {os: ubuntu-16.04, r: '3.6', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
|
||||
- {os: ubuntu-16.04, r: '3.5', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
|
||||
- {os: ubuntu-16.04, r: '3.4', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
|
||||
- {os: ubuntu-16.04, r: '3.3', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
|
||||
|
||||
env:
|
||||
_R_CHECK_FORCE_SUGGESTS_: false
|
||||
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
|
||||
RSPM: ${{ matrix.config.rspm }}
|
||||
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/checkout/issues/135
|
||||
- name: Set git to use LF
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
git config --system core.autocrlf false
|
||||
git config --system core.eol lf
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: r-lib/actions/setup-r@master
|
||||
with:
|
||||
r-version: ${{ matrix.config.r }}
|
||||
|
||||
- uses: r-lib/actions/setup-pandoc@master
|
||||
|
||||
- name: Query dependencies
|
||||
run: |
|
||||
install.packages('remotes')
|
||||
saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2)
|
||||
shell: Rscript {0}
|
||||
|
||||
- name: Cache R packages
|
||||
if: runner.os != 'Windows'
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ env.R_LIBS_USER }}
|
||||
key: ${{ matrix.config.os }}-r-${{ matrix.config.r }}-1-${{ hashFiles('.github/depends.Rds') }}
|
||||
restore-keys: ${{ matrix.config.os }}-r-${{ matrix.config.r }}-1-
|
||||
|
||||
- name: Install system dependencies
|
||||
if: runner.os == 'Linux'
|
||||
env:
|
||||
RHUB_PLATFORM: linux-x86_64-ubuntu-gcc
|
||||
run: |
|
||||
Rscript -e "remotes::install_github('r-hub/sysreqs')"
|
||||
sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))")
|
||||
sudo -s eval "$sysreqs"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
remotes::install_deps(dependencies = TRUE)
|
||||
remotes::install_cran("rcmdcheck")
|
||||
shell: Rscript {0}
|
||||
|
||||
- name: Find PhantomJS path
|
||||
id: phantomjs
|
||||
run: |
|
||||
echo "::set-output name=path::$(Rscript -e 'cat(shinytest:::phantom_paths()[[1]])')"
|
||||
- name: Cache PhantomJS
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.phantomjs.outputs.path }}
|
||||
key: ${{ runner.os }}-phantomjs
|
||||
restore-keys: ${{ runner.os }}-phantomjs
|
||||
- name: Install PhantomJS
|
||||
run: >
|
||||
Rscript
|
||||
-e "if (!shinytest::dependenciesInstalled()) shinytest::installDependencies()"
|
||||
|
||||
- name: Session info
|
||||
run: |
|
||||
options(width = 100)
|
||||
pkgs <- installed.packages()[, "Package"]
|
||||
sessioninfo::session_info(pkgs, include_base = TRUE)
|
||||
shell: Rscript {0}
|
||||
|
||||
- name: Check
|
||||
env:
|
||||
_R_CHECK_CRAN_INCOMING_: false
|
||||
run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check")
|
||||
shell: Rscript {0}
|
||||
|
||||
- name: Show testthat output
|
||||
if: always()
|
||||
run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true
|
||||
shell: bash
|
||||
|
||||
- name: Upload check results
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ runner.os }}-r${{ matrix.config.r }}-results
|
||||
path: check
|
||||
|
||||
|
||||
documentation:
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
name: documentation
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {os: macOS-latest, r: '4.0'}
|
||||
|
||||
env:
|
||||
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
|
||||
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: r-lib/actions/setup-r@master
|
||||
with:
|
||||
r-version: ${{ matrix.config.r }}
|
||||
|
||||
- name: Query dependencies
|
||||
run: |
|
||||
install.packages('remotes')
|
||||
saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2)
|
||||
shell: Rscript {0}
|
||||
- name: Cache R packages
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ env.R_LIBS_USER }}
|
||||
key: ${{ matrix.config.os }}-r-${{ matrix.config.r }}-2-${{ hashFiles('.github/depends.Rds') }}
|
||||
restore-keys: ${{ matrix.config.os }}-r-${{ matrix.config.r }}-2-
|
||||
- name: Remove dependencies file
|
||||
run: |
|
||||
rm .github/depends.Rds
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
install.packages(c("remotes"))
|
||||
remotes::install_deps(dependencies = TRUE)
|
||||
remotes::install_cran("devtools")
|
||||
remotes::install_cran("rprojroot")
|
||||
shell: Rscript {0}
|
||||
|
||||
- name: Check documentation
|
||||
run: |
|
||||
./tools/documentation/checkDocsCurrent.sh
|
||||
|
||||
|
||||
node_js:
|
||||
runs-on: macOS-latest
|
||||
name: node_js
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.x'
|
||||
|
||||
# https://github.com/actions/cache/blame/ccf96194800dbb7b7094edcd5a7cf3ec3c270f10/examples.md#L185-L200
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: yarn cache
|
||||
uses: actions/cache@v1
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Check node build
|
||||
run: |
|
||||
./tools/checkJSCurrent.sh
|
||||
35
.github/workflows/pr-commands.yaml
vendored
Normal file
35
.github/workflows/pr-commands.yaml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
name: Commands
|
||||
jobs:
|
||||
document:
|
||||
if: startsWith(github.event.comment.body, '/document')
|
||||
name: document
|
||||
runs-on: macOS-latest
|
||||
env:
|
||||
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: r-lib/actions/pr-fetch@master
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: r-lib/actions/setup-r@master
|
||||
- name: Install dependencies
|
||||
run: Rscript -e 'install.packages(c("remotes", "roxygen2"))' -e 'remotes::install_deps(dependencies = TRUE)'
|
||||
- name: Document
|
||||
run: Rscript -e 'roxygen2::roxygenise()'
|
||||
- name: commit
|
||||
run: |
|
||||
git add man/\* NAMESPACE
|
||||
git commit -m 'Document'
|
||||
- uses: r-lib/actions/pr-push@master
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# added so that the workflow doesn't fail.
|
||||
always_runner:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Always run
|
||||
run: echo "This job is used to prevent the workflow status from showing as failed when all other jobs are skipped"
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@
|
||||
shinyapps/
|
||||
README.html
|
||||
.*.Rnb.cached
|
||||
tools/yarn-error.log
|
||||
|
||||
12
.travis.yml
12
.travis.yml
@@ -1,12 +0,0 @@
|
||||
language: r
|
||||
r:
|
||||
- oldrel
|
||||
- release
|
||||
- devel
|
||||
sudo: false
|
||||
cache: packages
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: change
|
||||
on_failure: change
|
||||
88
DESCRIPTION
88
DESCRIPTION
@@ -1,13 +1,18 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 1.1.0.9000
|
||||
Version: 1.5.0.9004
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
|
||||
person("JJ", "Allaire", role = "aut", email = "jj@rstudio.com"),
|
||||
person("Carson", "Sievert", role = "aut", email = "carson@rstudio.com"),
|
||||
person("Barret", "Schloerke", role = "aut", email = "barret@rstudio.com"),
|
||||
person("Yihui", "Xie", role = "aut", email = "yihui@rstudio.com"),
|
||||
person("Jeff", "Allen", role = "aut", email = "jeff@rstudio.com"),
|
||||
person("Jonathan", "McPherson", role = "aut", email = "jonathan@rstudio.com"),
|
||||
person("Alan", "Dipert", role = "aut"),
|
||||
person("Barbara", "Borges", role = "aut"),
|
||||
person(family = "RStudio", role = "cph"),
|
||||
person(family = "jQuery Foundation", role = "cph",
|
||||
comment = "jQuery library and jQuery UI library"),
|
||||
@@ -23,10 +28,18 @@ Authors@R: c(
|
||||
comment = "Bootstrap library"),
|
||||
person(family = "Twitter, Inc", role = "cph",
|
||||
comment = "Bootstrap library"),
|
||||
person("Alexander", "Farkas", role = c("ctb", "cph"),
|
||||
comment = "html5shiv library"),
|
||||
person("Scott", "Jehl", role = c("ctb", "cph"),
|
||||
comment = "Respond.js library"),
|
||||
person("Prem Nawaz", "Khan", role = "ctb",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person("Victor", "Tsaran", role = "ctb",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person("Dennis", "Lembree", role = "ctb",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person("Srinivasu", "Chakravarthula", role = "ctb",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person("Cathy", "O'Connor", role = "ctb",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person(family = "PayPal, Inc", role = "cph",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person("Stefan", "Petre", role = c("ctb", "cph"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
person("Andrew", "Rowls", role = c("ctb", "cph"),
|
||||
@@ -35,10 +48,8 @@ Authors@R: c(
|
||||
comment = "Font-Awesome font"),
|
||||
person("Brian", "Reavis", role = c("ctb", "cph"),
|
||||
comment = "selectize.js library"),
|
||||
person("Kristopher Michael", "Kowal", role = c("ctb", "cph"),
|
||||
comment = "es5-shim library"),
|
||||
person(family = "es5-shim contributors", role = c("ctb", "cph"),
|
||||
comment = "es5-shim library"),
|
||||
person("Salmen", "Bejaoui", role = c("ctb", "cph"),
|
||||
comment = "selectize-plugin-a11y library"),
|
||||
person("Denis", "Ineshin", role = c("ctb", "cph"),
|
||||
comment = "ion.rangeSlider library"),
|
||||
person("Sami", "Samhuri", role = c("ctb", "cph"),
|
||||
@@ -65,50 +76,66 @@ Depends:
|
||||
Imports:
|
||||
utils,
|
||||
grDevices,
|
||||
httpuv (>= 1.4.3.9001),
|
||||
httpuv (>= 1.5.2),
|
||||
mime (>= 0.3),
|
||||
jsonlite (>= 0.9.16),
|
||||
xtable,
|
||||
digest,
|
||||
htmltools (>= 0.3.5),
|
||||
htmltools (>= 0.5.0.9001),
|
||||
R6 (>= 2.0),
|
||||
sourcetools,
|
||||
later (>= 0.7.2),
|
||||
promises (>= 1.0.1),
|
||||
later (>= 1.0.0),
|
||||
promises (>= 1.1.0),
|
||||
tools,
|
||||
crayon,
|
||||
rlang
|
||||
rlang (>= 0.4.0),
|
||||
fastmap (>= 1.0.0),
|
||||
withr,
|
||||
commonmark (>= 1.7),
|
||||
glue (>= 1.3.2),
|
||||
bootstraplib (>= 0.2.0.9001)
|
||||
Suggests:
|
||||
datasets,
|
||||
Cairo (>= 1.5-5),
|
||||
testthat,
|
||||
testthat (>= 2.1.1),
|
||||
knitr (>= 1.6),
|
||||
markdown,
|
||||
rmarkdown,
|
||||
ggplot2,
|
||||
magrittr
|
||||
reactlog (>= 1.0.0),
|
||||
magrittr,
|
||||
shinytest,
|
||||
yaml,
|
||||
future,
|
||||
dygraphs,
|
||||
ragg,
|
||||
showtext,
|
||||
sass
|
||||
Remotes:
|
||||
rstudio/htmltools,
|
||||
rstudio/sass,
|
||||
rstudio/bootstraplib
|
||||
URL: http://shiny.rstudio.com
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Remotes:
|
||||
tidyverse/ggplot2,
|
||||
rstudio/httpuv
|
||||
Collate:
|
||||
'app.R'
|
||||
'globals.R'
|
||||
'app-state.R'
|
||||
'app_template.R'
|
||||
'bookmark-state-local.R'
|
||||
'stack.R'
|
||||
'bookmark-state.R'
|
||||
'bootstrap-deprecated.R'
|
||||
'bootstrap-layout.R'
|
||||
'globals.R'
|
||||
'conditions.R'
|
||||
'map.R'
|
||||
'utils.R'
|
||||
'bootstrap.R'
|
||||
'cache-context.R'
|
||||
'cache-disk.R'
|
||||
'cache-memory.R'
|
||||
'cache-utils.R'
|
||||
'diagnose.R'
|
||||
'fileupload.R'
|
||||
'font-awesome.R'
|
||||
'graph.R'
|
||||
'reactives.R'
|
||||
'reactive-domains.R'
|
||||
@@ -137,29 +164,40 @@ Collate:
|
||||
'insert-tab.R'
|
||||
'insert-ui.R'
|
||||
'jqueryui.R'
|
||||
'knitr.R'
|
||||
'middleware-shiny.R'
|
||||
'middleware.R'
|
||||
'timer.R'
|
||||
'shiny.R'
|
||||
'mock-session.R'
|
||||
'modal.R'
|
||||
'modules.R'
|
||||
'notifications.R'
|
||||
'priorityqueue.R'
|
||||
'progress.R'
|
||||
'react.R'
|
||||
'reexports.R'
|
||||
'render-cached-plot.R'
|
||||
'render-plot.R'
|
||||
'render-table.R'
|
||||
'run-url.R'
|
||||
'runapp.R'
|
||||
'serializers.R'
|
||||
'server-input-handlers.R'
|
||||
'server-resource-paths.R'
|
||||
'server.R'
|
||||
'shiny-options.R'
|
||||
'shiny.R'
|
||||
'shinyapp.R'
|
||||
'shinyui.R'
|
||||
'shinywrappers.R'
|
||||
'showcase.R'
|
||||
'snapshot.R'
|
||||
'tar.R'
|
||||
'test-export.R'
|
||||
'timer.R'
|
||||
'test-server.R'
|
||||
'test.R'
|
||||
'update-input.R'
|
||||
RoxygenNote: 6.1.0
|
||||
'viewer.R'
|
||||
RoxygenNote: 7.1.1
|
||||
Encoding: UTF-8
|
||||
Roxygen: list(markdown = TRUE)
|
||||
|
||||
773
LICENSE
773
LICENSE
@@ -8,12 +8,11 @@ these components are included below):
|
||||
- jQuery, https://github.com/jquery/jquery
|
||||
- jQuery UI (some components), https://github.com/jquery/jquery-ui
|
||||
- Bootstrap, https://github.com/twbs/bootstrap
|
||||
- html5shiv, https://github.com/aFarkas/html5shiv
|
||||
- Respond.js, https://github.com/scottjehl/Respond
|
||||
- bootstrap-accessibility-plugin, https://github.com/paypal/bootstrap-accessibility-plugin
|
||||
- bootstrap-datepicker, https://github.com/eternicode/bootstrap-datepicker
|
||||
- Font Awesome, https://github.com/FortAwesome/Font-Awesome
|
||||
- selectize.js, https://github.com/selectize/selectize.js
|
||||
- es5-shim, https://github.com/es-shims/es5-shim
|
||||
- selectize-plugin-a11y, https://github.com/SLMNBJ/selectize-plugin-a11y
|
||||
- ion.rangeSlider, https://github.com/IonDen/ion.rangeSlider
|
||||
- strftime for Javascript, https://github.com/samsonjs/strftime
|
||||
- DataTables, https://github.com/DataTables/DataTables
|
||||
@@ -25,7 +24,7 @@ these components are included below):
|
||||
jQuery license and license for included components from jQuery UI
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright jQuery Foundation and other contributors, https://jquery.org/
|
||||
Copyright JS Foundation and other contributors, https://js.foundation/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
@@ -51,7 +50,7 @@ Bootstrap License
|
||||
----------------------------------------------------------------------
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011-2014 Twitter, Inc
|
||||
Copyright (c) 2011-2019 Twitter, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -72,399 +71,35 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
html5shiv License (MIT and GPL-2)
|
||||
bootstrap-accessibility-plugin (BSD-3-Clause License)
|
||||
----------------------------------------------------------------------
|
||||
Copyright (c) 2014 Alexander Farkas (aFarkas).
|
||||
|
||||
Licensed under MIT
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<Html5shiv, The HTML5 Shiv enables use of HTML5 sectioning elements in
|
||||
legacy Internet Explorer and provides basic HTML5 styling for Internet Explorer 6-9,
|
||||
Safari 4.x (and iPhone 3.x), and Firefox 3.x.>
|
||||
Copyright (C) 2014 Alexander Farkas (aFarkas)
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) 2014 Alexander Farkas (aFarkas)
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
|
||||
|
||||
Respond.js License
|
||||
----------------------------------------------------------------------
|
||||
Copyright (c) 2012 Scott Jehl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
Copyright (c) 2014, PayPal
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the PayPal nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
bootstrap-datepicker
|
||||
@@ -673,7 +308,7 @@ bootstrap-datepicker
|
||||
limitations under the License.
|
||||
|
||||
|
||||
Font-Awesome (CSS file is MIT licensed; font has SIL Open Font License 1.1)
|
||||
Font Awesome (CSS files are MIT licensed; fonts have SIL Open Font License 1.1, svgs have CC BY 4.0 License)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
@@ -795,6 +430,326 @@ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
extent this Public License may be interpreted as a contract, You are
|
||||
granted the Licensed Rights in consideration of Your acceptance of
|
||||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
selectize.js
|
||||
----------------------------------------------------------------------
|
||||
@@ -1002,30 +957,18 @@ selectize.js
|
||||
limitations under the License.
|
||||
|
||||
|
||||
es5-shim License
|
||||
selectize-plugin-a11y License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2009-2014 Kristopher Michael Kowal and contributors
|
||||
Copyright 2018-present Salmen Bejaoui
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
ion.rangeSlider License
|
||||
|
||||
76
NAMESPACE
76
NAMESPACE
@@ -1,15 +1,18 @@
|
||||
# Generated by roxygen2: do not edit by hand
|
||||
|
||||
S3method("$",mockclientdata)
|
||||
S3method("$",reactivevalues)
|
||||
S3method("$",session_proxy)
|
||||
S3method("$",shinyoutput)
|
||||
S3method("$<-",reactivevalues)
|
||||
S3method("$<-",session_proxy)
|
||||
S3method("$<-",shinyoutput)
|
||||
S3method("[",mockclientdata)
|
||||
S3method("[",reactivevalues)
|
||||
S3method("[",shinyoutput)
|
||||
S3method("[<-",reactivevalues)
|
||||
S3method("[<-",shinyoutput)
|
||||
S3method("[[",mockclientdata)
|
||||
S3method("[[",reactivevalues)
|
||||
S3method("[[",session_proxy)
|
||||
S3method("[[",shinyoutput)
|
||||
@@ -25,14 +28,17 @@ S3method(as.tags,shiny.render.function)
|
||||
S3method(format,reactiveExpr)
|
||||
S3method(format,reactiveVal)
|
||||
S3method(names,reactivevalues)
|
||||
S3method(print,key_missing)
|
||||
S3method(print,reactive)
|
||||
S3method(print,reactivevalues)
|
||||
S3method(print,shiny.appobj)
|
||||
S3method(print,shiny.render.function)
|
||||
S3method(print,shiny_runtests)
|
||||
S3method(str,reactivevalues)
|
||||
export("conditionStackTrace<-")
|
||||
export(..stacktraceoff..)
|
||||
export(..stacktraceon..)
|
||||
export(HTML)
|
||||
export(MockShinySession)
|
||||
export(NS)
|
||||
export(Progress)
|
||||
export(a)
|
||||
@@ -93,6 +99,7 @@ export(formatStackTrace)
|
||||
export(freezeReactiveVal)
|
||||
export(freezeReactiveValue)
|
||||
export(getCurrentOutputInfo)
|
||||
export(getCurrentTheme)
|
||||
export(getDefaultReactiveDomain)
|
||||
export(getQueryString)
|
||||
export(getShinyOption)
|
||||
@@ -110,6 +117,7 @@ export(hoverOpts)
|
||||
export(hr)
|
||||
export(htmlOutput)
|
||||
export(htmlTemplate)
|
||||
export(httpResponse)
|
||||
export(icon)
|
||||
export(imageOutput)
|
||||
export(img)
|
||||
@@ -133,19 +141,16 @@ 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)
|
||||
export(knit_print.shiny.tag.list)
|
||||
export(loadSupport)
|
||||
export(mainPanel)
|
||||
export(makeReactiveBinding)
|
||||
export(markRenderFunction)
|
||||
export(markdown)
|
||||
export(maskReactiveContext)
|
||||
export(memoryCache)
|
||||
export(modalButton)
|
||||
export(modalDialog)
|
||||
export(moduleServer)
|
||||
export(navbarMenu)
|
||||
export(navbarPage)
|
||||
export(navlistPanel)
|
||||
@@ -178,6 +183,7 @@ export(printError)
|
||||
export(printStackTrace)
|
||||
export(radioButtons)
|
||||
export(reactive)
|
||||
export(reactiveConsole)
|
||||
export(reactiveFileReader)
|
||||
export(reactivePlot)
|
||||
export(reactivePoll)
|
||||
@@ -189,10 +195,15 @@ export(reactiveUI)
|
||||
export(reactiveVal)
|
||||
export(reactiveValues)
|
||||
export(reactiveValuesToList)
|
||||
export(reactlog)
|
||||
export(reactlogReset)
|
||||
export(reactlogShow)
|
||||
export(registerInputHandler)
|
||||
export(registerThemeDependency)
|
||||
export(removeInputHandler)
|
||||
export(removeModal)
|
||||
export(removeNotification)
|
||||
export(removeResourcePath)
|
||||
export(removeTab)
|
||||
export(removeUI)
|
||||
export(renderCachedPlot)
|
||||
@@ -205,12 +216,14 @@ export(renderText)
|
||||
export(renderUI)
|
||||
export(repeatable)
|
||||
export(req)
|
||||
export(resourcePaths)
|
||||
export(restoreInput)
|
||||
export(runApp)
|
||||
export(runExample)
|
||||
export(runGadget)
|
||||
export(runGist)
|
||||
export(runGitHub)
|
||||
export(runTests)
|
||||
export(runUrl)
|
||||
export(safeError)
|
||||
export(selectInput)
|
||||
@@ -222,6 +235,7 @@ export(setSerializer)
|
||||
export(shinyApp)
|
||||
export(shinyAppDir)
|
||||
export(shinyAppFile)
|
||||
export(shinyAppTemplate)
|
||||
export(shinyOptions)
|
||||
export(shinyServer)
|
||||
export(shinyUI)
|
||||
@@ -245,15 +259,19 @@ export(strong)
|
||||
export(submitButton)
|
||||
export(suppressDependencies)
|
||||
export(tabPanel)
|
||||
export(tabPanelBody)
|
||||
export(tableOutput)
|
||||
export(tabsetPanel)
|
||||
export(tag)
|
||||
export(tagAppendAttributes)
|
||||
export(tagAppendChild)
|
||||
export(tagAppendChildren)
|
||||
export(tagGetAttribute)
|
||||
export(tagHasAttribute)
|
||||
export(tagList)
|
||||
export(tagSetChildren)
|
||||
export(tags)
|
||||
export(testServer)
|
||||
export(textAreaInput)
|
||||
export(textInput)
|
||||
export(textOutput)
|
||||
@@ -261,6 +279,7 @@ export(throttle)
|
||||
export(titlePanel)
|
||||
export(uiOutput)
|
||||
export(updateActionButton)
|
||||
export(updateActionLink)
|
||||
export(updateCheckboxGroupInput)
|
||||
export(updateCheckboxInput)
|
||||
export(updateDateInput)
|
||||
@@ -298,5 +317,48 @@ import(httpuv)
|
||||
import(methods)
|
||||
import(mime)
|
||||
import(xtable)
|
||||
importFrom(fastmap,fastmap)
|
||||
importFrom(fastmap,is.key_missing)
|
||||
importFrom(fastmap,key_missing)
|
||||
importFrom(grDevices,dev.cur)
|
||||
importFrom(grDevices,dev.set)
|
||||
importFrom(htmltools,HTML)
|
||||
importFrom(htmltools,a)
|
||||
importFrom(htmltools,br)
|
||||
importFrom(htmltools,code)
|
||||
importFrom(htmltools,div)
|
||||
importFrom(htmltools,em)
|
||||
importFrom(htmltools,h1)
|
||||
importFrom(htmltools,h2)
|
||||
importFrom(htmltools,h3)
|
||||
importFrom(htmltools,h4)
|
||||
importFrom(htmltools,h5)
|
||||
importFrom(htmltools,h6)
|
||||
importFrom(htmltools,hr)
|
||||
importFrom(htmltools,htmlTemplate)
|
||||
importFrom(htmltools,img)
|
||||
importFrom(htmltools,includeCSS)
|
||||
importFrom(htmltools,includeHTML)
|
||||
importFrom(htmltools,includeMarkdown)
|
||||
importFrom(htmltools,includeScript)
|
||||
importFrom(htmltools,includeText)
|
||||
importFrom(htmltools,is.singleton)
|
||||
importFrom(htmltools,p)
|
||||
importFrom(htmltools,pre)
|
||||
importFrom(htmltools,singleton)
|
||||
importFrom(htmltools,span)
|
||||
importFrom(htmltools,strong)
|
||||
importFrom(htmltools,suppressDependencies)
|
||||
importFrom(htmltools,tag)
|
||||
importFrom(htmltools,tagAppendAttributes)
|
||||
importFrom(htmltools,tagAppendChild)
|
||||
importFrom(htmltools,tagAppendChildren)
|
||||
importFrom(htmltools,tagGetAttribute)
|
||||
importFrom(htmltools,tagHasAttribute)
|
||||
importFrom(htmltools,tagList)
|
||||
importFrom(htmltools,tagSetChildren)
|
||||
importFrom(htmltools,tags)
|
||||
importFrom(htmltools,validateCssUnit)
|
||||
importFrom(htmltools,withTags)
|
||||
importFrom(promises,"%...!%")
|
||||
importFrom(promises,"%...>%")
|
||||
|
||||
642
NEWS.md
642
NEWS.md
@@ -1,25 +1,326 @@
|
||||
shiny 1.1.0.9000
|
||||
|
||||
shiny 1.5.0.9000
|
||||
================
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Closed #3074: Shiny no longer supports file uploads for Internet Explorer 8 or 9. (#3075)
|
||||
|
||||
* Subtle changes, and some soft-deprecations, have come to `freezeReactiveValue` and `freezeReactiveVal` (#3055). These functions have been fragile at best in previous releases (issues #1791, #2463, #2946). In this release, we've solved all the problems we know about with `freezeReactiveValue(input, "x")`, by 1) invalidating `input$x` and set it to `NULL` whenever we freeze, and 2) ensuring that, after a freeze, even if the effect of `renderUI` or `updateXXXInput` is to set `input$x` to the same value it already has, this will result in an invalidation (whereas by default, Shiny filters out such spurious assignments).
|
||||
|
||||
Similar problems may exist when using `freezeReactiveVal`, and when using `freezeReactiveValue` with non-`input` reactive values objects. But support for those was added mostly for symmetry with `freezeReactiveValue(input)`, and given the above issues, it's not clear to us how you could have used these successfully in the past, or why you would even want to. For this release, we're soft-deprecating both of those uses, but we're more than willing to un-deprecate if it turns out people are using these; if that includes you, please join the conversation at https://github.com/rstudio/shiny/issues/3063. In the meantime, you can squelch the deprecation messages for these functions specifically, by setting `options(shiny.deprecation.messages.freeze = FALSE)`.
|
||||
|
||||
### Accessibility
|
||||
|
||||
* Added [bootstrap accessibility plugin](https://github.com/paypal/bootstrap-accessibility-plugin) under the hood to improve accessibility of shiny apps for screen-reader and keyboard users: the enhancements include better navigations for alert, tooltip, popover, modal dialog, dropdown, tab Panel, collapse, and carousel elements. (#2911)
|
||||
|
||||
* Added appropriate labels to `icon()` element to provide screen-reader users with alternative descriptions for the `fontawesome` and `glyphicon`: `aria-label` is automatically applied based on the fontawesome name. For example, `icon("calendar")` will be announced as "calendar icon" to screen readers. "presentation" aria role has also been attached to `icon()` to remove redundant semantic info for screen readers. (#2917)
|
||||
|
||||
* Closed #2929: Fixed keyboard accessibility for file picker button: keyboard users can now tab to focus on `fileInput()` widget. (#2937)
|
||||
|
||||
* Fixed #2951: screen readers correctly announce labels and date formats for `dateInput()` and `dateRangeInput()` widgets. (#2978)
|
||||
|
||||
* Closed #2847: `selectInput()` is reasonably accessible for screen readers even when `selectize` option is set to TRUE. To improve `selectize.js` accessibility, we have added [selectize-plugin-a11y](https://github.com/SLMNBJ/selectize-plugin-a11y) by default. (#2993)
|
||||
|
||||
* Closed #612: Added `alt` argument to `renderPlot()` and `renderCachedPlot()` to specify descriptive texts for `plotOutput()` objects, which is essential for screen readers. By default, alt text is set to the static text, "Plot object," but even dynamic text can be made with reactive function. (#3006, thanks @trafficonese and @leonawicz for the original PR and discussion via #2494)
|
||||
|
||||
* Added semantic landmarks for `mainPanel()` and `sidebarPanel()` so that assistive technologies can recognize them as "main" and "complementary" region respectively. (#3009)
|
||||
|
||||
* Closed #2844: Added `lang` argument to ui `*Page()` functions (e.g., `fluidPage`, `bootstrapPage`) that specifies document-level language within the app for the accessibility of screen readers and search-engine parsers. By default, it is set to empty string which is commonly recognized as a browser's default locale. (#2920)
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* New `reactiveConsole()` makes it easier to interactively experiment with reactivity at the console (#2518).
|
||||
|
||||
* When UI is specified as a function (e.g. `ui <- function(req) { ... }`), the response can now be an HTTP response as returned from the (newly exported) `httpResponse()` function. (#2970)
|
||||
|
||||
* `selectInput` and `selectizeInput` now warn about performance implications when thousands of choices are used, and recommend [server-side selectize](https://shiny.rstudio.com/articles/selectize.html) be used instead. (#2959)
|
||||
|
||||
* Closed #2980: `addResourcePath()` now allows paths with a leading `.` (thanks to @ColinFay). (#2981)
|
||||
|
||||
* Closed #2972: `runExample()` now supports the `shiny.port` option (thanks to @ColinFay). (#2982)
|
||||
|
||||
* Closed #2692: `downloadButton()` icon can now be changed via the `icon` parameter (thanks to @ColinFay). (#3010)
|
||||
|
||||
* Closed #2984: improved documentation for `renderCachedPlot()` (thanks to @aalucaci). (#3016)
|
||||
|
||||
* `reactiveValuesToList()` will save its `reactlog` label as `reactiveValuesToList(<ID>)` vs `as.list(<ID>)` (#3017)
|
||||
|
||||
* Removed unused (and non-exported) `cacheContext` class.
|
||||
|
||||
* `testServer()` can accept a single server function as input (#2965).
|
||||
|
||||
* `shinyOptions()` now has session-level scoping, in addition to global and application-level scoping. (#3080)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed #2859: `renderPlot()` wasn't correctly setting `showtext::showtext_opts()`'s `dpi` setting with the correct resolution on high resolution displays; which means, if the font was rendered by showtext, font sizes would look smaller than they should on such displays. (#2941)
|
||||
|
||||
* Fixed #1942: Calling `runApp("app.R")` no longer ignores options passed into `shinyApp()`. This makes it possible for Shiny apps to specify what port/host should be used by default. (#2969)
|
||||
|
||||
* Fixed #3033: When a `DiskCache` was created with both `max_n` and `max_size`, too many items could get pruned when `prune()` was called. (#3034)
|
||||
|
||||
* Fixed #2936: `dateYMD` was giving a warning when passed a vector of dates from `dateInput` which was greater than length 1. The length check was removed because it was not needed. (#3061)
|
||||
|
||||
* Fixed #2266, #2688: `radioButtons` and `updateRadioButtons` now accept `character(0)` to indicate that none of the options should be selected (thanks to @ColinFay). (#3043)
|
||||
|
||||
* Fixed a bug that `textAreaInput()` doesn't work as expected for relative `width` (thanks to @shrektan). (#2049)
|
||||
|
||||
### Library updates
|
||||
|
||||
* Removed html5shiv and respond.js, which were used for IE 8 and IE 9 compatibility. (#2973)
|
||||
|
||||
* Removed es5-shim library, which was internally used within `selectInput()` for ECMAScript 5 compatibility. (#2993)
|
||||
|
||||
|
||||
shiny 1.5.0
|
||||
===========
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Fixed #2869: Until this release, `renderImage()` had a dangerous default of `deleteFile = TRUE`. (Sorry!) Going forward, calls to `renderImage()` will need an explicit `deleteFile` argument; for now, failing to provide one will result in a warning message, and the file will be deleted if it appears to be within the `tempdir()`. (#2881)
|
||||
|
||||
### New features
|
||||
|
||||
* The new `shinyAppTemplate()` function creates a new template Shiny application, where components are optional, such as helper files in an R/ subdirectory, a module, and various kinds of tests. (#2704)
|
||||
|
||||
* `runTests()` is a new function that behaves much like R CMD check. `runTests()` invokes all of the top-level R files in the tests/ directory inside an application, in that application's environment. (#2585)
|
||||
|
||||
* `testServer()` is a new function for testing reactive behavior inside server functions and modules. ([#2682](https://github.com/rstudio/shiny/pull/2682), [#2764](https://github.com/rstudio/shiny/pull/2764), [#2807](https://github.com/rstudio/shiny/pull/2807))
|
||||
|
||||
* The new `moduleServer` function provides a simpler interface for creating and using modules. (#2773)
|
||||
|
||||
* Resolved #2732: `markdown()` is a new function for writing Markdown with Github extensions directly in Shiny UIs. Markdown rendering is performed by the [commonmark](https://github.com/jeroen/commonmark) package. (#2737)
|
||||
|
||||
* The `getCurrentOutputInfo()` function can now return the background color (`bg`), foreground color (`fg`), `accent` (i.e., hyperlink) color, and `font` information of the output's HTML container. This information is reported by `plotOutput()`, `imageOutput()`, and any other output bindings containing a class of `.shiny-report-theme`. This feature allows developers to style an output's contents based on the container's CSS styling. (#2740)
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Support for selecting variables of a data frame with the output values to be used within tidy evaluation. Added functions: `varSelectInput`, `varSelectizeInput`, `updateVarSelectInput`, `updateVarSelectizeInput`. ([#2091](https://github.com/rstudio/shiny/pull/2091))
|
||||
* Fixed #2042, #2628: In a `dateInput` and `dateRangeInput`, disabled months and years are now a lighter gray, to make it easier to see that they are disabled. (#2690)
|
||||
|
||||
* Addressed [#2042](https://github.com/rstudio/shiny/issues/2042): dates outside of `min`/`max` date range are now a lighter shade of grey to highlight the allowed range. ([#2087](https://github.com/rstudio/shiny/pull/2087))
|
||||
* `getCurrentOutputInfo()` previously threw an error when called from outside of an output; now it returns `NULL`. (#2707 and #2858)
|
||||
|
||||
* Fixed [#1933](https://github.com/rstudio/shiny/issues/1933): extended server-side selectize to lists and optgroups. ([#2102](https://github.com/rstudio/shiny/pull/2102))
|
||||
* Added a label to observer that auto-reloads `R/` directory to avoid confusion when using `reactlog`. (#58)
|
||||
|
||||
* Fixed [#1935](https://github.com/rstudio/shiny/issues/1935): correctly returns plot coordinates when using outer margins. ([#2108](https://github.com/rstudio/shiny/pull/2108))
|
||||
* `getDefaultReactiveDomain()` can now be called inside a `session$onSessionEnded` callback and will return the calling `session` information. (#2757)
|
||||
|
||||
* Resolved [#2019](https://github.com/rstudio/shiny/issues/2019): `updateSliderInput` now changes the slider formatting if the input type changes. ([#2099](https://github.com/rstudio/shiny/pull/2099))
|
||||
* Added a `'function'` class to `reactive()` and `reactiveVal()` objects. (#2793)
|
||||
|
||||
* Added namespace support when freezing reactiveValue keys. [#2080](https://github.com/rstudio/shiny/pull/2080)
|
||||
* Added a new option (`type = "hidden"`) to `tabsetPanel()`, making it easier to set the active tab via other input controls (e.g., `radioButtons()`) rather than tabs or pills. Use this option in conjunction with `updateTabsetPanel()` and the new `tabsetPanelBody()` function (see `help(tabsetPanel)` for an example and more details). (#2814)
|
||||
|
||||
* Added function `updateActionLink()` to update an `actionLink()` label and/or icon value. (#2811)
|
||||
|
||||
* Fixed #2856: Bumped jQuery 3 from 3.4.1 to 3.5.1. (#2857)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed #2606: `debounce()` would not work properly if the code in the reactive expression threw an error on the first run. (#2652)
|
||||
|
||||
* Fixed #2653: The `dataTableOutput()` could have incorrect output if certain characters were in the column names. (#2658)
|
||||
|
||||
### Documentation Updates
|
||||
|
||||
* Addressed [#1864](https://github.com/rstudio/shiny/issues/1864) by changing `optgroup` documentation to use `list` instead of `c`. ([#2084](https://github.com/rstudio/shiny/pull/2084))
|
||||
### Library updates
|
||||
|
||||
* Updated from Font-Awesome 5.3.1 to 5.13.0, which includes icons related to COVID-19. For upgrade notes, see https://github.com/FortAwesome/Font-Awesome/blob/master/UPGRADING.md. (#2891)
|
||||
|
||||
|
||||
shiny 1.4.0.2
|
||||
===========
|
||||
|
||||
Minor patch release: fixed some timing-dependent tests failed intermittently on CRAN build machines.
|
||||
|
||||
|
||||
shiny 1.4.0.1
|
||||
===========
|
||||
|
||||
Minor patch release to account for changes to the grid package that will be upcoming in the R 4.0 release (#2776).
|
||||
|
||||
|
||||
shiny 1.4.0
|
||||
===========
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Resolved #2554: Upgraded jQuery from v.1.12.4 to v3.4.1 and bootstrap from v3.3.7 to v3.4.1. (#2557). Since the jQuery upgrade may introduce breaking changes to user code, there is an option to switch back to the old version by setting `options(shiny.jquery.version = 1)`. If you've hard-coded `shared/jquery[.min].js` in the HTML of your Shiny app, in order to downgrade, you'll have to change that filepath to `shared/legacy/jquery[.min].js`.
|
||||
|
||||
### Improvements
|
||||
|
||||
* Resolved #1433: `plotOutput()`'s coordmap info now includes discrete axis limits for **ggplot2** plots. As a result, any **shinytest** tests that contain **ggplot2** plots with discrete axes (that were recorded before this change) will now report differences that can safely be updated. This new coordmap info was added to correctly infer what data points are within an input brush and/or near input click/hover in scenarios where a non-trivial discrete axis scale is involved (e.g., whenever `scale_[x/y]_discrete(limits = ...)` and/or free scales across multiple discrete axes are used). (#2410)
|
||||
|
||||
* Resolved #2402: An informative warning is now thrown for mis-specified (date) strings in `dateInput()`, `updateDateInput()`, `dateRangeInput()`, and `updateDateRangeInput()`. (#2403)
|
||||
|
||||
* If the `shiny.autoload.r` option is set to `TRUE`, all files ending in `.r` or `.R` contained in a directory named `R/` adjacent to your application are sourced when your app is started. This will become the default Shiny behavior in a future release (#2547)
|
||||
|
||||
* Resolved #2442: The `shiny:inputchanged` JavaScript event now triggers on the related input element instead of `document`. Existing event listeners bound to `document` will still detect the event due to event bubbling. (#2446)
|
||||
|
||||
* Fixed #1393, #2223: For plots with any interactions enabled, the image is no longer draggable. (#2460)
|
||||
|
||||
* Resolved #2469: `renderText` now takes a `sep` argument that is passed to `cat`. (#2497)
|
||||
|
||||
* Added `resourcePaths()` and `removeResourcePaths()` functions. (#2459)
|
||||
|
||||
* Resolved #2433: An informative warning is now thrown if subdirectories of the app's `www/` directory are masked by other resource prefixes and/or the same resource prefix is mapped to different local file paths. (#2434)
|
||||
|
||||
* Resolved #2478: `cmd + shift + f3` and `ctrl + shift + f3` can now be used to add a reactlog mark. If reactlog keybindings are used and the reactlog is not enabled, an error page is displayed showing how to enable reactlog recordings. (#2560)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Partially resolved #2423: Reactivity in Shiny leaked some memory, because R can leak memory whenever a new symbols is interned, which happens whenever a new name/key is used in an environment. R now uses the fastmap package, which avoids this problem. (#2429)
|
||||
|
||||
* Fixed #2267: Fixed a memory leak with `invalidateLater`. (#2555)
|
||||
|
||||
* Fixed #1548: The `reactivePoll` function leaked an observer; that is the observer would continue to exist even if the `reactivePoll` object was no longer accessible. #2522
|
||||
|
||||
* Fixed #2116: Fixed an issue where dynamic tabs could not be added when on a hosted platform. (#2545)
|
||||
|
||||
* Resolved #2515: `selectInput()` and `selectizeInput()` now deal appropriately with named factors. Note that `updateSelectInput()` and `updateSelectizeInput()` **do not** yet handle factors; their behavior is unchanged. (#2524, #2540, #2625)
|
||||
|
||||
* Resolved #2471: Large file uploads to a Windows computer were slow. (#2579)
|
||||
|
||||
* Fixed #2387: Updating a `sliderInput()`'s type from numeric to date no longer changes the rate policy from debounced to immediate. More generally, updating an input binding with a new type should (no longer) incorrectly alter the input rate policy. (#2404)
|
||||
|
||||
* Fixed #868: If an input is initialized with a `NULL` label, it can now be updated with a string. Moreover, if an input label is initialized with a string, it can now be removed by updating with `label=character(0)` (similar to how `choices` and `selected` can be cleared in `updateSelectInput()`). (#2406)
|
||||
|
||||
* Fixed #2250: `updateSliderInput()` now works with un-specified (or zero-length) `min`, `max`, and `value`. (#2416)
|
||||
|
||||
* Fixed #2396: `selectInput("myID", ...)` resulting in an extra `myID-selectized` input (introduced in v1.2.0). (#2418)
|
||||
|
||||
* Fixed #2233: `verbatimTextOutput()` produced wrapped text on Safari, but the text should not be wrapped. (#2353)
|
||||
|
||||
* Fixed #2335: When `dateInput()`'s `value` was unspecified, and `max` and/or `min` was set to `Sys.Date()`, the value was not being set properly. (#2526)
|
||||
|
||||
* Fixed #2591: Providing malformed date-strings to `min` or `max` no longer results in JS errors for `dateInput()` and `dateRangeInput()`. (#2592)
|
||||
|
||||
* Fixed [rstudio/reactlog#36](https://github.com/rstudio/reactlog/issues/36): Changes to reactive values not displaying accurately in reactlog. (#2424)
|
||||
|
||||
* Fixed #2598: Showcase files don't appear with a wide window. (#2582)
|
||||
|
||||
* Fixed #2329, #1817: These bugs were reported as fixed in Shiny 1.3.0 but were not actually fixed because some JavaScript changes were accidentally not included in the release. The fix resolves issues that occur when `withProgressBar()` or bookmarking are combined with the [networkD3](https://christophergandrud.github.io/networkD3/) package's Sankey plot.
|
||||
|
||||
|
||||
shiny 1.3.2
|
||||
===========
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed #2385: Static CSS/JS resources in subapps in R Markdown documents did not render properly. (#2386)
|
||||
|
||||
* Fixed #2280: Shiny applications that used a www/index.html file did not serve up the index file. (#2382)
|
||||
|
||||
|
||||
shiny 1.3.1
|
||||
===========
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed a performance issue introduced in v1.3.0 when using large nested lists within Shiny. (#2377)
|
||||
|
||||
|
||||
shiny 1.3.0
|
||||
===========
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Breaking changes
|
||||
|
||||
### New features
|
||||
|
||||
* Revamped Shiny's [reactlog](https://github.com/rstudio/reactlog) viewer which debugs reactivity within a shiny application. This allows users to traverse the reactivity history of a shiny application, filter to the dependency tree of a selected reactive object, and search for matching reactive objects. See `?reactlogShow` for more details and how to enable this feature. (#2107)
|
||||
|
||||
* Shiny now serves static files on a background thread. This means that things like JavaScript and CSS assets can be served without blocking or being blocked by the main R thread, and should result in significantly better performance for heavily loaded servers. (#2280)
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* The `Shiny-Shared-Secret` security header is now checked using constant-time comparison to prevent timing attacks (thanks @dirkschumacher!). (#2319)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed #2245: `updateSelectizeInput()` did not update labels. (#2248)
|
||||
|
||||
* Fixed #2308: When restoring a bookmarked application, inputs with a leading `.` would not be restored. (#2311)
|
||||
|
||||
* Fixed #2305, #2322, #2351: When an input in dynamic UI is restored from bookmarks, it would keep getting set to the same value. (#2360)
|
||||
|
||||
* Fixed #2349, #2329, #1817: These were various bugs triggered by the presence of the [networkD3](https://christophergandrud.github.io/networkD3/) package's Sankey plot in an app. Impacted features included `dateRangeInput`, `withProgressBar`, and bookmarking (#2359)
|
||||
|
||||
### Documentation Updates
|
||||
|
||||
* Fixed #2247: `renderCachedPlot` now supports using promises for either `expr` or `cacheKeyExpr`. (Shiny v1.2.0 supported async `expr`, but only if `cacheKeyExpr` was async as well; now you can use any combination of sync/async for `expr` and `cacheKeyExpr`.) #2261
|
||||
|
||||
|
||||
shiny 1.2.0
|
||||
===========
|
||||
|
||||
This release features plot caching, an important new tool for improving performance and scalability. Using `renderCachedPlot` in place of `renderPlot` can greatly improve responsiveness for apps that show the same plot many times (for example, a dashboard or report where all users view the same data). Shiny gives you a fair amount of control in where the cache is stored and how cached plots are invalidated, so be sure to read [this article](http://shiny.rstudio.com/articles/plot-caching.html) to get the most out of this feature.
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The URL paths for FontAwesome CSS/JS/font assets have changed, due to our upgrade from FontAwesome 4 to 5. This shouldn't affect you unless you're using `www/index.html` to provide your UI and have hardcoded the old FontAwesome paths into your HTML. If that's you, consider switching to [HTML templates](https://shiny.rstudio.com/articles/templates.html), which give you the syntax of raw HTML while still taking advantage of Shiny's automatic management of web dependencies.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `renderCachedPlot()`, which stores plots in a cache so that they can be served up almost instantly. (#1997)
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Upgrade FontAwesome from 4.7.0 to 5.3.1 and made `icon` tags browsable, which means they will display in a web browser or RStudio viewer by default (#2186). Note that if your application or library depends on FontAwesome directly using custom CSS, you may need to make some or all of the changes recommended in [Upgrade from Version 4](https://fontawesome.com/how-to-use/on-the-web/setup/upgrading-from-version-4). Font Awesome icons can also now be used in static R Markdown documents.
|
||||
|
||||
* Address #174: Added `datesdisabled` and `daysofweekdisabled` as new parameters to `dateInput()`. This resolves #174 and exposes the underlying arguments of [Bootstrap Datepicker](http://bootstrap-datepicker.readthedocs.io/en/latest/options.html#datesdisabled). `datesdisabled` expects a character vector with values in `yyyy/mm/dd` format and `daysofweekdisabled` expects an integer vector with day interger ids (Sunday=0, Saturday=6). The default value for both is `NULL`, which leaves all days selectable. Thanks, @nathancday! (#2147)
|
||||
|
||||
* Support for selecting variables of a data frame with the output values to be used within tidy evaluation. Added functions: `varSelectInput`, `varSelectizeInput`, `updateVarSelectInput`, `updateVarSelectizeInput`. (#2091)
|
||||
|
||||
* Addressed #2042: dates outside of `min`/`max` date range are now a lighter shade of grey to highlight the allowed range. (#2087)
|
||||
|
||||
* Added support for plot interaction when the plot is scaled. (#2125)
|
||||
|
||||
* Fixed #1933: extended server-side selectize to lists and optgroups. (#2102)
|
||||
|
||||
* Added namespace support when freezing reactiveValue keys. #2080
|
||||
|
||||
* Upgrade selectize.js from 0.12.1 to 0.12.4 #2028
|
||||
|
||||
* Addressed #2079: Added `coords_img`, `coords_css`, and `img_css_ratio` fields containing x and y location information for plot brush, hover, and click events. #2183
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed #2033: RStudio Viewer window not closed on `shiny::stopApp()`. Thanks, @vnijs! #2047
|
||||
|
||||
* Fixed #1935: correctly returns plot coordinates when using outer margins. (#2108)
|
||||
|
||||
* Resolved #2019: `updateSliderInput` now changes the slider formatting if the input type changes. (#2099)
|
||||
|
||||
* Fixed #2138: Inputs that are part of a `renderUI` were no longer restoring correctly from bookmarked state. #2139
|
||||
|
||||
* The `knit_print` methods from htmltools are no longer imported into shiny and then exported. Also, shiny's `knit_print` methods are no longer exported. #2166
|
||||
|
||||
* Fixed #2093: Make sure bookmark scope directory does not exist before trying to create it. #2168
|
||||
|
||||
* Fixed #2177: The session name is now being recorded when exiting a context. Multiple sessions can now view their respective reactlogs. #2180
|
||||
|
||||
* Fixed #2162: `selectInput` was sending spurious duplicate values to the server when using backspace. Thanks, @sada1993! #2187
|
||||
|
||||
* Fixed #2142: Dropping files on `fileInput`s stopped working on recent releases of Firefox. Thanks @dmenne for reporting! #2203
|
||||
|
||||
* Fixed #2204: `updateDateInput` could set the wrong date on days where DST begins. (Thanks @GaGaMan1101!) #2212
|
||||
|
||||
* Fixed #2225: Input event queue can stall in apps that use async. #2226
|
||||
|
||||
* Fixed #2228: `reactiveTimer` fails when not owned by a session. Thanks, @P-Bettega! #2229
|
||||
|
||||
### Documentation Updates
|
||||
|
||||
* Addressed #1864 by changing `optgroup` documentation to use `list` instead of `c`. (#2084)
|
||||
|
||||
|
||||
shiny 1.1.0
|
||||
@@ -35,59 +336,60 @@ This is a significant release for Shiny, with a major new feature that was nearl
|
||||
|
||||
### New features
|
||||
|
||||
* Support for asynchronous operations! Built-in render functions that expected a certain kind of object to be yielded from their `expr`, now generally can handle a promise for that kind of object. Reactive expressions and observers are now promise-aware as well. ([#1932](https://github.com/rstudio/shiny/pull/1932))
|
||||
* Support for asynchronous operations! Built-in render functions that expected a certain kind of object to be yielded from their `expr`, now generally can handle a promise for that kind of object. Reactive expressions and observers are now promise-aware as well. (#1932)
|
||||
|
||||
* Introduced two changes to the (undocumented but widely used) JavaScript function `Shiny.onInputChange(name, value)`. First, we changed the function name to `Shiny.setInputValue` (but don't worry--the old function name will continue to work). Second, until now, all calls to `Shiny.onInputChange(inputId, value)` have been "deduplicated"; that is, anytime an input is set to the same value it already has, the set is ignored. With Shiny v1.1, you can now add an options object as the third parameter: `Shiny.setInputValue("name", value, {priority: "event"})`. When the priority option is set to `"event"`, Shiny will always send the value and trigger reactivity, whether it is a duplicate or not. This closes [#928](https://github.com/rstudio/shiny/issues/928), which was the most upvoted open issue by far! Thanks, @daattali. ([#2018](https://github.com/rstudio/shiny/pull/2018))
|
||||
* Introduced two changes to the (undocumented but widely used) JavaScript function `Shiny.onInputChange(name, value)`. First, we changed the function name to `Shiny.setInputValue` (but don't worry--the old function name will continue to work). Second, until now, all calls to `Shiny.onInputChange(inputId, value)` have been "deduplicated"; that is, anytime an input is set to the same value it already has, the set is ignored. With Shiny v1.1, you can now add an options object as the third parameter: `Shiny.setInputValue("name", value, {priority: "event"})`. When the priority option is set to `"event"`, Shiny will always send the value and trigger reactivity, whether it is a duplicate or not. This closes #928, which was the most upvoted open issue by far! Thanks, @daattali. (#2018)
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Addressed [#1978](https://github.com/rstudio/shiny/issues/1978): `shiny:value` is now triggered when duplicate output data is received from the server. (Thanks, @andrewsali! [#1999](https://github.com/rstudio/shiny/pull/1999))
|
||||
* Addressed #1978: `shiny:value` is now triggered when duplicate output data is received from the server. (Thanks, @andrewsali! #1999)
|
||||
|
||||
* If a shiny output contains a css class of `shiny-report-size`, its container height and width are now reported in `session$clientData`. So, for an output with an id with `"myID"`, the height/width can be accessed via `session$clientData[['output_myID_height']]`/`session$clientData[['output_myID_width']]`. Addresses [#1980](https://github.com/rstudio/shiny/issues/1980). (Thanks, @cpsievert! [#1981](https://github.com/rstudio/shiny/pull/1981))
|
||||
* If a shiny output contains a css class of `shiny-report-size`, its container height and width are now reported in `session$clientData`. So, for an output with an id with `"myID"`, the height/width can be accessed via `session$clientData[['output_myID_height']]`/`session$clientData[['output_myID_width']]`. Addresses #1980. (Thanks, @cpsievert! #1981)
|
||||
|
||||
* Added a new `autoclose = TRUE` parameter to `dateInput()` and `dateRangeInput()`. This closed [#1969](https://github.com/rstudio/shiny/issues/1969) which was a duplicate of much older issue, [#173](https://github.com/rstudio/shiny/issues/173). The default value is `TRUE` since that seems to be the common use case. However, this will cause existing apps with date inputs (that update to this version of Shiny) to have the datepicker be immediately closed once a date is selected. For most apps, this is actually desired behavior; if you wish to keep the datepicker open until the user clicks out of it use `autoclose = FALSE`. ([#1987](https://github.com/rstudio/shiny/pull/1987))
|
||||
* Added a new `autoclose = TRUE` parameter to `dateInput()` and `dateRangeInput()`. This closed #1969 which was a duplicate of much older issue, #173. The default value is `TRUE` since that seems to be the common use case. However, this will cause existing apps with date inputs (that update to this version of Shiny) to have the datepicker be immediately closed once a date is selected. For most apps, this is actually desired behavior; if you wish to keep the datepicker open until the user clicks out of it use `autoclose = FALSE`. (#1987)
|
||||
|
||||
* The version of Shiny is now accessible from Javascript, with `Shiny.version`. There is also a new function for comparing version strings, `Shiny.compareVersion()`. ([#1826](https://github.com/rstudio/shiny/pull/1826), [#1830](https://github.com/rstudio/shiny/pull/1830))
|
||||
* The version of Shiny is now accessible from Javascript, with `Shiny.version`. There is also a new function for comparing version strings, `Shiny.compareVersion()`. (#1826, #1830)
|
||||
|
||||
* Addressed [#1851](https://github.com/rstudio/shiny/issues/1851): Stack traces are now smaller in some places `do.call()` is used. ([#1856](https://github.com/rstudio/shiny/pull/1856))
|
||||
* Addressed #1851: Stack traces are now smaller in some places `do.call()` is used. (#1856)
|
||||
|
||||
* Stack traces have been improved, with more aggressive de-noising and support for deep stack traces (stitching together multiple stack traces that are conceptually part of the same async operation).
|
||||
|
||||
* Addressed [#1859](https://github.com/rstudio/shiny/issues/1859): Server-side selectize is now significantly faster. (Thanks to @dselivanov [#1861](https://github.com/rstudio/shiny/pull/1861))
|
||||
* Addressed #1859: Server-side selectize is now significantly faster. (Thanks to @dselivanov #1861)
|
||||
|
||||
* [#1989](https://github.com/rstudio/shiny/issues/1989): The server side of outputs can now be removed (e.g. `output$plot <- NULL`). This is not usually necessary but it does allow some objects to be garbage collected, which might matter if you are dynamically creating and destroying many outputs. (Thanks, @mmuurr! [#2011](https://github.com/rstudio/shiny/pull/2011))
|
||||
* #1989: The server side of outputs can now be removed (e.g. `output$plot <- NULL`). This is not usually necessary but it does allow some objects to be garbage collected, which might matter if you are dynamically creating and destroying many outputs. (Thanks, @mmuurr! #2011)
|
||||
|
||||
* Removed the (ridiculously outdated) "experimental feature" tag from the reference documentation for `renderUI`. ([#2036](https://github.com/rstudio/shiny/pull/2036))
|
||||
* Removed the (ridiculously outdated) "experimental feature" tag from the reference documentation for `renderUI`. (#2036)
|
||||
|
||||
* Addressed [#1907](https://github.com/rstudio/shiny/issues/1907): the `ignoreInit` argument was first added only to `observeEvent`. Later, we also added it to `eventReactive`, but forgot to update the documentation. Now done, thanks [@flo12392](https://github.com/flo12392)! ([#2036](https://github.com/rstudio/shiny/pull/2036))
|
||||
* Addressed #1907: the `ignoreInit` argument was first added only to `observeEvent`. Later, we also added it to `eventReactive`, but forgot to update the documentation. Now done, thanks [@flo12392](https://github.com/flo12392)! (#2036)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1006](https://github.com/rstudio/shiny/issues/1006): Slider inputs sometimes showed too many digits. ([#1956](https://github.com/rstudio/shiny/pull/1956))
|
||||
* Fixed #1006: Slider inputs sometimes showed too many digits. (#1956)
|
||||
|
||||
* Fixed [#1958](https://github.com/rstudio/shiny/issues/1958): Slider inputs previously displayed commas after a decimal point. ([#1960](https://github.com/rstudio/shiny/pull/1960))
|
||||
* Fixed #1958: Slider inputs previously displayed commas after a decimal point. (#1960)
|
||||
|
||||
* The internal `URLdecode()` function previously was a copy of `httpuv::decodeURIComponent()`, assigned at build time; now it invokes the httpuv function at run time.
|
||||
|
||||
* Fixed [#1840](https://github.com/rstudio/shiny/issues/1840): with the release of Shiny 1.0.5, we accidently changed the relative positioning of the icon and the title text in `navbarMenu`s and `tabPanel`s. This fix reverts this behavior back (i.e. the icon should be to the left of the text and/or the downward arrow in case of `navbarMenu`s). ([#1848](https://github.com/rstudio/shiny/pull/1848))
|
||||
* Fixed #1840: with the release of Shiny 1.0.5, we accidently changed the relative positioning of the icon and the title text in `navbarMenu`s and `tabPanel`s. This fix reverts this behavior back (i.e. the icon should be to the left of the text and/or the downward arrow in case of `navbarMenu`s). (#1848)
|
||||
|
||||
* Fixed [#1600](https://github.com/rstudio/shiny/issues/1600): URL-encoded bookmarking did not work with sliders that had dates or date-times. ([#1961](https://github.com/rstudio/shiny/pull/1961))
|
||||
* Fixed #1600: URL-encoded bookmarking did not work with sliders that had dates or date-times. (#1961)
|
||||
|
||||
* Fixed [#1962](https://github.com/rstudio/shiny/issues/1962): [File dragging and dropping](https://blog.rstudio.com/2017/08/15/shiny-1-0-4/) broke in the presence of jQuery version 3.0 as introduced by the [rhandsontable](https://jrowen.github.io/rhandsontable/) [htmlwidget](https://www.htmlwidgets.org/). ([#2005](https://github.com/rstudio/shiny/pull/2005))
|
||||
* Fixed #1962: [File dragging and dropping](https://blog.rstudio.com/2017/08/15/shiny-1-0-4/) broke in the presence of jQuery version 3.0 as introduced by the [rhandsontable](https://jrowen.github.io/rhandsontable/) [htmlwidget](https://www.htmlwidgets.org/). (#2005)
|
||||
|
||||
* Improved the error handling inside the `addResourcePath()` function, to give end users more informative error messages when the `directoryPath` argument cannot be normalized. This is especially useful for `runtime: shiny_prerendered` Rmd documents, like `learnr` tutorials. ([#1968](https://github.com/rstudio/shiny/pull/1968))
|
||||
* Improved the error handling inside the `addResourcePath()` function, to give end users more informative error messages when the `directoryPath` argument cannot be normalized. This is especially useful for `runtime: shiny_prerendered` Rmd documents, like `learnr` tutorials. (#1968)
|
||||
|
||||
* Changed script tags in reactlog ([inst/www/reactive-graph.html](https://github.com/rstudio/shiny/blob/master/inst/www/reactive-graph.html)) from HTTP to HTTPS in order to avoid mixed content blocking by most browsers. (Thanks, @jekriske-lilly! [#1844](https://github.com/rstudio/shiny/pull/1844))
|
||||
* Changed script tags in reactlog ([inst/www/reactive-graph.html](https://github.com/rstudio/shiny/blob/v1.1.0/inst/www/reactive-graph.html)) from HTTP to HTTPS in order to avoid mixed content blocking by most browsers. (Thanks, @jekriske-lilly! #1844)
|
||||
|
||||
* Addressed [#1784](https://github.com/rstudio/shiny/issues/1784): `runApp()` will avoid port 6697, which is considered unsafe by Chrome.
|
||||
* Addressed #1784: `runApp()` will avoid port 6697, which is considered unsafe by Chrome.
|
||||
|
||||
* Fixed [#2000](https://github.com/rstudio/shiny/issues/2000): Implicit calls to `xxxOutput` not working inside modules. (Thanks, @GregorDeCillia! [#2010](https://github.com/rstudio/shiny/pull/2010))
|
||||
* Fixed #2000: Implicit calls to `xxxOutput` not working inside modules. (Thanks, @GregorDeCillia! #2010)
|
||||
|
||||
* Fixed #2021: Memory leak with `reactiveTimer` and `invalidateLater`. (#2022)
|
||||
|
||||
* Fixed [#2021](https://github.com/rstudio/shiny/issues/2021): Memory leak with `reactiveTimer` and `invalidateLater`. ([#2022](https://github.com/rstudio/shiny/pull/2022))
|
||||
|
||||
### Library updates
|
||||
|
||||
* Updated to ion.rangeSlider 2.2.0. ([#1955](https://github.com/rstudio/shiny/pull/1955))
|
||||
* Updated to ion.rangeSlider 2.2.0. (#1955)
|
||||
|
||||
|
||||
## Known issues
|
||||
@@ -102,11 +404,11 @@ shiny 1.0.5
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1818](https://github.com/rstudio/shiny/issues/1818): `conditionalPanel()` expressions that have a newline character in them caused the application to not work. ([#1820](https://github.com/rstudio/shiny/pull/1820))
|
||||
* Fixed #1818: `conditionalPanel()` expressions that have a newline character in them caused the application to not work. (#1820)
|
||||
|
||||
* Added a safe wrapper function for internal calls to `jsonlite::fromJSON()`. ([#1822](https://github.com/rstudio/shiny/pull/1822))
|
||||
* Added a safe wrapper function for internal calls to `jsonlite::fromJSON()`. (#1822)
|
||||
|
||||
* Fixed [#1824](https://github.com/rstudio/shiny/issues/1824): HTTP HEAD requests on static files caused the application to stop. ([#1825](https://github.com/rstudio/shiny/pull/1825))
|
||||
* Fixed #1824: HTTP HEAD requests on static files caused the application to stop. (#1825)
|
||||
|
||||
|
||||
shiny 1.0.4
|
||||
@@ -118,57 +420,57 @@ There are three headlining features in this release of Shiny. It is now possible
|
||||
|
||||
### New features
|
||||
|
||||
* Implemented [#1668](https://github.com/rstudio/shiny/issues/1668): dynamic tabs: added functions (`insertTab`, `appendTab`, `prependTab`, `removeTab`, `showTab` and `hideTab`) that allow you to do those actions for an existing `tabsetPanel`. ([#1794](https://github.com/rstudio/shiny/pull/1794))
|
||||
* Implemented #1668: dynamic tabs: added functions (`insertTab`, `appendTab`, `prependTab`, `removeTab`, `showTab` and `hideTab`) that allow you to do those actions for an existing `tabsetPanel`. (#1794)
|
||||
|
||||
* Implemented [#1213](https://github.com/rstudio/shiny/issues/1213): Added a new function, `onStop()`, which can be used to register callback functions that are invoked when an application exits, or when a user session ends. (Multiple sessions can be connected to a single running Shiny application.) This is useful if you have finalization/clean-up code that should be run after the application exits. ([#1770](https://github.com/rstudio/shiny/pull/1770)
|
||||
* Implemented #1213: Added a new function, `onStop()`, which can be used to register callback functions that are invoked when an application exits, or when a user session ends. (Multiple sessions can be connected to a single running Shiny application.) This is useful if you have finalization/clean-up code that should be run after the application exits. (#1770
|
||||
|
||||
* Implemented [#1155](https://github.com/rstudio/shiny/issues/1155): Files can now be drag-and-dropped on `fileInput` controls. The appearance of `fileInput` controls while files are being dragged can be modified by overriding the `shiny-file-input-active` and `shiny-file-input-over` classes. ([#1782](https://github.com/rstudio/shiny/pull/1782))
|
||||
* Implemented #1155: Files can now be drag-and-dropped on `fileInput` controls. The appearance of `fileInput` controls while files are being dragged can be modified by overriding the `shiny-file-input-active` and `shiny-file-input-over` classes. (#1782)
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Addressed [#1688](https://github.com/rstudio/shiny/issues/1688): trigger a new `shiny:outputinvalidated` event when an output gets invalidated, at the same time that the `recalculating` CSS class is added. ([#1758](https://github.com/rstudio/shiny/pull/1758), thanks [@andrewsali](https://github.com/andrewsali)!)
|
||||
* Addressed #1688: trigger a new `shiny:outputinvalidated` event when an output gets invalidated, at the same time that the `recalculating` CSS class is added. (#1758, thanks [@andrewsali](https://github.com/andrewsali)!)
|
||||
|
||||
* Addressed [#1508](https://github.com/rstudio/shiny/issues/1508): `fileInput` now permits the same file to be uploaded multiple times. ([#1719](https://github.com/rstudio/shiny/pull/1719))
|
||||
* Addressed #1508: `fileInput` now permits the same file to be uploaded multiple times. (#1719)
|
||||
|
||||
* Addressed [#1501](https://github.com/rstudio/shiny/issues/1501): The `fileInput` control now retains uploaded file extensions on the server. This fixes [readxl](https://github.com/tidyverse/readxl)'s `readxl::read_excel` and other functions that must recognize a file's extension in order to work. ([#1706](https://github.com/rstudio/shiny/pull/1706))
|
||||
* Addressed #1501: The `fileInput` control now retains uploaded file extensions on the server. This fixes [readxl](https://github.com/tidyverse/readxl)'s `readxl::read_excel` and other functions that must recognize a file's extension in order to work. (#1706)
|
||||
|
||||
* For `conditionalPanel`s, Shiny now gives more informative messages if there are errors evaluating or parsing the JavaScript conditional expression. ([#1727](https://github.com/rstudio/shiny/pull/1727))
|
||||
* For `conditionalPanel`s, Shiny now gives more informative messages if there are errors evaluating or parsing the JavaScript conditional expression. (#1727)
|
||||
|
||||
* Addressed [#1586](https://github.com/rstudio/shiny/issues/1586): The `conditionalPanel` function now accepts an `ns` argument. The `ns` argument can be used in a [module](https://shiny.rstudio.com/articles/modules.html) UI function to scope the `condition` expression to the module's own input and output IDs. ([#1735](https://github.com/rstudio/shiny/pull/1735))
|
||||
* Addressed #1586: The `conditionalPanel` function now accepts an `ns` argument. The `ns` argument can be used in a [module](https://shiny.rstudio.com/articles/modules.html) UI function to scope the `condition` expression to the module's own input and output IDs. (#1735)
|
||||
|
||||
* With `options(shiny.testmode=TRUE)`, the Shiny process will send a message to the client in response to a changed input, even if no outputs have changed. This helps to streamline testing using the shinytest package. ([#1747](https://github.com/rstudio/shiny/pull/1747))
|
||||
* With `options(shiny.testmode=TRUE)`, the Shiny process will send a message to the client in response to a changed input, even if no outputs have changed. This helps to streamline testing using the shinytest package. (#1747)
|
||||
|
||||
* Addressed [#1738](https://github.com/rstudio/shiny/issues/1738): The `updateTextInput` and `updateTextAreaInput` functions can now update the placeholder. ([#1742](https://github.com/rstudio/shiny/pull/1742))
|
||||
* Addressed #1738: The `updateTextInput` and `updateTextAreaInput` functions can now update the placeholder. (#1742)
|
||||
|
||||
* Converted examples to single file apps, and made updates and enhancements to comments in the example app scripts. ([#1685](https://github.com/rstudio/shiny/pull/1685))
|
||||
* Converted examples to single file apps, and made updates and enhancements to comments in the example app scripts. (#1685)
|
||||
|
||||
* Added new `snapshotPreprocessInput()` and `snapshotPreprocessOutput()` functions, which is used for preprocessing and input and output values before taking a test snapshot. ([#1760](https://github.com/rstudio/shiny/pull/1760), [#1789](https://github.com/rstudio/shiny/pull/1789))
|
||||
* Added new `snapshotPreprocessInput()` and `snapshotPreprocessOutput()` functions, which is used for preprocessing and input and output values before taking a test snapshot. (#1760, #1789)
|
||||
|
||||
* The HTML generated by `renderTable()` no longer includes comments with the R version, xtable version, and timestamp. ([#1771](https://github.com/rstudio/shiny/pull/1771))
|
||||
* The HTML generated by `renderTable()` no longer includes comments with the R version, xtable version, and timestamp. (#1771)
|
||||
|
||||
* Added a function `isRunning` to test whether a Shiny app is currently running. ([#1785](https://github.com/rstudio/shiny/pull/1785))
|
||||
* Added a function `isRunning` to test whether a Shiny app is currently running. (#1785)
|
||||
|
||||
* Added a function `setSerializer`, which allows authors to specify a function for serializing the value of a custom input. ([#1791](https://github.com/rstudio/shiny/pull/1791))
|
||||
* Added a function `setSerializer`, which allows authors to specify a function for serializing the value of a custom input. (#1791)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1546](https://github.com/rstudio/shiny/issues/1546): make it possible (without any hacks) to write arbitrary data into a module's `session$userData` (which is exactly the same environment as the parent's `session$userData`). To be clear, it allows something like `session$userData$x <- TRUE`, but not something like `session$userData <- TRUE` (that is not allowed in any context, whether you're in the main app, or in a module) ([#1732](https://github.com/rstudio/shiny/pull/1732)).
|
||||
* Fixed #1546: make it possible (without any hacks) to write arbitrary data into a module's `session$userData` (which is exactly the same environment as the parent's `session$userData`). To be clear, it allows something like `session$userData$x <- TRUE`, but not something like `session$userData <- TRUE` (that is not allowed in any context, whether you're in the main app, or in a module) (#1732).
|
||||
|
||||
* Fixed [#1701](https://github.com/rstudio/shiny/issues/1701): There was a partial argument match in the `generateOptions` function. ([#1702](https://github.com/rstudio/shiny/pull/1702))
|
||||
* Fixed #1701: There was a partial argument match in the `generateOptions` function. (#1702)
|
||||
|
||||
* Fixed [#1710](https://github.com/rstudio/shiny/issues/1710): `ReactiveVal` objects did not have separate dependents. ([#1712](https://github.com/rstudio/shiny/pull/1712))
|
||||
* Fixed #1710: `ReactiveVal` objects did not have separate dependents. (#1712)
|
||||
|
||||
* Fixed [#1438](https://github.com/rstudio/shiny/issues/1438): `unbindAll()` should not be called when inserting content with `insertUI()`. A previous fix ([#1449](https://github.com/rstudio/shiny/pull/1449)) did not work correctly. ([#1736](https://github.com/rstudio/shiny/pull/1736))
|
||||
* Fixed #1438: `unbindAll()` should not be called when inserting content with `insertUI()`. A previous fix (#1449) did not work correctly. (#1736)
|
||||
|
||||
* Fixed [#1755](https://github.com/rstudio/shiny/issues/1755): dynamic htmlwidgets sent the path of the package on the server to the client. ([#1756](https://github.com/rstudio/shiny/pull/1756))
|
||||
* Fixed #1755: dynamic htmlwidgets sent the path of the package on the server to the client. (#1756)
|
||||
|
||||
* Fixed [#1763](https://github.com/rstudio/shiny/issues/1763): Shiny's private random stream leaked out into the main random stream. ([#1768](https://github.com/rstudio/shiny/pull/1768))
|
||||
* Fixed #1763: Shiny's private random stream leaked out into the main random stream. (#1768)
|
||||
|
||||
* Fixed [#1680](https://github.com/rstudio/shiny/issues/1680): `options(warn=2)` was not respected when running an app. ([#1790](https://github.com/rstudio/shiny/pull/1790))
|
||||
* Fixed #1680: `options(warn=2)` was not respected when running an app. (#1790)
|
||||
|
||||
* Fixed [#1772](https://github.com/rstudio/shiny/issues/1772): ensure that `runApp()` respects the `shinyApp(onStart = function())` argument. ([#1770](https://github.com/rstudio/shiny/pull/1770))
|
||||
* Fixed #1772: ensure that `runApp()` respects the `shinyApp(onStart = function())` argument. (#1770)
|
||||
|
||||
* Fixed [#1474](https://github.com/rstudio/shiny/issues/1474): A `browser()` call in an observer could cause an error in the RStudio IDE on Windows. ([#1802](https://github.com/rstudio/shiny/pull/1802))
|
||||
* Fixed #1474: A `browser()` call in an observer could cause an error in the RStudio IDE on Windows. (#1802)
|
||||
|
||||
|
||||
shiny 1.0.3
|
||||
@@ -180,9 +482,9 @@ This is a hotfix release of Shiny. With previous versions of Shiny, when running
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1672](https://github.com/rstudio/shiny/issues/1672): When an error occurred while uploading a file, the progress bar did not change colors. ([#1673](https://github.com/rstudio/shiny/pull/1673))
|
||||
* Fixed #1672: When an error occurred while uploading a file, the progress bar did not change colors. (#1673)
|
||||
|
||||
* Fixed [#1676](https://github.com/rstudio/shiny/issues/1676): On R 3.4.0, running a Shiny application gave a warning: `Warning in body(fun) : argument is not a function`. ([#1677](https://github.com/rstudio/shiny/pull/1677))
|
||||
* Fixed #1676: On R 3.4.0, running a Shiny application gave a warning: `Warning in body(fun) : argument is not a function`. (#1677)
|
||||
|
||||
|
||||
shiny 1.0.2
|
||||
@@ -194,15 +496,15 @@ This is a hotfix release of Shiny. The primary reason for this release is becaus
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Added a `shiny:sessioninitialized` Javascript event, which is fired at the end of the initialize method of the Session object. This allows us to listen for this event when we want to get the value of things like `Shiny.user`. ([#1568](https://github.com/rstudio/shiny/pull/1568))
|
||||
* Added a `shiny:sessioninitialized` Javascript event, which is fired at the end of the initialize method of the Session object. This allows us to listen for this event when we want to get the value of things like `Shiny.user`. (#1568)
|
||||
|
||||
* Fixed [#1649](https://github.com/rstudio/shiny/issues/1649): allow the `choices` argument in `checkboxGroupInput()` to be `NULL` (or `c()`) to keep backward compatibility with Shiny < 1.0.1. This will result in the same thing as providing `choices = character(0)`. ([#1652](https://github.com/rstudio/shiny/pull/1652))
|
||||
* Fixed #1649: allow the `choices` argument in `checkboxGroupInput()` to be `NULL` (or `c()`) to keep backward compatibility with Shiny < 1.0.1. This will result in the same thing as providing `choices = character(0)`. (#1652)
|
||||
|
||||
* The official URL for accessing MathJax libraries over CDN has been deprecated and will be removed soon. We have switched to a new rstudio.com URL that we will support going forward. ([#1664](https://github.com/rstudio/shiny/pull/1664))
|
||||
* The official URL for accessing MathJax libraries over CDN has been deprecated and will be removed soon. We have switched to a new rstudio.com URL that we will support going forward. (#1664)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1653](https://github.com/rstudio/shiny/issues/1653): wrong code example in documentation. ([#1658](https://github.com/rstudio/shiny/pull/1658))
|
||||
* Fixed #1653: wrong code example in documentation. (#1658)
|
||||
|
||||
|
||||
shiny 1.0.1
|
||||
@@ -218,62 +520,62 @@ This is a maintenance release of Shiny, mostly aimed at fixing bugs and introduc
|
||||
|
||||
### New features
|
||||
|
||||
* Added `reactiveVal` function, for storing a single value which can be (reactively) read and written. Similar to `reactiveValues`, except that `reactiveVal` just lets you store a single value instead of storing multiple values by name. ([#1614](https://github.com/rstudio/shiny/pull/1614))
|
||||
* Added `reactiveVal` function, for storing a single value which can be (reactively) read and written. Similar to `reactiveValues`, except that `reactiveVal` just lets you store a single value instead of storing multiple values by name. (#1614)
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Fixed [#1637](https://github.com/rstudio/shiny/issues/1637): Outputs stay faded on MS Edge. ([#1640](https://github.com/rstudio/shiny/pull/1640))
|
||||
* Fixed #1637: Outputs stay faded on MS Edge. (#1640)
|
||||
|
||||
* Addressed [#1348](https://github.com/rstudio/shiny/issues/1348) and [#1437](https://github.com/rstudio/shiny/issues/1437) by adding two new arguments to `radioButtons()` and `checkboxGroupInput()`: `choiceNames` (list or vector) and `choiceValues` (list or vector). These can be passed in as an alternative to `choices`, with the added benefit that the elements in `choiceNames` can be arbitrary UI (i.e. anything created by `HTML()` and the `tags()` functions, like icons and images). While the underlying values for each choice (passed in through `choiceValues`) must still be simple text, their visual representation on the app (what the user actually clicks to select a different option) can be any valid HTML element. See `?radioButtons` for a small example. ([#1521](https://github.com/rstudio/shiny/pull/1521))
|
||||
* Addressed #1348 and #1437 by adding two new arguments to `radioButtons()` and `checkboxGroupInput()`: `choiceNames` (list or vector) and `choiceValues` (list or vector). These can be passed in as an alternative to `choices`, with the added benefit that the elements in `choiceNames` can be arbitrary UI (i.e. anything created by `HTML()` and the `tags()` functions, like icons and images). While the underlying values for each choice (passed in through `choiceValues`) must still be simple text, their visual representation on the app (what the user actually clicks to select a different option) can be any valid HTML element. See `?radioButtons` for a small example. (#1521)
|
||||
|
||||
* Updated `tools/README.md` with more detailed instructions. ([#1616](https://github.com/rstudio/shiny/pull/1616))
|
||||
* Updated `tools/README.md` with more detailed instructions. (#1616)
|
||||
|
||||
* Fixed [#1565](https://github.com/rstudio/shiny/issues/1565), which meant that resources with spaces in their names return HTTP 404. ([#1566](https://github.com/rstudio/shiny/pull/1566))
|
||||
* Fixed #1565, which meant that resources with spaces in their names return HTTP 404. (#1566)
|
||||
|
||||
* Exported `session$user` (if it exists) to the client-side; it's accessible in the Shiny object: `Shiny.user`. ([#1563](https://github.com/rstudio/shiny/pull/1563))
|
||||
* Exported `session$user` (if it exists) to the client-side; it's accessible in the Shiny object: `Shiny.user`. (#1563)
|
||||
|
||||
* Added support for HTML5's `pushState` which allows for pseudo-navigation
|
||||
in shiny apps. For more info, see the documentation (`?updateQueryString` and `?getQueryString`). ([#1447](https://github.com/rstudio/shiny/pull/1447))
|
||||
in shiny apps. For more info, see the documentation (`?updateQueryString` and `?getQueryString`). (#1447)
|
||||
|
||||
* Fixed [#1121](https://github.com/rstudio/shiny/issues/1121): plot interactions with ggplot2 now support `coord_fixed()`. ([#1525](https://github.com/rstudio/shiny/pull/1525))
|
||||
* Fixed #1121: plot interactions with ggplot2 now support `coord_fixed()`. (#1525)
|
||||
|
||||
* Added `snapshotExclude` function, which marks an output so that it is not recorded in a test snapshot. ([#1559](https://github.com/rstudio/shiny/pull/1559))
|
||||
* Added `snapshotExclude` function, which marks an output so that it is not recorded in a test snapshot. (#1559)
|
||||
|
||||
* Added `shiny:filedownload` JavaScript event, which is triggered when a `downloadButton` or `downloadLink` is clicked. Also, the values of `downloadHandler`s are not recorded in test snapshots, because the values change every time the application is run. ([#1559](https://github.com/rstudio/shiny/pull/1559))
|
||||
* Added `shiny:filedownload` JavaScript event, which is triggered when a `downloadButton` or `downloadLink` is clicked. Also, the values of `downloadHandler`s are not recorded in test snapshots, because the values change every time the application is run. (#1559)
|
||||
|
||||
* Added support for plot interactions with ggplot2 > 2.2.1. ([#1578](https://github.com/rstudio/shiny/pull/1578))
|
||||
* Added support for plot interactions with ggplot2 > 2.2.1. (#1578)
|
||||
|
||||
* Fixed [#1577](https://github.com/rstudio/shiny/issues/1577): Improved `escapeHTML` (util.js) in terms of the order dependency of replacing, XSS risk attack and performance. ([#1579](https://github.com/rstudio/shiny/pull/1579))
|
||||
* Fixed #1577: Improved `escapeHTML` (util.js) in terms of the order dependency of replacing, XSS risk attack and performance. (#1579)
|
||||
|
||||
* The `shiny:inputchanged` JavaScript event now includes two new fields, `binding` and `el`, which contain the input binding and DOM element, respectively. Additionally, `Shiny.onInputChange()` now accepts an optional argument, `opts`, which can contain the same fields. ([#1596](https://github.com/rstudio/shiny/pull/1596))
|
||||
* The `shiny:inputchanged` JavaScript event now includes two new fields, `binding` and `el`, which contain the input binding and DOM element, respectively. Additionally, `Shiny.onInputChange()` now accepts an optional argument, `opts`, which can contain the same fields. (#1596)
|
||||
|
||||
* The `NS()` function now returns a vectorized function. ([#1613](https://github.com/rstudio/shiny/pull/1613))
|
||||
* The `NS()` function now returns a vectorized function. (#1613)
|
||||
|
||||
* Fixed [#1617](https://github.com/rstudio/shiny/issues/1617): `fileInput` can have customized text for the button and the placeholder. ([#1619](https://github.com/rstudio/shiny/pull/1619))
|
||||
* Fixed #1617: `fileInput` can have customized text for the button and the placeholder. (#1619)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1511](https://github.com/rstudio/shiny/issues/1511): `fileInput`s did not trigger the `shiny:inputchanged` event on the client. Also removed `shiny:fileuploaded` JavaScript event, because it is no longer needed after this fix. ([#1541](https://github.com/rstudio/shiny/pull/1541), [#1570](https://github.com/rstudio/shiny/pull/1570))
|
||||
* Fixed #1511: `fileInput`s did not trigger the `shiny:inputchanged` event on the client. Also removed `shiny:fileuploaded` JavaScript event, because it is no longer needed after this fix. (#1541, #1570)
|
||||
|
||||
* Fixed [#1472](https://github.com/rstudio/shiny/issues/1472): With a Progress object, calling `set(value=NULL)` made the progress bar go to 100%. Now it does not change the value of the progress bar. The documentation also incorrectly said that setting the `value` to `NULL` would hide the progress bar. ([#1547](https://github.com/rstudio/shiny/pull/1547))
|
||||
* Fixed #1472: With a Progress object, calling `set(value=NULL)` made the progress bar go to 100%. Now it does not change the value of the progress bar. The documentation also incorrectly said that setting the `value` to `NULL` would hide the progress bar. (#1547)
|
||||
|
||||
* Fixed [#162](https://github.com/rstudio/shiny/issues/162): When a dynamically-generated input changed to a different `inputType`, it might be incorrectly deduplicated. ([#1594](https://github.com/rstudio/shiny/pull/1594))
|
||||
* Fixed #162: When a dynamically-generated input changed to a different `inputType`, it might be incorrectly deduplicated. (#1594)
|
||||
|
||||
* Removed redundant call to `inputs.setInput`. ([#1595](https://github.com/rstudio/shiny/pull/1595))
|
||||
* Removed redundant call to `inputs.setInput`. (#1595)
|
||||
|
||||
* Fixed bug where `dateRangeInput` did not respect `weekstart` argument. ([#1592](https://github.com/rstudio/shiny/pull/1592))
|
||||
* Fixed bug where `dateRangeInput` did not respect `weekstart` argument. (#1592)
|
||||
|
||||
* Fixed [#1598](https://github.com/rstudio/shiny/issues/1598): `setBookmarkExclude()` did not work properly inside of modules. ([#1599](https://github.com/rstudio/shiny/pull/1599))
|
||||
* Fixed #1598: `setBookmarkExclude()` did not work properly inside of modules. (#1599)
|
||||
|
||||
* Fixed [#1605](https://github.com/rstudio/shiny/issues/1605): sliders did not move when clicked on the bar area. ([#1610](https://github.com/rstudio/shiny/pull/1610))
|
||||
* Fixed #1605: sliders did not move when clicked on the bar area. (#1610)
|
||||
|
||||
* Fixed [#1621](https://github.com/rstudio/shiny/issues/1621): if a `reactiveTimer`'s session was closed before the first time that the `reactiveTimer` fired, then the `reactiveTimer` would not get cleared and would keep firing indefinitely. ([#1623](https://github.com/rstudio/shiny/pull/1623))
|
||||
* Fixed #1621: if a `reactiveTimer`'s session was closed before the first time that the `reactiveTimer` fired, then the `reactiveTimer` would not get cleared and would keep firing indefinitely. (#1623)
|
||||
|
||||
* Fixed [#1634](https://github.com/rstudio/shiny/issues/1634): If brushing on a plot causes the plot to redraw, then the redraw could in turn trigger the plot to redraw again and again. This was due to spurious changes in values of floating point numbers. ([#1641](https://github.com/rstudio/shiny/pull/1641))
|
||||
* Fixed #1634: If brushing on a plot causes the plot to redraw, then the redraw could in turn trigger the plot to redraw again and again. This was due to spurious changes in values of floating point numbers. (#1641)
|
||||
|
||||
### Library updates
|
||||
|
||||
* Closed [#1500](https://github.com/rstudio/shiny/issues/1500): Updated ion.rangeSlider to 2.1.6. ([#1540](https://github.com/rstudio/shiny/pull/1540))
|
||||
* Closed #1500: Updated ion.rangeSlider to 2.1.6. (#1540)
|
||||
|
||||
|
||||
shiny 1.0.0
|
||||
@@ -285,57 +587,57 @@ Here are some highlights from this release. For more details, see the full chang
|
||||
|
||||
## Support for testing Shiny applications
|
||||
|
||||
Shiny now supports automated testing of applications, with the [shinytest](https://github.com/rstudio/shinytest) package. Shinytest has not yet been released on CRAN, but will be soon. ([#18](https://github.com/rstudio/shiny/issues/18), [#1464](https://github.com/rstudio/shiny/pull/1464))
|
||||
Shiny now supports automated testing of applications, with the [shinytest](https://github.com/rstudio/shinytest) package. Shinytest has not yet been released on CRAN, but will be soon. (#18, #1464)
|
||||
|
||||
## Debounce/throttle reactives
|
||||
|
||||
Now there's an official way to slow down reactive values and expressions that invalidate too quickly. Pass a reactive expression to the new `debounce` or `throttle` function, and get back a modified reactive expression that doesn't invalidate as often. ([#1510](https://github.com/rstudio/shiny/pull/1510))
|
||||
Now there's an official way to slow down reactive values and expressions that invalidate too quickly. Pass a reactive expression to the new `debounce` or `throttle` function, and get back a modified reactive expression that doesn't invalidate as often. (#1510)
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Added a new `placeholder` argument to `verbatimTextOutput()`. The default is `FALSE`, which means that, if there is no content for this output, no representation of this slot will be made in the UI. Previsouly, even if there was no content, you'd see an empty rectangle in the UI that served as a placeholder. You can set `placeholder = TRUE` to revert back to that look. ([#1480](https://github.com/rstudio/shiny/pull/1480))
|
||||
* Added a new `placeholder` argument to `verbatimTextOutput()`. The default is `FALSE`, which means that, if there is no content for this output, no representation of this slot will be made in the UI. Previsouly, even if there was no content, you'd see an empty rectangle in the UI that served as a placeholder. You can set `placeholder = TRUE` to revert back to that look. (#1480)
|
||||
|
||||
### New features
|
||||
|
||||
* Added support for testing Shiny applications with the shinytest package. ([#18](https://github.com/rstudio/shiny/issues/18), [#1464](https://github.com/rstudio/shiny/pull/1464))
|
||||
* Added support for testing Shiny applications with the shinytest package. (#18, #1464)
|
||||
|
||||
* Added `debounce` and `throttle` functions, to control the rate at which reactive values and expressions invalidate. ([#1510](https://github.com/rstudio/shiny/pull/1510))
|
||||
* Added `debounce` and `throttle` functions, to control the rate at which reactive values and expressions invalidate. (#1510)
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Addressed [#1486](https://github.com/rstudio/shiny/issues/1486) by adding a new argument to `observeEvent` and `eventReactive`, called `ignoreInit` (defaults to `FALSE` for backwards compatibility). When set to `TRUE`, the action (i.e. the second argument: `handlerExpr` and `valueExpr`, respectively) will not be triggered when the observer/reactive is first created/initialized. In other words, `ignoreInit = TRUE` ensures that the `observeEvent` (or `eventReactive`) is *never* run right away. For more info, see the documentation (`?observeEvent`). ([#1494](https://github.com/rstudio/shiny/pull/1494))
|
||||
* Addressed #1486 by adding a new argument to `observeEvent` and `eventReactive`, called `ignoreInit` (defaults to `FALSE` for backwards compatibility). When set to `TRUE`, the action (i.e. the second argument: `handlerExpr` and `valueExpr`, respectively) will not be triggered when the observer/reactive is first created/initialized. In other words, `ignoreInit = TRUE` ensures that the `observeEvent` (or `eventReactive`) is *never* run right away. For more info, see the documentation (`?observeEvent`). (#1494)
|
||||
|
||||
* Added a new argument to `observeEvent` called `once`. When set to `TRUE`, it results in the observer being destroyed (stop observing) after the first time that `handlerExpr` is run (i.e. `once = TRUE` guarantees that the observer only runs, at most, once). For more info, see the documentation (`?observeEvent`). ([#1494](https://github.com/rstudio/shiny/pull/1494))
|
||||
* Added a new argument to `observeEvent` called `once`. When set to `TRUE`, it results in the observer being destroyed (stop observing) after the first time that `handlerExpr` is run (i.e. `once = TRUE` guarantees that the observer only runs, at most, once). For more info, see the documentation (`?observeEvent`). (#1494)
|
||||
|
||||
* Addressed [#1358](https://github.com/rstudio/shiny/issues/1358): more informative error message when calling `runApp()` inside of an app's app.R (or inside ui.R or server.R). ([#1482](https://github.com/rstudio/shiny/pull/1482))
|
||||
* Addressed #1358: more informative error message when calling `runApp()` inside of an app's app.R (or inside ui.R or server.R). (#1482)
|
||||
|
||||
* Added a more descriptive JS warning for `insertUI()` when the selector argument does not match anything in DOM. ([#1488](https://github.com/rstudio/shiny/pull/1488))
|
||||
* Added a more descriptive JS warning for `insertUI()` when the selector argument does not match anything in DOM. (#1488)
|
||||
|
||||
* Added support for injecting JavaScript code when the `shiny.testmode` option is set to `TRUE`. This makes it possible to record test events interactively. ([#1464](https://github.com/rstudio/shiny/pull/1464))
|
||||
* Added support for injecting JavaScript code when the `shiny.testmode` option is set to `TRUE`. This makes it possible to record test events interactively. (#1464)
|
||||
|
||||
* Added ability through arguments to the `a` tag function called inside `downloadButton()` and `downloadLink()`. Closes [#986](https://github.com/rstudio/shiny/issues/986). ([#1492](https://github.com/rstudio/shiny/pulls/1492))
|
||||
* Added ability through arguments to the `a` tag function called inside `downloadButton()` and `downloadLink()`. Closes #986. (#1492)
|
||||
|
||||
* Implemented [#1512](https://github.com/rstudio/shiny/issues/1512): added a `userData` environment to `session`, for storing arbitrary session-related variables. Generally, session-scoped variables are created just by declaring normal variables that are local to the Shiny server function, but `session$userData` may be more convenient for some advanced scenarios. ([#1513](https://github.com/rstudio/shiny/pull/1513))
|
||||
* Implemented #1512: added a `userData` environment to `session`, for storing arbitrary session-related variables. Generally, session-scoped variables are created just by declaring normal variables that are local to the Shiny server function, but `session$userData` may be more convenient for some advanced scenarios. (#1513)
|
||||
|
||||
* Relaxed naming requirements for `addResourcePath()` (the first character no longer needs to be a letter). ([#1529](https://github.com/rstudio/shiny/pull/1529))
|
||||
* Relaxed naming requirements for `addResourcePath()` (the first character no longer needs to be a letter). (#1529)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#969](https://github.com/rstudio/shiny/issues/969): allow navbarPage's `fluid` param to control both containers. ([#1481](https://github.com/rstudio/shiny/pull/1481))
|
||||
* Fixed #969: allow navbarPage's `fluid` param to control both containers. (#1481)
|
||||
|
||||
* Fixed [#1438](https://github.com/rstudio/shiny/issues/1438): `unbindAll()` should not be called when inserting content with `insertUI()` ([#1449](https://github.com/rstudio/shiny/pull/1449))
|
||||
* Fixed #1438: `unbindAll()` should not be called when inserting content with `insertUI()` (#1449)
|
||||
|
||||
* Fixed bug causing `<meta>` tags associated with HTML dependencies of Shiny R Markdown files to be rendered incorrectly. ([#1463](https://github.com/rstudio/shiny/pull/1463))
|
||||
* Fixed bug causing `<meta>` tags associated with HTML dependencies of Shiny R Markdown files to be rendered incorrectly. (#1463)
|
||||
|
||||
* Fixed [#1359](https://github.com/rstudio/shiny/issues/1359): `shinyApp()` options argument ignored when passed to `runApp()`. ([#1483](https://github.com/rstudio/shiny/pull/1483))
|
||||
* Fixed #1359: `shinyApp()` options argument ignored when passed to `runApp()`. (#1483)
|
||||
|
||||
* Fixed [#117](https://github.com/rstudio/shiny/issues/117): Reactive expressions now release references to cached values as soon as they are invalidated, potentially making those cached values eligible for garbage collection sooner. Previously, this would not occur until the next cached value was calculated and stored. ([#1504](https://github.com/rstudio/shiny/pull/1504/files))
|
||||
* Fixed #117: Reactive expressions now release references to cached values as soon as they are invalidated, potentially making those cached values eligible for garbage collection sooner. Previously, this would not occur until the next cached value was calculated and stored. (#1504)
|
||||
|
||||
* Fixed [#1013](https://github.com/rstudio/shiny/issues/1013): `flushReact` should be called after app loads. Observers set up outside of server functions were not running until after the first user connects. ([#1503](https://github.com/rstudio/shiny/pull/1503))
|
||||
* Fixed #1013: `flushReact` should be called after app loads. Observers set up outside of server functions were not running until after the first user connects. (#1503)
|
||||
|
||||
* Fixed [#1453](https://github.com/rstudio/shiny/issues/1453): When using a modal dialog with `easyClose=TRUE` in a Shiny gadget, pressing Esc would close both the modal and the gadget. Now pressing Esc only closes the modal. ([#1523](https://github.com/rstudio/shiny/pull/1523))
|
||||
* Fixed #1453: When using a modal dialog with `easyClose=TRUE` in a Shiny gadget, pressing Esc would close both the modal and the gadget. Now pressing Esc only closes the modal. (#1523)
|
||||
|
||||
### Library updates
|
||||
|
||||
@@ -351,25 +653,25 @@ This is a maintenance release of Shiny, with some bug fixes and minor new featur
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Added a `fade` argument to `modalDialog()` -- setting it to `FALSE` will remove the usual fade-in animation for that modal window. ([#1414](https://github.com/rstudio/shiny/pull/1414))
|
||||
* Added a `fade` argument to `modalDialog()` -- setting it to `FALSE` will remove the usual fade-in animation for that modal window. (#1414)
|
||||
|
||||
* Fixed a "duplicate binding" error that occurred in some edge cases involving `insertUI` and nested `uiOutput`. ([#1402](https://github.com/rstudio/shiny/pull/1402))
|
||||
* Fixed a "duplicate binding" error that occurred in some edge cases involving `insertUI` and nested `uiOutput`. (#1402)
|
||||
|
||||
* Fixed [#1422](https://github.com/rstudio/shiny/issues/1422): When using the `shiny.trace` option, allow specifying to only log SEND or RECV messages, or both. (PR [#1428](https://github.com/rstudio/shiny/pull/1428))
|
||||
* Fixed #1422: When using the `shiny.trace` option, allow specifying to only log SEND or RECV messages, or both. (PR #1428)
|
||||
|
||||
* Fixed [#1419](https://github.com/rstudio/shiny/issues/1419): Allow overriding a JS custom message handler. (PR [#1445](https://github.com/rstudio/shiny/pull/1445))
|
||||
* Fixed #1419: Allow overriding a JS custom message handler. (PR #1445)
|
||||
|
||||
* Added `exportTestValues()` function, which allows a test driver to query the session for values internal to an application's server function. This only has an effect if the `shiny.testmode` option is set to `TRUE`. ([#1436](https://github.com/rstudio/shiny/pull/1436))
|
||||
* Added `exportTestValues()` function, which allows a test driver to query the session for values internal to an application's server function. This only has an effect if the `shiny.testmode` option is set to `TRUE`. (#1436)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1427](https://github.com/rstudio/shiny/issues/1427): make sure that modals do not close incorrectly when an element inside them is triggered as hidden. ([#1430](https://github.com/rstudio/shiny/pull/1430))
|
||||
* Fixed #1427: make sure that modals do not close incorrectly when an element inside them is triggered as hidden. (#1430)
|
||||
|
||||
* Fixed [#1404](https://github.com/rstudio/shiny/issues/1404): stack trace tests were not compatible with the byte-code compiler in R-devel, which now tracks source references.
|
||||
* Fixed #1404: stack trace tests were not compatible with the byte-code compiler in R-devel, which now tracks source references.
|
||||
|
||||
* `sliderInputBinding.setValue()` now sends a slider's value immediately, instead of waiting for the usual 250ms debounce delay. ([#1429](https://github.com/rstudio/shiny/pull/1429))
|
||||
* `sliderInputBinding.setValue()` now sends a slider's value immediately, instead of waiting for the usual 250ms debounce delay. (#1429)
|
||||
|
||||
* Fixed a bug where, in versions of R before 3.2, Shiny applications could crash due to a bug in R's implementation of `list2env()`. ([#1446](https://github.com/rstudio/shiny/pull/1446))
|
||||
* Fixed a bug where, in versions of R before 3.2, Shiny applications could crash due to a bug in R's implementation of `list2env()`. (#1446)
|
||||
|
||||
shiny 0.14.1
|
||||
============
|
||||
@@ -380,26 +682,26 @@ This is a maintenance release of Shiny, with some bug fixes and minor new featur
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Restored file inputs are now copied on restore, so that the restored application can't modify the bookmarked file. ([#1370](https://github.com/rstudio/shiny/issues/1370))
|
||||
* Restored file inputs are now copied on restore, so that the restored application can't modify the bookmarked file. (#1370)
|
||||
|
||||
* Added support for plot interaction in the development version of ggplot2, 2.1.0.9000. Also added support for ggplot2 plots with `coord_flip()` (in the development version of ggplot2). ([hadley/ggplot2#1781](https://github.com/hadley/ggplot2/issues/1781), [#1392](https://github.com/rstudio/shiny/pull/1392))
|
||||
* Added support for plot interaction in the development version of ggplot2, 2.1.0.9000. Also added support for ggplot2 plots with `coord_flip()` (in the development version of ggplot2). ([hadley/ggplot2#1781](https://github.com/hadley/ggplot2/issues/1781), #1392)
|
||||
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1093](https://github.com/rstudio/shiny/issues/1093) better: `updateRadioButtons()` and `updateCheckboxGroupInput()` were not working correctly if the choices were given as a numeric vector. This had been solved in [#1291](https://github.com/rstudio/shiny/pull/1291), but that introduced a different bug [#1396](https://github.com/rstudio/shiny/issues/1396) that this better fix avoids. ([#1370](https://github.com/rstudio/shiny/pull/1397))
|
||||
* Fixed #1093 better: `updateRadioButtons()` and `updateCheckboxGroupInput()` were not working correctly if the choices were given as a numeric vector. This had been solved in #1291, but that introduced a different bug #1396 that this better fix avoids. (#1370)
|
||||
|
||||
* Fixed [#1368](https://github.com/rstudio/shiny/issues/1368): If an app with a file input was bookmarked and restored, and then the restored app was bookmarked and restored (without uploading a new file), then it would fail to restore the file the second time. ([#1370](https://github.com/rstudio/shiny/pull/1370))
|
||||
* Fixed #1368: If an app with a file input was bookmarked and restored, and then the restored app was bookmarked and restored (without uploading a new file), then it would fail to restore the file the second time. (#1370)
|
||||
|
||||
* Fixed [#1369](https://github.com/rstudio/shiny/issues/1369): `sliderInput()` did not allow showing numbers without a thousands separator.
|
||||
* Fixed #1369: `sliderInput()` did not allow showing numbers without a thousands separator.
|
||||
|
||||
* Fixed [#1346](https://github.com/rstudio/shiny/issues/1346) and [#1107](https://github.com/rstudio/shiny/issues/1107) : jQuery UI's datepicker conflicted with the bootstrap-datepicker used by Shiny's `dateInput()` and `dateRangeInput()`. ([#1374](https://github.com/rstudio/shiny/pull/1374))
|
||||
* Fixed #1346 and #1107 : jQuery UI's datepicker conflicted with the bootstrap-datepicker used by Shiny's `dateInput()` and `dateRangeInput()`. (#1374)
|
||||
|
||||
### Library updates
|
||||
|
||||
* Updated to bootstrap-datepicker 1.6.4. ([#1218](https://github.com/rstudio/shiny/issues/1218), [#1374](https://github.com/rstudio/shiny/pull/1374))
|
||||
* Updated to bootstrap-datepicker 1.6.4. (#1218, #1374)
|
||||
|
||||
* Updated to jQuery UI 1.12.1. Previously, Shiny included a build of 1.11.4 which was missing the datepicker component due to a conflict with the bootstrap-datepicker used by Shiny's `dateInput()` and `dateRangeInput()`. ([#1374](https://github.com/rstudio/shiny/pull/1374))
|
||||
* Updated to jQuery UI 1.12.1. Previously, Shiny included a build of 1.11.4 which was missing the datepicker component due to a conflict with the bootstrap-datepicker used by Shiny's `dateInput()` and `dateRangeInput()`. (#1374)
|
||||
|
||||
|
||||
shiny 0.14
|
||||
@@ -471,9 +773,9 @@ There are many more minor features, small improvements, and bug fixes than we ca
|
||||
|
||||
* **Error Sanitization**: you now have the option to sanitize error messages; in other words, the content of the original error message can be suppressed so that it doesn't leak any sensitive information. To sanitize errors everywhere in your app, just add `options(shiny.sanitize.errors = TRUE)` somewhere in your app. Read [this article](http://shiny.rstudio.com/articles/sanitize-errors.html) for more, or play with the [demo app](https://gallery.shinyapps.io/110-error-sanitization/).
|
||||
|
||||
* **Code Diagnostics**: if there is an error parsing `ui.R`, `server.R`, `app.R`, or `global.R`, Shiny will search the code for missing commas, extra commas, and unmatched braces, parens, and brackets, and will print out messages pointing out those problems. ([#1126](https://github.com/rstudio/shiny/pull/1126))
|
||||
* **Code Diagnostics**: if there is an error parsing `ui.R`, `server.R`, `app.R`, or `global.R`, Shiny will search the code for missing commas, extra commas, and unmatched braces, parens, and brackets, and will print out messages pointing out those problems. (#1126)
|
||||
|
||||
* **Reactlog visualization**: by default, the [`showReactLog()` function](http://shiny.rstudio.com/reference/shiny/latest/showReactLog.html) (which brings up the reactive graph) also displays the time that each reactive and observer were active for:
|
||||
* **Reactlog visualization**: by default, the [`showReactLog()` function](http://shiny.rstudio.com/reference/shiny/latest/reactlog.html) (which brings up the reactive graph) also displays the time that each reactive and observer were active for:
|
||||
|
||||
<p align="center">
|
||||
<img src="http://shiny.rstudio.com/images/reactlog.png" alt="modal-dialog" width="75%"/>
|
||||
@@ -495,99 +797,99 @@ There are many more minor features, small improvements, and bug fixes than we ca
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Progress indicators can now either use the new notification API, using `style = "notification"` (default), or be displayed with the previous styling, using `style = "old"`. You can also call `shinyOptions(progress.style = "old")` in the server function to make all progress indicators use the old styling. Note that if you had customized your progress indicators with additional CSS, you'll need to use the old style if you want your UI to look the same ([#1160](https://github.com/rstudio/shiny/pull/1160) and [#1329](https://github.com/rstudio/shiny/pull/1329)).
|
||||
* Progress indicators can now either use the new notification API, using `style = "notification"` (default), or be displayed with the previous styling, using `style = "old"`. You can also call `shinyOptions(progress.style = "old")` in the server function to make all progress indicators use the old styling. Note that if you had customized your progress indicators with additional CSS, you'll need to use the old style if you want your UI to look the same (#1160 and #1329).
|
||||
|
||||
* Closed [#1161](https://github.com/rstudio/shiny/issues/1161): Deprecated the `position` argument to `tabsetPanel()` since Bootstrap 3 stopped supporting this feature.
|
||||
* Closed #1161: Deprecated the `position` argument to `tabsetPanel()` since Bootstrap 3 stopped supporting this feature.
|
||||
|
||||
* The long-deprecated ability to pass a `func` argument to many of the `render` functions has been removed.
|
||||
|
||||
### New features
|
||||
|
||||
* Added the ability to bookmark and restore application state. (main PR: [#1209](https://github.com/rstudio/shiny/pull/1209))
|
||||
* Added the ability to bookmark and restore application state. (main PR: #1209)
|
||||
|
||||
* Added a new notification API. From R, there are new functions `showNotification` and `hideNotification`. From JavaScript, there is a new `Shiny.notification` object that controls notifications. ([#1141](https://github.com/rstudio/shiny/pull/1141))
|
||||
* Added a new notification API. From R, there are new functions `showNotification` and `hideNotification`. From JavaScript, there is a new `Shiny.notification` object that controls notifications. (#1141)
|
||||
|
||||
* Progress indicators now use the notification API. ([#1160](https://github.com/rstudio/shiny/pull/1160))
|
||||
* Progress indicators now use the notification API. (#1160)
|
||||
|
||||
* Added the ability for the client browser to reconnect to a new session on the server, by setting `session$allowReconnect(TRUE)`. This requires a version of Shiny Server that supports reconnections. ([#1074](https://github.com/rstudio/shiny/pull/1074))
|
||||
* Added the ability for the client browser to reconnect to a new session on the server, by setting `session$allowReconnect(TRUE)`. This requires a version of Shiny Server that supports reconnections. (#1074)
|
||||
|
||||
* Added modal dialogs. ([#1157](https://github.com/rstudio/shiny/pull/1157))
|
||||
* Added modal dialogs. (#1157)
|
||||
|
||||
* Added insertUI and removeUI functions to be able to add and remove chunks of UI, standalone, and all independent of one another. ([#1174](https://github.com/rstudio/shiny/pull/1174) and [#1189](https://github.com/rstudio/shiny/pull/1189))
|
||||
* Added insertUI and removeUI functions to be able to add and remove chunks of UI, standalone, and all independent of one another. (#1174 and #1189)
|
||||
|
||||
* Improved `renderTable()` function to make the tables look prettier and also provide the user with a lot more parameters to customize their tables with. ([#1129](https://github.com/rstudio/shiny/pull/1129))
|
||||
* Improved `renderTable()` function to make the tables look prettier and also provide the user with a lot more parameters to customize their tables with. (#1129)
|
||||
|
||||
* Added support for the `pool` package (use Shiny's timer/scheduler). ([#1226](https://github.com/rstudio/shiny/pull/1226))
|
||||
* Added support for the `pool` package (use Shiny's timer/scheduler). (#1226)
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Added `cancelOutput` argument to `req()`. This causes the currently executing reactive to cancel its execution, and leave its previous state alone (as opposed to clearing the output). ([#1272](https://github.com/rstudio/shiny/pull/1272))
|
||||
* Added `cancelOutput` argument to `req()`. This causes the currently executing reactive to cancel its execution, and leave its previous state alone (as opposed to clearing the output). (#1272)
|
||||
|
||||
* `Display: Showcase` now displays the .js, .html and .css files in the `www` directory by default. In order to use showcase mode and not display these, include a new line in your Description file: `IncludeWWW: False`. ([#1185](https://github.com/rstudio/shiny/pull/1185))
|
||||
* `Display: Showcase` now displays the .js, .html and .css files in the `www` directory by default. In order to use showcase mode and not display these, include a new line in your Description file: `IncludeWWW: False`. (#1185)
|
||||
|
||||
* Added an error sanitization option: `options(shiny.sanitize.errors = TRUE)`. By default, this option is `FALSE`. When `TRUE`, normal errors will be sanitized, displaying only a generic error message. This changes the look of an app when errors are printed (but the console log remains the same). ([#1156](https://github.com/rstudio/shiny/pull/1156))
|
||||
* Added an error sanitization option: `options(shiny.sanitize.errors = TRUE)`. By default, this option is `FALSE`. When `TRUE`, normal errors will be sanitized, displaying only a generic error message. This changes the look of an app when errors are printed (but the console log remains the same). (#1156)
|
||||
|
||||
* Added the option of passing arguments to an `xxxOutput()` function through the corresponding `renderXXX()` function via an `outputArgs` parameter to the latter. This is only valid for snippets of Shiny code in an interactive `runtime: shiny` Rmd document (never for full apps, even if embedded in an Rmd). ([#1443](https://github.com/rstudio/shiny/pull/1143))
|
||||
* Added the option of passing arguments to an `xxxOutput()` function through the corresponding `renderXXX()` function via an `outputArgs` parameter to the latter. This is only valid for snippets of Shiny code in an interactive `runtime: shiny` Rmd document (never for full apps, even if embedded in an Rmd). (#1443)
|
||||
|
||||
* Added `updateActionButton()` function, so the user can change an `actionButton`'s (or `actionLink`'s) label and/or icon. It also checks that the icon argument (for both creation and updating of a button) is valid and throws a warning otherwise. ([#1134](https://github.com/rstudio/shiny/pull/1134))
|
||||
* Added `updateActionButton()` function, so the user can change an `actionButton`'s (or `actionLink`'s) label and/or icon. It also checks that the icon argument (for both creation and updating of a button) is valid and throws a warning otherwise. (#1134)
|
||||
|
||||
* Added code diagnostics: if there is an error parsing ui.R, server.R, app.R, or global.R, Shiny will search the code for missing commas, extra commas, and unmatched braces, parens, and brackets, and will print out messages pointing out those problems. ([#1126](https://github.com/rstudio/shiny/pull/1126))
|
||||
* Added code diagnostics: if there is an error parsing ui.R, server.R, app.R, or global.R, Shiny will search the code for missing commas, extra commas, and unmatched braces, parens, and brackets, and will print out messages pointing out those problems. (#1126)
|
||||
|
||||
* Added support for horizontal dividers in `navbarMenu`. ([#1147](https://github.com/rstudio/shiny/pull/1147))
|
||||
* Added support for horizontal dividers in `navbarMenu`. (#1147)
|
||||
|
||||
* Added `placeholder` option to `passwordInput`. ([#1152](https://github.com/rstudio/shiny/pull/1152))
|
||||
* Added `placeholder` option to `passwordInput`. (#1152)
|
||||
|
||||
* Added `session$resetBrush(brushId)` (R) and `Shiny.resetBrush(brushId)` (JS) to programatically clear brushes from `imageOutput`/`plotOutput`. ([#1197](https://github.com/rstudio/shiny/pull/1197))
|
||||
* Added `session$resetBrush(brushId)` (R) and `Shiny.resetBrush(brushId)` (JS) to programatically clear brushes from `imageOutput`/`plotOutput`. (#1197)
|
||||
|
||||
* Added textAreaInput. (thanks, [@nuno-agostinho](https://github.com/nuno-agostinho)! [#1300](https://github.com/rstudio/shiny/pull/1300))
|
||||
* Added textAreaInput. (thanks, [@nuno-agostinho](https://github.com/nuno-agostinho)! #1300)
|
||||
|
||||
* Added `session$sendBinaryMessage(type, message)` method for sending custom binary data to the client. See `?session`. (thanks, [@daef](https://github.com/daef)! [#1316](https://github.com/rstudio/shiny/pull/1316) and [#1320](https://github.com/rstudio/shiny/pull/1320))
|
||||
* Added `session$sendBinaryMessage(type, message)` method for sending custom binary data to the client. See `?session`. (thanks, [@daef](https://github.com/daef)! #1316 and #1320)
|
||||
|
||||
* Almost all code examples now have a runnable example with `shinyApp()`, so that users can run the examples and see them in action. ([#1158](https://github.com/rstudio/shiny/pull/1158))
|
||||
* Almost all code examples now have a runnable example with `shinyApp()`, so that users can run the examples and see them in action. (#1158)
|
||||
|
||||
* When resized, plots are drawn with `replayPlot()`, instead of re-executing all plotting code. This results in faster plot rendering. ([#1112](https://github.com/rstudio/shiny/pull/1112))
|
||||
* When resized, plots are drawn with `replayPlot()`, instead of re-executing all plotting code. This results in faster plot rendering. (#1112)
|
||||
|
||||
* Exported the `isTruthy()` function. (part of PR [#1272](https://github.com/rstudio/shiny/pull/1272))
|
||||
* Exported the `isTruthy()` function. (part of PR #1272)
|
||||
|
||||
* Reactive log now shows elapsed time for reactives and observers. ([#1132](https://github.com/rstudio/shiny/pull/1132))
|
||||
* Reactive log now shows elapsed time for reactives and observers. (#1132)
|
||||
|
||||
* Nodes in the reactlog visualization are now sticky if the user drags them. ([#1283](https://github.com/rstudio/shiny/pull/1283))
|
||||
* Nodes in the reactlog visualization are now sticky if the user drags them. (#1283)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1350](https://github.com/rstudio/shiny/issues/1350): Highlighting of reactives didn't work in showcase mode.
|
||||
* Fixed #1350: Highlighting of reactives didn't work in showcase mode.
|
||||
|
||||
* Fixed [#1331](https://github.com/rstudio/shiny/issues/1331): `renderPlot()` now correctly records and replays plots when `execOnResize = FALSE`.
|
||||
* Fixed #1331: `renderPlot()` now correctly records and replays plots when `execOnResize = FALSE`.
|
||||
|
||||
* `updateDateInput()` and `updateDateRangeInput()` can now clear the date input fields. (thanks, [@gaborcsardi](https://github.com/gaborcsardi)! [#1299](https://github.com/rstudio/shiny/pull/1299), [#1315](https://github.com/rstudio/shiny/pull/1315) and [#1317](https://github.com/rstudio/shiny/pull/1317))
|
||||
* `updateDateInput()` and `updateDateRangeInput()` can now clear the date input fields. (thanks, [@gaborcsardi](https://github.com/gaborcsardi)! #1299, #1315 and #1317)
|
||||
|
||||
* Fixed [#561](https://github.com/rstudio/shiny/issues/561): DataTables previously might pop up a warning when the data was updated extremely frequently.
|
||||
* Fixed #561: DataTables previously might pop up a warning when the data was updated extremely frequently.
|
||||
|
||||
* Fixed [#776](https://github.com/rstudio/shiny/issues/776): In some browsers, plots sometimes flickered when updated.
|
||||
* Fixed #776: In some browsers, plots sometimes flickered when updated.
|
||||
|
||||
* Fixed [#543](https://github.com/rstudio/shiny/issues/543) and [#855](https://github.com/rstudio/shiny/issues/855): When `navbarPage()` had a `navbarMenu()` as the first item, it did not automatically select an item.
|
||||
* Fixed #543 and #855: When `navbarPage()` had a `navbarMenu()` as the first item, it did not automatically select an item.
|
||||
|
||||
* Fixed [#970](https://github.com/rstudio/shiny/issues/970): `navbarPage()` previously did not have an option to set the selected tab.
|
||||
* Fixed #970: `navbarPage()` previously did not have an option to set the selected tab.
|
||||
|
||||
* Fixed [#1253](https://github.com/rstudio/shiny/issues/1253): Memory could leak when an observer was destroyed without first being invalidated.
|
||||
* Fixed #1253: Memory could leak when an observer was destroyed without first being invalidated.
|
||||
|
||||
* Fixed [#931](https://github.com/rstudio/shiny/issues/931): Nested observers could leak memory.
|
||||
* Fixed #931: Nested observers could leak memory.
|
||||
|
||||
* Fixed [#1144](https://github.com/rstudio/shiny/issues/1144): `updateRadioButton()` and `updateCheckboxGroupInput()` broke controls when used in modules (thanks, [@sipemu](https://github.com/sipemu)!).
|
||||
* Fixed #1144: `updateRadioButton()` and `updateCheckboxGroupInput()` broke controls when used in modules (thanks, [@sipemu](https://github.com/sipemu)!).
|
||||
|
||||
* Fixed [#1093](https://github.com/rstudio/shiny/issues/1093): `updateRadioButtons()` and `updateCheckboxGroupInput()` didn't work if `choices` was numeric vector.
|
||||
* Fixed #1093: `updateRadioButtons()` and `updateCheckboxGroupInput()` didn't work if `choices` was numeric vector.
|
||||
|
||||
* Fixed [#1122](https://github.com/rstudio/shiny/issues/1122): `downloadHandler()` popped up empty browser window if the file wasn't present. It now gives a 404 error code.
|
||||
* Fixed #1122: `downloadHandler()` popped up empty browser window if the file wasn't present. It now gives a 404 error code.
|
||||
|
||||
* Fixed [#1278](https://github.com/rstudio/shiny/issues/1278): Reactive system was being flushed too often (usually this just means a more-expensive no-op than necessary).
|
||||
* Fixed #1278: Reactive system was being flushed too often (usually this just means a more-expensive no-op than necessary).
|
||||
|
||||
* Fixed [#803](https://github.com/rstudio/shiny/issues/803) and [#1179](https://github.com/rstudio/shiny/issues/1179): handling malformed dates in `dateInput` and `updateDateInput()`.
|
||||
* Fixed #803 and #1179: handling malformed dates in `dateInput` and `updateDateInput()`.
|
||||
|
||||
* Fixed [#1257](https://github.com/rstudio/shiny/issues/1257): `updateSelectInput()` didn't work correctly in IE 11 and Edge.
|
||||
* Fixed #1257: `updateSelectInput()` didn't work correctly in IE 11 and Edge.
|
||||
|
||||
* Fixed [#971](https://github.com/rstudio/shiny/issues/971): `runApp()` would give confusing error if `port` was not numeric.
|
||||
* Fixed #971: `runApp()` would give confusing error if `port` was not numeric.
|
||||
|
||||
* Shiny now avoids using ports that Chrome deems unsafe. ([#1222](https://github.com/rstudio/shiny/pull/1222))
|
||||
* Shiny now avoids using ports that Chrome deems unsafe. (#1222)
|
||||
|
||||
* Added workaround for quartz graphics device resolution bug, where resolution is hard-coded to 72 ppi.
|
||||
|
||||
|
||||
26
R/app-state.R
Normal file
26
R/app-state.R
Normal file
@@ -0,0 +1,26 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
# The current app state is a place to read and hang state for the
|
||||
# currently-running application. This is useful for setting options that will
|
||||
# last as long as the application is running.
|
||||
|
||||
.globals$appState <- NULL
|
||||
|
||||
initCurrentAppState <- function(appobj) {
|
||||
if (!is.null(.globals$appState)) {
|
||||
stop("Can't initialize current app state when another is currently active.")
|
||||
}
|
||||
.globals$appState <- new.env(parent = emptyenv())
|
||||
.globals$appState$app <- appobj
|
||||
# Copy over global options
|
||||
.globals$appState$options <- .globals$options
|
||||
}
|
||||
|
||||
getCurrentAppState <- function() {
|
||||
.globals$appState
|
||||
}
|
||||
|
||||
clearCurrentAppState <- function() {
|
||||
.globals$appState <- NULL
|
||||
}
|
||||
293
R/app_template.R
Normal file
293
R/app_template.R
Normal file
@@ -0,0 +1,293 @@
|
||||
#' Generate a Shiny application from a template
|
||||
#'
|
||||
#' This function populates a directory with files for a Shiny application.
|
||||
#'
|
||||
#' In an interactive R session, this function will, by default, prompt the user
|
||||
#' to select which components to add to the application. Choices are
|
||||
#'
|
||||
#' ```
|
||||
#' 1: All
|
||||
#' 2: app.R : Main application file
|
||||
#' 3: R/example.R : Helper file with R code
|
||||
#' 4: R/example-module.R : Example module
|
||||
#' 5: tests/shinytest/ : Tests using the shinytest package
|
||||
#' 6: tests/testthat/ : Tests using the testthat package
|
||||
#' ```
|
||||
#'
|
||||
#' If option 1 is selected, the full example application including the
|
||||
#' following files and directories is created:
|
||||
#'
|
||||
#' ```
|
||||
#' appdir/
|
||||
#' |- app.R
|
||||
#' |- R
|
||||
#' | |- example-module.R
|
||||
#' | `- example.R
|
||||
#' `- tests
|
||||
#' |- shinytest.R
|
||||
#' |- shinytest
|
||||
#' | `- mytest.R
|
||||
#' |- testthat.R
|
||||
#' `- testthat
|
||||
#' |- test-examplemodule.R
|
||||
#' |- test-server.R
|
||||
#' `- test-sort.R
|
||||
#' ```
|
||||
#'
|
||||
#' Some notes about these files:
|
||||
#' * `app.R` is the main application file.
|
||||
#' * All files in the `R/` subdirectory are automatically sourced when the
|
||||
#' application is run.
|
||||
#' * `R/example.R` and `R/example-module.R` are automatically sourced when
|
||||
#' the application is run. The first contains a function `lexical_sort()`,
|
||||
#' and the second contains code for module created by the
|
||||
#' [moduleServer()] function, which is used in the application.
|
||||
#' * `tests/` contains various tests for the application. You may
|
||||
#' choose to use or remove any of them. They can be executed by the
|
||||
#' [runTests()] function.
|
||||
#' * `tests/shinytest.R` is a test runner for test files in the
|
||||
#' `tests/shinytest/` directory.
|
||||
#' * `tests/shinytest/mytest.R` is a test that uses the
|
||||
#' [shinytest](https://rstudio.github.io/shinytest/) package to do
|
||||
#' snapshot-based testing.
|
||||
#' * `tests/testthat.R` is a test runner for test files in the
|
||||
#' `tests/testthat/` directory using the [testthat](https://testthat.r-lib.org/) package.
|
||||
#' * `tests/testthat/test-examplemodule.R` is a test for an application's module server function.
|
||||
#' * `tests/testthat/test-server.R` is a test for the application's server code
|
||||
#' * `tests/testthat/test-sort.R` is a test for a supporting function in the `R/` directory.
|
||||
#'
|
||||
#' @param path Path to create new shiny application template.
|
||||
#' @param examples Either one of "default", "ask", "all", or any combination of
|
||||
#' "app", "rdir", "module", "shinytest", and "testthat". In an
|
||||
#' interactive session, "default" falls back to "ask"; in a non-interactive
|
||||
#' session, "default" falls back to "all". With "ask", this function will
|
||||
#' prompt the user to select which template items will be added to the new app
|
||||
#' directory. With "all", all template items will be added to the app
|
||||
#' directory.
|
||||
#' @param dryrun If `TRUE`, don't actually write any files; just print out which
|
||||
#' files would be written.
|
||||
#'
|
||||
#' @export
|
||||
shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
|
||||
{
|
||||
if (is.null(path)) {
|
||||
stop("Please provide a `path`.")
|
||||
}
|
||||
|
||||
# =======================================================
|
||||
# Option handling
|
||||
# =======================================================
|
||||
|
||||
choices <- c(
|
||||
app = "app.R : Main application file",
|
||||
rdir = "R/example.R : Helper file with R code",
|
||||
module = "R/example-module.R : Example module",
|
||||
shinytest = "tests/shinytest/ : Tests using the shinytest package",
|
||||
testthat = "tests/testthat/ : Tests using the testthat package"
|
||||
)
|
||||
|
||||
if (identical(examples, "default")) {
|
||||
if (interactive()) {
|
||||
examples <- "ask"
|
||||
} else {
|
||||
examples <- "all"
|
||||
}
|
||||
}
|
||||
|
||||
if (!identical(examples, "ask") &&
|
||||
!identical(examples, "all") &&
|
||||
any(! examples %in% names(choices)))
|
||||
{
|
||||
stop('`examples` must be one of "default", "ask", "all", or any combination of "',
|
||||
paste(names(choices), collapse = '", "'), '".')
|
||||
}
|
||||
|
||||
if (identical(examples, "ask")) {
|
||||
response <- select_menu(
|
||||
c(all = "All", choices),
|
||||
title = paste0(
|
||||
"Select which of the following to add at ", path, "/ :"
|
||||
),
|
||||
msg = "Enter one or more numbers (with spaces), or an empty line to exit: \n"
|
||||
)
|
||||
|
||||
examples <- names(response)
|
||||
}
|
||||
|
||||
examples <- unique(examples)
|
||||
|
||||
if ("all" %in% examples) {
|
||||
examples <- names(choices)
|
||||
}
|
||||
|
||||
if (length(examples) == 0) {
|
||||
return(invisible())
|
||||
}
|
||||
|
||||
if ("shinytest" %in% examples) {
|
||||
if (!is_available("shinytest", "1.4.0"))
|
||||
{
|
||||
message(
|
||||
"The tests/shinytest directory needs shinytest 1.4.0 or later to work properly."
|
||||
)
|
||||
if (is_available("shinytest")) {
|
||||
message("You currently have shinytest ",
|
||||
utils::packageVersion("shinytest"), " installed.")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# =======================================================
|
||||
# Utility functions
|
||||
# =======================================================
|
||||
|
||||
# Check if a directory is empty, ignoring certain files
|
||||
dir_is_empty <- function(path) {
|
||||
files <- list.files(path, all.files = TRUE, no.. = TRUE)
|
||||
# Ignore .DS_Store files, which are sometimes automatically created on macOS
|
||||
files <- setdiff(files, ".DS_Store")
|
||||
return(length(files) != 0)
|
||||
}
|
||||
|
||||
# Helper to resolve paths relative to our template
|
||||
template_path <- function(...) {
|
||||
system.file("app_template", ..., package = "shiny")
|
||||
}
|
||||
|
||||
# Resolve path relative to destination
|
||||
dest_path <- function(...) {
|
||||
file.path(path, ...)
|
||||
}
|
||||
|
||||
mkdir <- function(path) {
|
||||
if (!dirExists(path)) {
|
||||
message("Creating ", ensure_trailing_slash(path))
|
||||
if (!dryrun) {
|
||||
dir.create(path, recursive = TRUE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Copy a file from the template directory to the destination directory. If the
|
||||
# file has templating code (it contains `{{` in the text), then run it through
|
||||
# the htmlTemplate().
|
||||
copy_file_one <- function(name) {
|
||||
from <- template_path(name)
|
||||
to <- dest_path(name)
|
||||
|
||||
message("Creating ", to)
|
||||
if (file.exists(to)) {
|
||||
stop(to, " already exists. Please remove it and try again.", call. = FALSE)
|
||||
}
|
||||
|
||||
if (!dryrun) {
|
||||
is_template <- any(grepl("{{", readLines(from), fixed = TRUE))
|
||||
|
||||
if (is_template) {
|
||||
writeChar(
|
||||
as.character(htmlTemplate(
|
||||
from,
|
||||
rdir = "rdir" %in% examples,
|
||||
module = "module" %in% examples
|
||||
)),
|
||||
con = to,
|
||||
eos = NULL
|
||||
)
|
||||
} else {
|
||||
file.copy(from, to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Copy multiple files from template to destination.
|
||||
copy_file <- function(names) {
|
||||
for (name in names) {
|
||||
copy_file_one(name)
|
||||
}
|
||||
}
|
||||
|
||||
# Copy the files for a tests/ subdirectory
|
||||
copy_test_dir <- function(name) {
|
||||
files <- dir(template_path("tests"), recursive = TRUE)
|
||||
# Note: This is not the same as using dir(pattern = "^shinytest"), since
|
||||
# that will not match files inside of shinytest/.
|
||||
files <- files[grepl(paste0("^", name), files)]
|
||||
|
||||
# Filter out files that are not module files in the R directory.
|
||||
if (! "rdir" %in% examples) {
|
||||
# find all files in the testthat folder that are not module or server files
|
||||
is_r_folder_file <- (!grepl("module|server", basename(files))) & (dirname(files) == "testthat")
|
||||
files <- files[!is_r_folder_file]
|
||||
}
|
||||
|
||||
# Filter out module files, if applicable.
|
||||
if (! "module" %in% examples) {
|
||||
files <- files[!grepl("module", files)]
|
||||
}
|
||||
|
||||
mkdir(dest_path("tests"))
|
||||
|
||||
# Create any subdirectories if needed
|
||||
dirs <- setdiff(unique(dirname(files)), ".")
|
||||
for (dir in dirs) {
|
||||
mkdir(dest_path("tests", dir))
|
||||
}
|
||||
|
||||
copy_file(file.path("tests", files))
|
||||
}
|
||||
|
||||
# =======================================================
|
||||
# Main function
|
||||
# =======================================================
|
||||
|
||||
if (is.null(path)) {
|
||||
stop("`path` is missing.")
|
||||
}
|
||||
if (file.exists(path) && !dirExists(path)) {
|
||||
stop(path, " exists but is not a directory.")
|
||||
}
|
||||
|
||||
if (dirExists(path) && dir_is_empty(path)) {
|
||||
if (interactive()) {
|
||||
response <- readline(paste0(
|
||||
ensure_trailing_slash(path),
|
||||
" is not empty. Do you want to use this directory anyway? [y/n] "
|
||||
))
|
||||
if (tolower(response) != "y") {
|
||||
return(invisible())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mkdir(path)
|
||||
}
|
||||
|
||||
if ("app" %in% examples) {
|
||||
copy_file("app.R")
|
||||
}
|
||||
|
||||
# R/ dir with non-module files
|
||||
if ("rdir" %in% examples) {
|
||||
files <- dir(template_path("R"))
|
||||
non_module_files <- files[!grepl("module.R$", files)]
|
||||
mkdir(dest_path("R"))
|
||||
copy_file(file.path("R", non_module_files))
|
||||
}
|
||||
|
||||
# R/ dir with module files
|
||||
if ("module" %in% examples) {
|
||||
files <- dir(template_path("R"))
|
||||
module_files <- files[grepl("module.R$", files)]
|
||||
mkdir(dest_path("R"))
|
||||
copy_file(file.path("R", module_files))
|
||||
}
|
||||
|
||||
# tests/ dir
|
||||
if ("shinytest" %in% examples) {
|
||||
copy_test_dir("shinytest")
|
||||
}
|
||||
if ("testthat" %in% examples) {
|
||||
copy_test_dir("testthat")
|
||||
}
|
||||
invisible()
|
||||
}
|
||||
@@ -217,6 +217,22 @@ RestoreContext <- R6Class("RestoreContext",
|
||||
self$dir <- NULL
|
||||
},
|
||||
|
||||
# Completely replace the state
|
||||
set = function(active = FALSE, initErrorMessage = NULL, input = list(), values = list(), dir = NULL) {
|
||||
# Validate all inputs
|
||||
stopifnot(is.logical(active))
|
||||
stopifnot(is.null(initErrorMessage) || is.character(initErrorMessage))
|
||||
stopifnot(is.list(input))
|
||||
stopifnot(is.list(values))
|
||||
stopifnot(is.null(dir) || is.character(dir))
|
||||
|
||||
self$active <- active
|
||||
self$initErrorMessage <- initErrorMessage
|
||||
self$input <- RestoreInputSet$new(input)
|
||||
self$values <- list2env2(values, parent = emptyenv())
|
||||
self$dir <- dir
|
||||
},
|
||||
|
||||
# This should be called before a restore context is popped off the stack.
|
||||
flushPending = function() {
|
||||
self$input$flushPending()
|
||||
@@ -426,7 +442,7 @@ RestoreInputSet <- R6Class("RestoreInputSet",
|
||||
},
|
||||
|
||||
asList = function() {
|
||||
as.list.environment(private$values)
|
||||
as.list.environment(private$values, all.names = TRUE)
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -448,7 +464,13 @@ withRestoreContext <- function(ctx, expr) {
|
||||
|
||||
# Is there a current restore context?
|
||||
hasCurrentRestoreContext <- function() {
|
||||
restoreCtxStack$size() > 0
|
||||
if (restoreCtxStack$size() > 0)
|
||||
return(TRUE)
|
||||
domain <- getDefaultReactiveDomain()
|
||||
if (!is.null(domain) && !is.null(domain$restoreContext))
|
||||
return(TRUE)
|
||||
|
||||
return(FALSE)
|
||||
}
|
||||
|
||||
# Call to access the current restore context. First look on the restore
|
||||
@@ -473,7 +495,7 @@ getCurrentRestoreContext <- function() {
|
||||
#' Restore an input value
|
||||
#'
|
||||
#' This restores an input value from the current restore context. It should be
|
||||
#' called early on inside of input functions (like \code{\link{textInput}}).
|
||||
#' called early on inside of input functions (like [textInput()]).
|
||||
#'
|
||||
#' @param id Name of the input value to restore.
|
||||
#' @param default A default value to use, if there's no value to restore.
|
||||
@@ -503,23 +525,23 @@ restoreInput <- function(id, default) {
|
||||
#' It typically is called from an observer. Note that this will not work in
|
||||
#' Internet Explorer 9 and below.
|
||||
#'
|
||||
#' For \code{mode = "push"}, only three updates are currently allowed:
|
||||
#' For `mode = "push"`, only three updates are currently allowed:
|
||||
#' \enumerate{
|
||||
#' \item the query string (format: \code{?param1=val1¶m2=val2})
|
||||
#' \item the hash (format: \code{#hash})
|
||||
#' \item the query string (format: `?param1=val1¶m2=val2`)
|
||||
#' \item the hash (format: `#hash`)
|
||||
#' \item both the query string and the hash
|
||||
#' (format: \code{?param1=val1¶m2=val2#hash})
|
||||
#' (format: `?param1=val1¶m2=val2#hash`)
|
||||
#' }
|
||||
#'
|
||||
#' In other words, if \code{mode = "push"}, the \code{queryString} must start
|
||||
#' with either \code{?} or with \code{#}.
|
||||
#' In other words, if `mode = "push"`, the `queryString` must start
|
||||
#' with either `?` or with `#`.
|
||||
#'
|
||||
#' A technical curiosity: under the hood, this function is calling the HTML5
|
||||
#' history API (which is where the names for the \code{mode} argument come from).
|
||||
#' When \code{mode = "replace"}, the function called is
|
||||
#' \code{window.history.replaceState(null, null, queryString)}.
|
||||
#' When \code{mode = "push"}, the function called is
|
||||
#' \code{window.history.pushState(null, null, queryString)}.
|
||||
#' history API (which is where the names for the `mode` argument come from).
|
||||
#' When `mode = "replace"`, the function called is
|
||||
#' `window.history.replaceState(null, null, queryString)`.
|
||||
#' When `mode = "push"`, the function called is
|
||||
#' `window.history.pushState(null, null, queryString)`.
|
||||
#'
|
||||
#' @param queryString The new query string to show in the location bar.
|
||||
#' @param mode When the query string is updated, should the the current history
|
||||
@@ -528,7 +550,7 @@ restoreInput <- function(id, default) {
|
||||
#' context. The latter is useful if you want to navigate between states using
|
||||
#' the browser's back and forward buttons. See Examples.
|
||||
#' @param session A Shiny session object.
|
||||
#' @seealso \code{\link{enableBookmarking}}, \code{\link{getQueryString}}
|
||||
#' @seealso [enableBookmarking()], [getQueryString()]
|
||||
#' @examples
|
||||
#' ## Only run these examples in interactive sessions
|
||||
#' if (interactive()) {
|
||||
@@ -591,7 +613,7 @@ updateQueryString <- function(queryString, mode = c("replace", "push"),
|
||||
|
||||
#' Create a button for bookmarking/sharing
|
||||
#'
|
||||
#' A \code{bookmarkButton} is a \code{\link{actionButton}} with a default label
|
||||
#' A `bookmarkButton` is a [actionButton()] with a default label
|
||||
#' that consists of a link icon and the text "Bookmark...". It is meant to be
|
||||
#' used for bookmarking state.
|
||||
#'
|
||||
@@ -601,10 +623,10 @@ updateQueryString <- function(queryString, mode = c("replace", "push"),
|
||||
#' @param id An ID for the bookmark button. The only time it is necessary to set
|
||||
#' the ID unless you have more than one bookmark button in your application.
|
||||
#' If you specify an input ID, it should be excluded from bookmarking with
|
||||
#' \code{\link{setBookmarkExclude}}, and you must create an observer that
|
||||
#' [setBookmarkExclude()], and you must create an observer that
|
||||
#' does the bookmarking when the button is pressed. See the examples below.
|
||||
#'
|
||||
#' @seealso \code{\link{enableBookmarking}} for more examples.
|
||||
#' @seealso [enableBookmarking()] for more examples.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run these examples in interactive sessions
|
||||
@@ -654,10 +676,10 @@ bookmarkButton <- function(label = "Bookmark...",
|
||||
|
||||
#' Generate a modal dialog that displays a URL
|
||||
#'
|
||||
#' The modal dialog generated by \code{urlModal} will display the URL in a
|
||||
#' The modal dialog generated by `urlModal` will display the URL in a
|
||||
#' textarea input, and the URL text will be selected so that it can be easily
|
||||
#' copied. The result from \code{urlModal} should be passed to the
|
||||
#' \code{\link{showModal}} function to display it in the browser.
|
||||
#' copied. The result from `urlModal` should be passed to the
|
||||
#' [showModal()] function to display it in the browser.
|
||||
#'
|
||||
#' @param url A URL to display in the dialog box.
|
||||
#' @param title A title for the dialog box.
|
||||
@@ -713,8 +735,8 @@ urlModal <- function(url, title = "Bookmarked application link", subtitle = NULL
|
||||
|
||||
#' Display a modal dialog for bookmarking
|
||||
#'
|
||||
#' This is a wrapper function for \code{\link{urlModal}} that is automatically
|
||||
#' called if an application is bookmarked but no other \code{\link{onBookmark}}
|
||||
#' This is a wrapper function for [urlModal()] that is automatically
|
||||
#' called if an application is bookmarked but no other [onBookmark()]
|
||||
#' callback was set. It displays a modal dialog with the bookmark URL, along
|
||||
#' with a subtitle that is appropriate for the type of bookmarking used ("url"
|
||||
#' or "server").
|
||||
@@ -755,8 +777,8 @@ showBookmarkUrlModal <- function(url) {
|
||||
#' @details
|
||||
#'
|
||||
#' For restoring state to work properly, the UI must be a function that takes
|
||||
#' one argument, \code{request}. In most Shiny applications, the UI is not a
|
||||
#' function; it might have the form \code{fluidPage(....)}. Converting it to a
|
||||
#' one argument, `request`. In most Shiny applications, the UI is not a
|
||||
#' function; it might have the form `fluidPage(....)`. Converting it to a
|
||||
#' function is as simple as wrapping it in a function, as in
|
||||
#' \code{function(request) \{ fluidPage(....) \}}.
|
||||
#'
|
||||
@@ -765,17 +787,17 @@ showBookmarkUrlModal <- function(url) {
|
||||
#' but not if the state is encoded in a URL.
|
||||
#'
|
||||
#' When bookmarking state, arbitrary values can be stored, by passing a function
|
||||
#' as the \code{onBookmark} argument. That function will be passed a
|
||||
#' \code{ShinySaveState} object. The \code{values} field of the object is a list
|
||||
#' as the `onBookmark` argument. That function will be passed a
|
||||
#' `ShinySaveState` object. The `values` field of the object is a list
|
||||
#' which can be manipulated to save extra information. Additionally, if the
|
||||
#' state is being saved on the server, and the \code{dir} field of that object
|
||||
#' state is being saved on the server, and the `dir` field of that object
|
||||
#' can be used to save extra information to files in that directory.
|
||||
#'
|
||||
#' For saved-to-server state, this is how the state directory is chosen:
|
||||
#' \itemize{
|
||||
#' \item If running in a hosting environment such as Shiny Server or
|
||||
#' Connect, the hosting environment will choose the directory.
|
||||
#' \item If running an app in a directory with \code{\link{runApp}()}, the
|
||||
#' \item If running an app in a directory with [runApp()], the
|
||||
#' saved states will be saved in a subdirectory of the app called
|
||||
#' shiny_bookmarks.
|
||||
#' \item If running a Shiny app object that is generated from code (not run
|
||||
@@ -783,22 +805,22 @@ showBookmarkUrlModal <- function(url) {
|
||||
#' the current working directory called shiny_bookmarks.
|
||||
#' }
|
||||
#'
|
||||
#' When used with \code{\link{shinyApp}()}, this function must be called before
|
||||
#' \code{shinyApp()}, or in the \code{shinyApp()}'s \code{onStart} function. An
|
||||
#' alternative to calling the \code{enableBookmarking()} function is to use the
|
||||
#' \code{enableBookmarking} \emph{argument} for \code{shinyApp()}. See examples
|
||||
#' When used with [shinyApp()], this function must be called before
|
||||
#' `shinyApp()`, or in the `shinyApp()`'s `onStart` function. An
|
||||
#' alternative to calling the `enableBookmarking()` function is to use the
|
||||
#' `enableBookmarking` *argument* for `shinyApp()`. See examples
|
||||
#' below.
|
||||
#'
|
||||
#' @param store Either \code{"url"}, which encodes all of the relevant values in
|
||||
#' a URL, \code{"server"}, which saves to disk on the server, or
|
||||
#' \code{"disable"}, which disables any previously-enabled bookmarking.
|
||||
#' @param store Either `"url"`, which encodes all of the relevant values in
|
||||
#' a URL, `"server"`, which saves to disk on the server, or
|
||||
#' `"disable"`, which disables any previously-enabled bookmarking.
|
||||
#'
|
||||
#' @seealso \code{\link{onBookmark}}, \code{\link{onBookmarked}},
|
||||
#' \code{\link{onRestore}}, and \code{\link{onRestored}} for registering
|
||||
#' @seealso [onBookmark()], [onBookmarked()],
|
||||
#' [onRestore()], and [onRestored()] for registering
|
||||
#' callback functions that are invoked when the state is bookmarked or
|
||||
#' restored.
|
||||
#'
|
||||
#' Also see \code{\link{updateQueryString}}.
|
||||
#' Also see [updateQueryString()].
|
||||
#'
|
||||
#' @export
|
||||
#' @examples
|
||||
@@ -977,7 +999,7 @@ enableBookmarking <- function(store = c("url", "server", "disable")) {
|
||||
#' @param names A character vector containing names of inputs to exclude from
|
||||
#' bookmarking.
|
||||
#' @param session A shiny session object.
|
||||
#' @seealso \code{\link{enableBookmarking}} for examples.
|
||||
#' @seealso [enableBookmarking()] for examples.
|
||||
#' @export
|
||||
setBookmarkExclude <- function(names = character(0), session = getDefaultReactiveDomain()) {
|
||||
session$setBookmarkExclude(names)
|
||||
@@ -992,17 +1014,17 @@ setBookmarkExclude <- function(names = character(0), session = getDefaultReactiv
|
||||
#' should be called within an application's server function.
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \item \code{onBookmark} registers a function that will be called just
|
||||
#' \item `onBookmark` registers a function that will be called just
|
||||
#' before Shiny bookmarks state.
|
||||
#' \item \code{onBookmarked} registers a function that will be called just
|
||||
#' \item `onBookmarked` registers a function that will be called just
|
||||
#' after Shiny bookmarks state.
|
||||
#' \item \code{onRestore} registers a function that will be called when a
|
||||
#' \item `onRestore` registers a function that will be called when a
|
||||
#' session is restored, after the server function executes, but before all
|
||||
#' other reactives, observers and render functions are run.
|
||||
#' \item \code{onRestored} registers a function that will be called after a
|
||||
#' session is restored. This is similar to \code{onRestore}, but it will be
|
||||
#' \item `onRestored` registers a function that will be called after a
|
||||
#' session is restored. This is similar to `onRestore`, but it will be
|
||||
#' called after all reactives, observers, and render functions run, and
|
||||
#' after results are sent to the client browser. \code{onRestored}
|
||||
#' after results are sent to the client browser. `onRestored`
|
||||
#' callbacks can be useful for sending update messages to the client
|
||||
#' browser.
|
||||
#' }
|
||||
@@ -1013,25 +1035,25 @@ setBookmarkExclude <- function(names = character(0), session = getDefaultReactiv
|
||||
#' arguments to cancel the registration.
|
||||
#'
|
||||
#' The callback function that is passed to these functions should take one
|
||||
#' argument, typically named "state" (for \code{onBookmark}, \code{onRestore},
|
||||
#' and \code{onRestored}) or "url" (for \code{onBookmarked}).
|
||||
#' argument, typically named "state" (for `onBookmark`, `onRestore`,
|
||||
#' and `onRestored`) or "url" (for `onBookmarked`).
|
||||
#'
|
||||
#' For \code{onBookmark}, the state object has three relevant fields. The
|
||||
#' \code{values} field is an environment which can be used to save arbitrary
|
||||
#' For `onBookmark`, the state object has three relevant fields. The
|
||||
#' `values` field is an environment which can be used to save arbitrary
|
||||
#' values (see examples). If the state is being saved to disk (as opposed to
|
||||
#' being encoded in a URL), the \code{dir} field contains the name of a
|
||||
#' being encoded in a URL), the `dir` field contains the name of a
|
||||
#' directory which can be used to store extra files. Finally, the state object
|
||||
#' has an \code{input} field, which is simply the application's \code{input}
|
||||
#' has an `input` field, which is simply the application's `input`
|
||||
#' object. It can be read, but not modified.
|
||||
#'
|
||||
#' For \code{onRestore} and \code{onRestored}, the state object is a list. This
|
||||
#' list contains \code{input}, which is a named list of input values to restore,
|
||||
#' \code{values}, which is an environment containing arbitrary values that were
|
||||
#' saved in \code{onBookmark}, and \code{dir}, the name of the directory that
|
||||
#' For `onRestore` and `onRestored`, the state object is a list. This
|
||||
#' list contains `input`, which is a named list of input values to restore,
|
||||
#' `values`, which is an environment containing arbitrary values that were
|
||||
#' saved in `onBookmark`, and `dir`, the name of the directory that
|
||||
#' the state is being restored from, and which could have been used to save
|
||||
#' extra files.
|
||||
#'
|
||||
#' For \code{onBookmarked}, the callback function receives a string with the
|
||||
#' For `onBookmarked`, the callback function receives a string with the
|
||||
#' bookmark URL. This callback function should be used to display UI in the
|
||||
#' client browser with the bookmark URL. If no callback function is registered,
|
||||
#' then Shiny will by default display a modal dialog with the bookmark URL.
|
||||
|
||||
47
R/bootstrap-deprecated.R
Normal file
47
R/bootstrap-deprecated.R
Normal file
@@ -0,0 +1,47 @@
|
||||
#' Create a page with a sidebar
|
||||
#'
|
||||
#' **DEPRECATED**: use [fluidPage()] and [sidebarLayout()] instead.
|
||||
#'
|
||||
#' @param headerPanel The [headerPanel] with the application title
|
||||
#' @param sidebarPanel The [sidebarPanel] containing input controls
|
||||
#' @param mainPanel The [mainPanel] containing outputs
|
||||
#' @keywords internal
|
||||
#' @return A UI defintion that can be passed to the [shinyUI] function
|
||||
#' @export
|
||||
pageWithSidebar <- function(headerPanel,
|
||||
sidebarPanel,
|
||||
mainPanel) {
|
||||
|
||||
bootstrapPage(
|
||||
# basic application container divs
|
||||
div(
|
||||
class="container-fluid",
|
||||
div(class="row",
|
||||
headerPanel
|
||||
),
|
||||
div(class="row",
|
||||
sidebarPanel,
|
||||
mainPanel
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#' Create a header panel
|
||||
#'
|
||||
#' **DEPRECATED**: use [titlePanel()] instead.
|
||||
#'
|
||||
#' @param title An application title to display
|
||||
#' @param windowTitle The title that should be displayed by the browser window.
|
||||
#' Useful if `title` is not a string.
|
||||
#' @return A headerPanel that can be passed to [pageWithSidebar]
|
||||
#' @keywords internal
|
||||
#' @export
|
||||
headerPanel <- function(title, windowTitle=title) {
|
||||
tagList(
|
||||
tags$head(tags$title(windowTitle)),
|
||||
div(class="col-sm-12",
|
||||
h1(title)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -10,25 +10,27 @@
|
||||
#'
|
||||
#' @param ... Elements to include within the page
|
||||
#' @param title The browser window title (defaults to the host URL of the page).
|
||||
#' Can also be set as a side effect of the \code{\link{titlePanel}} function.
|
||||
#' Can also be set as a side effect of the [titlePanel()] function.
|
||||
#' @param responsive This option is deprecated; it is no longer optional with
|
||||
#' Bootstrap 3.
|
||||
#' @param theme Alternative Bootstrap stylesheet (normally a css file within the
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' \code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.
|
||||
#' `www/bootstrap.css` you would use `theme = "bootstrap.css"`.
|
||||
#' @inheritParams bootstrapPage
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the \link{shinyUI} function.
|
||||
#' @return A UI defintion that can be passed to the [shinyUI] function.
|
||||
#'
|
||||
#' @details To create a fluid page use the \code{fluidPage} function and include
|
||||
#' instances of \code{fluidRow} and \code{\link{column}} within it. As an
|
||||
#' @details To create a fluid page use the `fluidPage` function and include
|
||||
#' instances of `fluidRow` and [column()] within it. As an
|
||||
#' alternative to low-level row and column functions you can also use
|
||||
#' higher-level layout functions like \code{\link{sidebarLayout}}.
|
||||
#' higher-level layout functions like [sidebarLayout()].
|
||||
#'
|
||||
#' @note See the \href{http://shiny.rstudio.com/articles/layout-guide.html}{
|
||||
#' Shiny-Application-Layout-Guide} for additional details on laying out fluid
|
||||
#' @note See the [
|
||||
#' Shiny-Application-Layout-Guide](http://shiny.rstudio.com/articles/layout-guide.html) for additional details on laying out fluid
|
||||
#' pages.
|
||||
#'
|
||||
#' @seealso \code{\link{column}}, \code{\link{sidebarLayout}}
|
||||
#' @family layout functions
|
||||
#' @seealso [column()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -86,11 +88,12 @@
|
||||
#' }
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
|
||||
fluidPage <- function(..., title = NULL, responsive = NULL, theme = NULL, lang = NULL) {
|
||||
bootstrapPage(div(class = "container-fluid", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
theme = theme)
|
||||
theme = theme,
|
||||
lang = lang)
|
||||
}
|
||||
|
||||
|
||||
@@ -116,21 +119,24 @@ fluidRow <- function(...) {
|
||||
#' Bootstrap 3.
|
||||
#' @param theme Alternative Bootstrap stylesheet (normally a css file within the
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' \code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.
|
||||
#' `www/bootstrap.css` you would use `theme = "bootstrap.css"`.
|
||||
#' @inheritParams bootstrapPage
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the \link{shinyUI} function.
|
||||
#' @return A UI defintion that can be passed to the [shinyUI] function.
|
||||
#'
|
||||
#' @details To create a fixed page use the \code{fixedPage} function and include
|
||||
#' instances of \code{fixedRow} and \code{\link{column}} within it. Note that
|
||||
#' unlike \code{\link{fluidPage}}, fixed pages cannot make use of higher-level
|
||||
#' layout functions like \code{sidebarLayout}, rather, all layout must be done
|
||||
#' with \code{fixedRow} and \code{column}.
|
||||
#' @details To create a fixed page use the `fixedPage` function and include
|
||||
#' instances of `fixedRow` and [column()] within it. Note that
|
||||
#' unlike [fluidPage()], fixed pages cannot make use of higher-level
|
||||
#' layout functions like `sidebarLayout`, rather, all layout must be done
|
||||
#' with `fixedRow` and `column`.
|
||||
#'
|
||||
#' @note See the \href{http://shiny.rstudio.com/articles/layout-guide.html}{
|
||||
#' Shiny Application Layout Guide} for additional details on laying out fixed
|
||||
#' @note See the [
|
||||
#' Shiny Application Layout Guide](http://shiny.rstudio.com/articles/layout-guide.html) for additional details on laying out fixed
|
||||
#' pages.
|
||||
#'
|
||||
#' @seealso \code{\link{column}}
|
||||
#' @family layout functions
|
||||
#'
|
||||
#' @seealso [column()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -153,11 +159,12 @@ fluidRow <- function(...) {
|
||||
#'
|
||||
#' @rdname fixedPage
|
||||
#' @export
|
||||
fixedPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
|
||||
fixedPage <- function(..., title = NULL, responsive = NULL, theme = NULL, lang = NULL) {
|
||||
bootstrapPage(div(class = "container", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
theme = theme)
|
||||
theme = theme,
|
||||
lang = lang)
|
||||
}
|
||||
|
||||
#' @rdname fixedPage
|
||||
@@ -169,8 +176,8 @@ fixedRow <- function(...) {
|
||||
|
||||
#' Create a column within a UI definition
|
||||
#'
|
||||
#' Create a column for use within a \code{\link{fluidRow}} or
|
||||
#' \code{\link{fixedRow}}
|
||||
#' Create a column for use within a [fluidRow()] or
|
||||
#' [fixedRow()]
|
||||
#'
|
||||
#' @param width The grid width of the column (must be between 1 and 12)
|
||||
#' @param ... Elements to include within the column
|
||||
@@ -178,10 +185,10 @@ fixedRow <- function(...) {
|
||||
#' previous column.
|
||||
#'
|
||||
#' @return A column that can be included within a
|
||||
#' \code{\link{fluidRow}} or \code{\link{fixedRow}}.
|
||||
#' [fluidRow()] or [fixedRow()].
|
||||
#'
|
||||
#'
|
||||
#' @seealso \code{\link{fluidRow}}, \code{\link{fixedRow}}.
|
||||
#' @seealso [fluidRow()], [fixedRow()].
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -228,8 +235,12 @@ column <- function(width, ..., offset = 0) {
|
||||
stop("column width must be between 1 and 12")
|
||||
|
||||
colClass <- paste0("col-sm-", width)
|
||||
if (offset > 0)
|
||||
colClass <- paste0(colClass, " col-sm-offset-", offset)
|
||||
if (offset > 0) {
|
||||
# offset-md-x is for bootstrap 4 forward compat
|
||||
# (every size tier has been bumped up one level)
|
||||
# https://github.com/twbs/bootstrap/blob/74b8fe7/docs/4.3/migration/index.html#L659
|
||||
colClass <- paste0(colClass, " offset-md-", offset, " col-sm-offset-", offset)
|
||||
}
|
||||
div(class = colClass, ...)
|
||||
}
|
||||
|
||||
@@ -240,10 +251,9 @@ column <- function(width, ..., offset = 0) {
|
||||
#' @param windowTitle The title that should be displayed by the browser window.
|
||||
#'
|
||||
#' @details Calling this function has the side effect of including a
|
||||
#' \code{title} tag within the head. You can also specify a page title
|
||||
#' `title` tag within the head. You can also specify a page title
|
||||
#' explicitly using the `title` parameter of the top-level page function.
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
@@ -263,16 +273,23 @@ titlePanel <- function(title, windowTitle=title) {
|
||||
|
||||
#' Layout a sidebar and main area
|
||||
#'
|
||||
#' Create a layout with a sidebar and main area. The sidebar is displayed with a
|
||||
#' distinct background color and typically contains input controls. The main
|
||||
#' Create a layout (`sidebarLayout()`) with a sidebar (`sidebarPanel()`) and
|
||||
#' main area (`mainPanel()`). The sidebar is displayed with a distinct
|
||||
#' background color and typically contains input controls. The main
|
||||
#' area occupies 2/3 of the horizontal width and typically contains outputs.
|
||||
#'
|
||||
#' @param sidebarPanel The \link{sidebarPanel} containing input controls
|
||||
#' @param mainPanel The \link{mainPanel} containing outputs
|
||||
#' @param sidebarPanel The `sidebarPanel()` containing input controls.
|
||||
#' @param mainPanel The `mainPanel()` containing outputs.
|
||||
#' @param position The position of the sidebar relative to the main area ("left"
|
||||
#' or "right")
|
||||
#' @param fluid \code{TRUE} to use fluid layout; \code{FALSE} to use fixed
|
||||
#' or "right").
|
||||
#' @param fluid `TRUE` to use fluid layout; `FALSE` to use fixed
|
||||
#' layout.
|
||||
#' @param width The width of the sidebar and main panel. By default, the
|
||||
#' sidebar takes up 1/3 of the width, and the main panel 2/3. The total
|
||||
#' width must be 12 or less.
|
||||
#' @param ... Output elements to include in the sidebar/main panel.
|
||||
#'
|
||||
#' @family layout functions
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -337,16 +354,38 @@ sidebarLayout <- function(sidebarPanel,
|
||||
fixedRow(firstPanel, secondPanel)
|
||||
}
|
||||
|
||||
#' @export
|
||||
#' @rdname sidebarLayout
|
||||
sidebarPanel <- function(..., width = 4) {
|
||||
div(class=paste0("col-sm-", width),
|
||||
tags$form(class="well",
|
||||
# A11y semantic landmark for sidebar
|
||||
role="complementary",
|
||||
...
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#' @export
|
||||
#' @rdname sidebarLayout
|
||||
mainPanel <- function(..., width = 8) {
|
||||
div(class=paste0("col-sm-", width),
|
||||
# A11y semantic landmark for main region
|
||||
role="main",
|
||||
...
|
||||
)
|
||||
}
|
||||
|
||||
#' Lay out UI elements vertically
|
||||
#'
|
||||
#' Create a container that includes one or more rows of content (each element
|
||||
#' passed to the container will appear on it's own line in the UI)
|
||||
#'
|
||||
#' @param ... Elements to include within the container
|
||||
#' @param fluid \code{TRUE} to use fluid layout; \code{FALSE} to use fixed
|
||||
#' @param fluid `TRUE` to use fluid layout; `FALSE` to use fixed
|
||||
#' layout.
|
||||
#'
|
||||
#' @seealso \code{\link{fluidPage}}, \code{\link{flowLayout}}
|
||||
#' @family layout functions
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -377,14 +416,14 @@ verticalLayout <- function(..., fluid = TRUE) {
|
||||
#' Lays out elements in a left-to-right, top-to-bottom arrangement. The elements
|
||||
#' on a given row will be top-aligned with each other. This layout will not work
|
||||
#' well with elements that have a percentage-based width (e.g.
|
||||
#' \code{\link{plotOutput}} at its default setting of \code{width = "100\%"}).
|
||||
#' [plotOutput()] at its default setting of `width = "100%"`).
|
||||
#'
|
||||
#' @param ... Unnamed arguments will become child elements of the layout. Named
|
||||
#' arguments will become HTML attributes on the outermost tag.
|
||||
#' @param cellArgs Any additional attributes that should be used for each cell
|
||||
#' of the layout.
|
||||
#'
|
||||
#' @seealso \code{\link{verticalLayout}}
|
||||
#' @family layout functions
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -415,7 +454,7 @@ flowLayout <- function(..., cellArgs = list()) {
|
||||
|
||||
#' Input panel
|
||||
#'
|
||||
#' A \code{\link{flowLayout}} with a grey border and light grey background,
|
||||
#' A [flowLayout()] with a grey border and light grey background,
|
||||
#' suitable for wrapping inputs.
|
||||
#'
|
||||
#' @param ... Input controls or other HTML elements.
|
||||
@@ -435,11 +474,13 @@ inputPanel <- function(...) {
|
||||
#' arguments will become HTML attributes on the outermost tag.
|
||||
#' @param cellWidths Character or numeric vector indicating the widths of the
|
||||
#' individual cells. Recycling will be used if needed. Character values will
|
||||
#' be interpreted as CSS lengths (see \code{\link{validateCssUnit}}), numeric
|
||||
#' be interpreted as CSS lengths (see [validateCssUnit()]), numeric
|
||||
#' values as pixels.
|
||||
#' @param cellArgs Any additional attributes that should be used for each cell
|
||||
#' of the layout.
|
||||
#'
|
||||
#' @family layout functions
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
@@ -509,41 +550,41 @@ splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) {
|
||||
#'
|
||||
#' 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
|
||||
#' proportional-grid layouts. **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
|
||||
#' @details If you try to use `fillRow` and `fillCol` inside of other
|
||||
#' Shiny containers, such as [sidebarLayout()],
|
||||
#' [navbarPage()], or even `tags$div`, you will probably find
|
||||
#' that they will not appear. This is due to `fillRow` and `fillCol`
|
||||
#' defaulting to `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}
|
||||
#' \item only use `fillRow`/`fillCol` inside of `fillPage`,
|
||||
#' `fillRow`, or `fillCol`
|
||||
#' \item provide an explicit `height` argument to
|
||||
#' `fillRow`/`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
|
||||
#' [tagList()] or [div()] to combine them.) Named
|
||||
#' arguments will be used as attributes on the `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
|
||||
#' single value like `1` or `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
|
||||
#' `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
|
||||
#' entire row/column. For the default height of `"100%"` to be
|
||||
#' effective, the parent must be `fillPage`, another
|
||||
#' `fillRow`/`fillCol`, or some other HTML element whose height is
|
||||
#' not determined by the height of its contents.
|
||||
#'
|
||||
#' @examples
|
||||
|
||||
858
R/bootstrap.R
858
R/bootstrap.R
File diff suppressed because it is too large
Load Diff
@@ -1,77 +0,0 @@
|
||||
# A context object for tracking a cache that needs to be dirtied when a set of
|
||||
# files changes on disk. Each time the cache is dirtied, the set of files is
|
||||
# cleared. Therefore, the set of files needs to be re-built each time the cached
|
||||
# code executes. This approach allows for dynamic dependency graphs.
|
||||
CacheContext <- R6Class(
|
||||
'CacheContext',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.dirty = TRUE,
|
||||
# List of functions that return TRUE if dirty
|
||||
.tests = list(),
|
||||
|
||||
addDependencyFile = function(file) {
|
||||
if (.dirty)
|
||||
return()
|
||||
|
||||
file <- normalizePath(file)
|
||||
|
||||
mtime <- file.info(file)$mtime
|
||||
.tests <<- c(.tests, function() {
|
||||
newMtime <- try(file.info(file)$mtime, silent=TRUE)
|
||||
if (inherits(newMtime, 'try-error'))
|
||||
return(TRUE)
|
||||
return(!identical(mtime, newMtime))
|
||||
})
|
||||
invisible()
|
||||
},
|
||||
forceDirty = function() {
|
||||
.dirty <<- TRUE
|
||||
.tests <<- list()
|
||||
invisible()
|
||||
},
|
||||
isDirty = function() {
|
||||
if (.dirty)
|
||||
return(TRUE)
|
||||
|
||||
for (test in .tests) {
|
||||
if (test()) {
|
||||
forceDirty()
|
||||
return(TRUE)
|
||||
}
|
||||
}
|
||||
|
||||
return(FALSE)
|
||||
},
|
||||
reset = function() {
|
||||
.dirty <<- FALSE
|
||||
.tests <<- list()
|
||||
},
|
||||
with = function(func) {
|
||||
oldCC <- .currentCacheContext$cc
|
||||
.currentCacheContext$cc <- self
|
||||
on.exit(.currentCacheContext$cc <- oldCC)
|
||||
|
||||
return(func())
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
.currentCacheContext <- new.env()
|
||||
|
||||
# Indicates to Shiny that the given file path is part of the dependency graph
|
||||
# for whatever is currently executing (so far, only ui.R). By default, ui.R only
|
||||
# gets re-executed when it is detected to have changed; this function allows the
|
||||
# caller to indicate that it should also re-execute if the given file changes.
|
||||
#
|
||||
# If NULL or NA is given as the argument, then ui.R will re-execute next time.
|
||||
dependsOnFile <- function(filepath) {
|
||||
if (is.null(.currentCacheContext$cc))
|
||||
return()
|
||||
|
||||
if (is.null(filepath) || is.na(filepath))
|
||||
.currentCacheContext$cc$forceDirty()
|
||||
else
|
||||
.currentCacheContext$cc$addDependencyFile(filepath)
|
||||
}
|
||||
180
R/cache-disk.R
180
R/cache-disk.R
@@ -1,44 +1,44 @@
|
||||
#' 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}.
|
||||
#' directory on disk. Objects can be stored and retrieved using the `get()`
|
||||
#' and `set()` methods. Objects are automatically pruned from the cache
|
||||
#' according to the parameters `max_size`, `max_age`, `max_n`,
|
||||
#' and `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
|
||||
#' The `missing` and `exec_missing` parameters controls what happens
|
||||
#' when `get()` is called with a key that is not in the cache (a cache
|
||||
#' miss). The default behavior is to return a [key_missing()]
|
||||
#' object. This is a *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}.
|
||||
#' missing key by using the [is.key_missing()] function. You can
|
||||
#' also have `get()` return a different sentinel value, like `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}.
|
||||
#' function for `missing` that takes one argument, the key, and also use
|
||||
#' `exec_missing=TRUE`.
|
||||
#'
|
||||
#' When the cache is created, you can supply a value for \code{missing}, which
|
||||
#' When the cache is created, you can supply a value for `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.
|
||||
#' overridden when `get()` is called, by supplying a `missing`
|
||||
#' argument. For example, if you use `cache$get("mykey", missing =
|
||||
#' NULL)`, it will return `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
|
||||
#' If your cache is configured so that `get()` returns a sentinel value
|
||||
#' to represent a cache miss, then `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
|
||||
#' the cache can execute a function each time `get()` encounters missing
|
||||
#' key. If the function returns a value, then `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.
|
||||
#' error. If an error is thrown, then `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
|
||||
#' To do this, pass a one-argument function to `missing`, and use
|
||||
#' `exec_missing=TRUE`. For example, if you want to throw an error that
|
||||
#' prints the missing key, you could do this:
|
||||
#'
|
||||
#' \preformatted{
|
||||
@@ -50,58 +50,58 @@
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
#' If you use this, the code that calls \code{get()} should be wrapped with
|
||||
#' \code{\link{tryCatch}()} to gracefully handle missing keys.
|
||||
#' If you use this, the code that calls `get()` should be wrapped with
|
||||
#' [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()}.
|
||||
#' Cache pruning occurs when `set()` is called, or it can be invoked
|
||||
#' manually by calling `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
|
||||
#' every call to `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
|
||||
#' calls to `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.
|
||||
#' `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
|
||||
#' The `max_size` and `max_n` parameters are applied to the cache as
|
||||
#' a whole, in contrast to `max_age`, which is applied to each object
|
||||
#' individually.
|
||||
#'
|
||||
#' If the number of objects in the cache exceeds \code{max_n}, then objects
|
||||
#' If the number of objects in the cache exceeds `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}.
|
||||
#' set with the `evict` parameter. Objects will be removed so that the
|
||||
#' number of items is `max_n`.
|
||||
#'
|
||||
#' If the size of the objects in the cache exceeds \code{max_size}, then
|
||||
#' If the size of the objects in the cache exceeds `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
|
||||
#' cache so that the total size remains under `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
|
||||
#' 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},
|
||||
#' `get()` is called. If the target object is older than `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
|
||||
#' If `max_n` or `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"}}{
|
||||
#' \item{`"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.
|
||||
#' `get()` is called, it will update the file's mtime.
|
||||
#' }
|
||||
#' \item{\code{"fifo"}}{
|
||||
#' \item{`"fifo"`}{
|
||||
#' First-in-first-out. The oldest objects will be removed.
|
||||
#' }
|
||||
#' }
|
||||
@@ -126,17 +126,17 @@
|
||||
#' 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)}
|
||||
#' race conditions. For example, if your code calls `exists(key)` to check
|
||||
#' if an object is in the cache, and then call `get(key)`, the object may
|
||||
#' be removed from the cache in between those two calls, and `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
|
||||
#' simply call `get(key)`, and check that the returned object is not a
|
||||
#' `key_missing()` object, using `is.key_missing()`. 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
|
||||
#' a warning from `file.remove()` failing to remove a file that has
|
||||
#' already been deleted.
|
||||
#'
|
||||
#'
|
||||
@@ -145,72 +145,72 @@
|
||||
#' 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
|
||||
#' \item{`get(key, missing, exec_missing)`}{
|
||||
#' Returns the value associated with `key`. If the key is not in the
|
||||
#' cache, then it returns the value specified by `missing` or,
|
||||
#' `missing` is a function and `exec_missing=TRUE`, then
|
||||
#' executes `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{`set(key, value)`}{
|
||||
#' Stores the `key`-`value` pair in the cache.
|
||||
#' }
|
||||
#' \item{\code{exists(key)}}{
|
||||
#' Returns \code{TRUE} if the cache contains the key, otherwise
|
||||
#' \code{FALSE}.
|
||||
#' \item{`exists(key)`}{
|
||||
#' Returns `TRUE` if the cache contains the key, otherwise
|
||||
#' `FALSE`.
|
||||
#' }
|
||||
#' \item{\code{size()}}{
|
||||
#' \item{`size()`}{
|
||||
#' Returns the number of items currently in the cache.
|
||||
#' }
|
||||
#' \item{\code{keys()}}{
|
||||
#' \item{`keys()`}{
|
||||
#' Returns a character vector of all keys currently in the cache.
|
||||
#' }
|
||||
#' \item{\code{reset()}}{
|
||||
#' \item{`reset()`}{
|
||||
#' Clears all objects from the cache.
|
||||
#' }
|
||||
#' \item{\code{destroy()}}{
|
||||
#' \item{`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}.
|
||||
#' \item{`prune()`}{
|
||||
#' Prunes the cache, using the parameters specified by `max_size`,
|
||||
#' `max_age`, `max_n`, and `evict`.
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#' @param dir Directory to store files for the cache. If \code{NULL} (the
|
||||
#' @param dir Directory to store files for the cache. If `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.
|
||||
#' seconds. Use `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.
|
||||
#' `evict`. Use `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.
|
||||
#' value of `evict`. Use `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
|
||||
#' when a cache pruning occurs. Currently, `"lru"` and `"fifo"` are
|
||||
#' supported.
|
||||
#' @param destroy_on_finalize If \code{TRUE}, then when the DiskCache object is
|
||||
#' @param destroy_on_finalize If `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
|
||||
#' deleted from disk. If `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
|
||||
#' `get(key)` is called but the key is not present in the cache. The
|
||||
#' default is a [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
|
||||
#' use `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.
|
||||
#' `get()`. See section Missing keys for more information.
|
||||
#' @param exec_missing If `FALSE` (the default), then treat `missing`
|
||||
#' as a value to return when `get()` results in a cache miss. If
|
||||
#' `TRUE`, treat `missing` as a function to execute when
|
||||
#' `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()}.
|
||||
#' information will be written. To log to the console, use `stdout()`.
|
||||
#'
|
||||
#' @export
|
||||
diskCache <- function(
|
||||
@@ -387,7 +387,7 @@ DiskCache <- R6Class("DiskCache",
|
||||
# is because it is expensive to find the size of the serialized object
|
||||
# before adding it.
|
||||
|
||||
private$log(paste0('prune'))
|
||||
private$log('prune')
|
||||
self$is_destroyed(throw = TRUE)
|
||||
|
||||
current_time <- Sys.time()
|
||||
@@ -407,7 +407,11 @@ DiskCache <- R6Class("DiskCache",
|
||||
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])
|
||||
rm_success <- file.remove(info$name[rm_idx])
|
||||
# This maps rm_success back into the TRUEs in the rm_idx vector.
|
||||
# If (for example) rm_idx is c(F,T,F,T,T) and rm_success is c(T,F,T),
|
||||
# then this line modifies rm_idx to be c(F,T,F,F,T).
|
||||
rm_idx[rm_idx] <- rm_success
|
||||
info <- info[!rm_idx, ]
|
||||
}
|
||||
}
|
||||
@@ -428,7 +432,8 @@ DiskCache <- R6Class("DiskCache",
|
||||
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, ]
|
||||
rm_idx[rm_idx] <- rm_success
|
||||
info <- info[!rm_idx, ]
|
||||
}
|
||||
|
||||
# 3. Remove files if cache is too large.
|
||||
@@ -438,7 +443,8 @@ DiskCache <- R6Class("DiskCache",
|
||||
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, ]
|
||||
rm_idx[rm_idx] <- rm_success
|
||||
info <- info[!rm_idx, ]
|
||||
}
|
||||
|
||||
private$prune_last_time <- as.numeric(current_time)
|
||||
@@ -555,7 +561,7 @@ DiskCache <- R6Class("DiskCache",
|
||||
if (is.null(private$logfile)) return()
|
||||
|
||||
text <- paste0(format(Sys.time(), "[%Y-%m-%d %H:%M:%OS3] DiskCache "), text)
|
||||
writeLines(text, private$logfile)
|
||||
cat(text, sep = "\n", file = private$logfile, append = TRUE)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
157
R/cache-memory.R
157
R/cache-memory.R
@@ -1,50 +1,50 @@
|
||||
#' 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}.
|
||||
#' environment. Objects can be stored and retrieved using the `get()` and
|
||||
#' `set()` methods. Objects are automatically pruned from the cache
|
||||
#' according to the parameters `max_size`, `max_age`, `max_n`,
|
||||
#' and `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
|
||||
#' In a `MemoryCache`, R objects are stored directly in the cache; they are
|
||||
#' not *not* serialized before being stored in the cache. This contrasts
|
||||
#' with other cache types, like [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
|
||||
#' The `missing` and `exec_missing` parameters controls what happens
|
||||
#' when `get()` is called with a key that is not in the cache (a cache
|
||||
#' miss). The default behavior is to return a [key_missing()]
|
||||
#' object. This is a *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}.
|
||||
#' missing key by using the [is.key_missing()] function. You can
|
||||
#' also have `get()` return a different sentinel value, like `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}.
|
||||
#' function for `missing` that takes one argument, the key, and also use
|
||||
#' `exec_missing=TRUE`.
|
||||
#'
|
||||
#' When the cache is created, you can supply a value for \code{missing}, which
|
||||
#' When the cache is created, you can supply a value for `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.
|
||||
#' overridden when `get()` is called, by supplying a `missing`
|
||||
#' argument. For example, if you use `cache$get("mykey", missing =
|
||||
#' NULL)`, it will return `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
|
||||
#' If your cache is configured so that `get()` returns a sentinel value
|
||||
#' to represent a cache miss, then `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
|
||||
#' the cache can execute a function each time `get()` encounters missing
|
||||
#' key. If the function returns a value, then `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.
|
||||
#' error. If an error is thrown, then `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
|
||||
#' To do this, pass a one-argument function to `missing`, and use
|
||||
#' `exec_missing=TRUE`. For example, if you want to throw an error that
|
||||
#' prints the missing key, you could do this:
|
||||
#'
|
||||
#' \preformatted{
|
||||
@@ -56,53 +56,53 @@
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
#' If you use this, the code that calls \code{get()} should be wrapped with
|
||||
#' \code{\link{tryCatch}()} to gracefully handle missing keys.
|
||||
#' If you use this, the code that calls `get()` should be wrapped with
|
||||
#' [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()}.
|
||||
#' Cache pruning occurs when `set()` is called, or it can be invoked
|
||||
#' manually by calling `prune()`.
|
||||
#'
|
||||
#' When a pruning occurs, if there are any objects that are older than
|
||||
#' \code{max_age}, they will be removed.
|
||||
#' `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
|
||||
#' The `max_size` and `max_n` parameters are applied to the cache as
|
||||
#' a whole, in contrast to `max_age`, which is applied to each object
|
||||
#' individually.
|
||||
#'
|
||||
#' If the number of objects in the cache exceeds \code{max_n}, then objects
|
||||
#' If the number of objects in the cache exceeds `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}.
|
||||
#' set with the `evict` parameter. Objects will be removed so that the
|
||||
#' number of items is `max_n`.
|
||||
#'
|
||||
#' If the size of the objects in the cache exceeds \code{max_size}, then
|
||||
#' If the size of the objects in the cache exceeds `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
|
||||
#' cache so that the total size remains under `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
|
||||
#' 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},
|
||||
#' `get()` is called. If the target object is older than `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
|
||||
#' If `max_n` or `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"}}{
|
||||
#' \item{`"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"}}{
|
||||
#' \item{`"fifo"`}{
|
||||
#' First-in-first-out. The oldest objects will be removed.
|
||||
#' }
|
||||
#' }
|
||||
@@ -112,38 +112,38 @@
|
||||
#' 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
|
||||
#' \item{`get(key, missing, exec_missing)`}{
|
||||
#' Returns the value associated with `key`. If the key is not in the
|
||||
#' cache, then it returns the value specified by `missing` or,
|
||||
#' `missing` is a function and `exec_missing=TRUE`, then
|
||||
#' executes `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{`set(key, value)`}{
|
||||
#' Stores the `key`-`value` pair in the cache.
|
||||
#' }
|
||||
#' \item{\code{exists(key)}}{
|
||||
#' Returns \code{TRUE} if the cache contains the key, otherwise
|
||||
#' \code{FALSE}.
|
||||
#' \item{`exists(key)`}{
|
||||
#' Returns `TRUE` if the cache contains the key, otherwise
|
||||
#' `FALSE`.
|
||||
#' }
|
||||
#' \item{\code{size()}}{
|
||||
#' \item{`size()`}{
|
||||
#' Returns the number of items currently in the cache.
|
||||
#' }
|
||||
#' \item{\code{keys()}}{
|
||||
#' \item{`keys()`}{
|
||||
#' Returns a character vector of all keys currently in the cache.
|
||||
#' }
|
||||
#' \item{\code{reset()}}{
|
||||
#' \item{`reset()`}{
|
||||
#' Clears all objects from the cache.
|
||||
#' }
|
||||
#' \item{\code{destroy()}}{
|
||||
#' \item{`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}.
|
||||
#' \item{`prune()`}{
|
||||
#' Prunes the cache, using the parameters specified by `max_size`,
|
||||
#' `max_age`, `max_n`, and `evict`.
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
@@ -179,7 +179,7 @@ MemoryCache <- R6Class("MemoryCache",
|
||||
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$cache <- fastmap()
|
||||
private$max_size <- max_size
|
||||
private$max_age <- max_age
|
||||
private$max_n <- max_n
|
||||
@@ -208,7 +208,7 @@ MemoryCache <- R6Class("MemoryCache",
|
||||
}
|
||||
|
||||
private$log(paste0('get: key "', key, '" found'))
|
||||
value <- private$cache[[key]]$value
|
||||
value <- private$cache$get(key)$value
|
||||
value
|
||||
},
|
||||
|
||||
@@ -226,37 +226,36 @@ MemoryCache <- R6Class("MemoryCache",
|
||||
size <- NULL
|
||||
}
|
||||
|
||||
private$cache[[key]] <- list(
|
||||
private$cache$set(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]])
|
||||
private$cache$has(key)
|
||||
},
|
||||
|
||||
keys = function() {
|
||||
ls(private$cache, sorted = FALSE) # Faster with sorted=FALSE
|
||||
private$cache$keys()
|
||||
},
|
||||
|
||||
remove = function(key) {
|
||||
private$log(paste0('remove: key "', key, '"'))
|
||||
validate_key(key)
|
||||
rm(list = key, envir = private$cache)
|
||||
private$cache$remove(key)
|
||||
invisible(self)
|
||||
},
|
||||
|
||||
reset = function() {
|
||||
private$log(paste0('reset'))
|
||||
rm(list = self$keys(), envir = private$cache)
|
||||
private$cache$reset()
|
||||
invisible(self)
|
||||
},
|
||||
|
||||
@@ -271,7 +270,7 @@ MemoryCache <- R6Class("MemoryCache",
|
||||
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)
|
||||
private$cache$remove(info$key[rm_idx])
|
||||
info <- info[!rm_idx, ]
|
||||
}
|
||||
}
|
||||
@@ -298,7 +297,7 @@ MemoryCache <- R6Class("MemoryCache",
|
||||
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)
|
||||
private$cache$remove(info$key[rm_idx])
|
||||
info <- info[!rm_idx, ]
|
||||
}
|
||||
|
||||
@@ -308,7 +307,7 @@ MemoryCache <- R6Class("MemoryCache",
|
||||
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)
|
||||
private$cache$remove(info$key[rm_idx])
|
||||
info <- info[!rm_idx, ]
|
||||
}
|
||||
|
||||
@@ -335,23 +334,23 @@ MemoryCache <- R6Class("MemoryCache",
|
||||
maybe_prune_single = function(key) {
|
||||
if (!is.finite(private$max_age)) return()
|
||||
|
||||
obj <- private$cache[[key]]
|
||||
obj <- private$cache$get(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)
|
||||
private$cache$remove(key)
|
||||
}
|
||||
},
|
||||
|
||||
object_info = function() {
|
||||
keys <- ls(private$cache, sorted = FALSE)
|
||||
keys <- private$cache$keys()
|
||||
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),
|
||||
size = vapply(keys, function(key) private$cache$get(key)$size, 0),
|
||||
mtime = vapply(keys, function(key) private$cache$get(key)$mtime, 0),
|
||||
atime = vapply(keys, function(key) private$cache$get(key)$atime, 0),
|
||||
stringsAsFactors = FALSE
|
||||
)
|
||||
},
|
||||
@@ -360,7 +359,7 @@ MemoryCache <- R6Class("MemoryCache",
|
||||
if (is.null(private$logfile)) return()
|
||||
|
||||
text <- paste0(format(Sys.time(), "[%Y-%m-%d %H:%M:%OS3] MemoryCache "), text)
|
||||
writeLines(text, private$logfile)
|
||||
cat(text, sep = "\n", file = private$logfile, append = TRUE)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,27 +1,3 @@
|
||||
#' 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) {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
#' 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}.
|
||||
#' @return `printError` and `printStackTrace` return
|
||||
#' `invisible()`. The other functions pass through the results of
|
||||
#' `expr`.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Keeps tryCatch and withVisible related calls off the
|
||||
@@ -106,17 +106,17 @@ getCallCategories <- function(calls) {
|
||||
}, 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
|
||||
#' @details `captureStackTraces` runs the given `expr` and if any
|
||||
#' *uncaught* errors occur, annotates them with stack trace info for use
|
||||
#' by `printError` and `printStackTrace`. It is not necessary to use
|
||||
#' `captureStackTraces` around the same expression as
|
||||
#' `withLogErrors`, as the latter includes a call to the former. Note
|
||||
#' that if `expr` contains calls (either directly or indirectly) to
|
||||
#' `try`, or `tryCatch` with an error handler, stack traces therein
|
||||
#' cannot be captured unless another `captureStackTraces` call is
|
||||
#' inserted in the interior of the `try` or `tryCatch`. This is
|
||||
#' because these calls catch the error and prevent it from traveling up to the
|
||||
#' condition handler installed by \code{captureStackTraces}.
|
||||
#' condition handler installed by `captureStackTraces`.
|
||||
#'
|
||||
#' @param expr The expression to wrap.
|
||||
#' @rdname stacktrace
|
||||
@@ -209,11 +209,11 @@ doCaptureStack <- function(e) {
|
||||
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
|
||||
#' @details `withLogErrors` captures stack traces and logs errors that
|
||||
#' occur in `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}.
|
||||
#' `captureStackTraces` with regard to `try`/`tryCatch` apply
|
||||
#' to `withLogErrors`.
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
withLogErrors <- function(expr,
|
||||
@@ -247,19 +247,19 @@ withLogErrors <- function(expr,
|
||||
)
|
||||
}
|
||||
|
||||
#' @details \code{printError} prints the error and stack trace (if any) using
|
||||
#' \code{warning(immediate.=TRUE)}. \code{printStackTrace} prints the stack
|
||||
#' @details `printError` prints the error and stack trace (if any) using
|
||||
#' `warning(immediate.=TRUE)`. `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
|
||||
#' @param full If `TRUE`, then every element of `sys.calls()` will be
|
||||
#' included in the stack trace. By default (`FALSE`), calls that Shiny
|
||||
#' deems uninteresting will be hidden.
|
||||
#' @param offset If \code{TRUE} (the default), srcrefs will be reassigned from
|
||||
#' @param offset If `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
|
||||
#' location specified by the srcref match up. If `FALSE`, srcrefs will be
|
||||
#' left alone (traditional R treatment where the srcref is of the callsite).
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
@@ -361,10 +361,10 @@ printStackTrace <- function(cond,
|
||||
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
|
||||
#' @details `extractStackTrace` takes a list of calls (e.g. as returned
|
||||
#' from `conditionStackTrace(cond)`) and returns a data frame with one
|
||||
#' row for each stack frame and the columns `num` (stack frame number),
|
||||
#' `call` (a function name or similar), and `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
|
||||
@@ -537,7 +537,7 @@ offsetSrcrefs <- function(calls, offset = TRUE) {
|
||||
calls
|
||||
}
|
||||
|
||||
#' @details \code{formatStackTrace} is similar to \code{extractStackTrace}, but
|
||||
#' @details `formatStackTrace` is similar to `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.
|
||||
@@ -588,11 +588,11 @@ stripStackTrace <- function(cond) {
|
||||
conditionStackTrace(cond) <- NULL
|
||||
}
|
||||
|
||||
#' @details \code{conditionStackTrace} and \code{conditionStackTrace<-} are
|
||||
#' @details `conditionStackTrace` and `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}).
|
||||
#' `captureStackTraces` (or `withLogErrors`).
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
conditionStackTrace <- function(cond) {
|
||||
@@ -607,8 +607,8 @@ conditionStackTrace <- function(cond) {
|
||||
invisible(cond)
|
||||
}
|
||||
|
||||
#' @details The two functions \code{..stacktraceon..} and
|
||||
#' \code{..stacktraceoff..} have no runtime behavior during normal execution;
|
||||
#' @details The two functions `..stacktraceon..` and
|
||||
#' `..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
|
||||
@@ -624,4 +624,4 @@ conditionStackTrace <- function(cond) {
|
||||
#' @export
|
||||
..stacktraceoff.. <- function(expr) expr
|
||||
|
||||
..stacktracefloor.. <- function(expr) expr
|
||||
..stacktracefloor.. <- function(expr) expr
|
||||
|
||||
445
R/font-awesome.R
Normal file
445
R/font-awesome.R
Normal file
@@ -0,0 +1,445 @@
|
||||
font_awesome_brands <- c(
|
||||
"500px",
|
||||
"accessible-icon",
|
||||
"accusoft",
|
||||
"acquisitions-incorporated",
|
||||
"adn",
|
||||
"adobe",
|
||||
"adversal",
|
||||
"affiliatetheme",
|
||||
"airbnb",
|
||||
"algolia",
|
||||
"alipay",
|
||||
"amazon",
|
||||
"amazon-pay",
|
||||
"amilia",
|
||||
"android",
|
||||
"angellist",
|
||||
"angrycreative",
|
||||
"angular",
|
||||
"app-store",
|
||||
"app-store-ios",
|
||||
"apper",
|
||||
"apple",
|
||||
"apple-pay",
|
||||
"artstation",
|
||||
"asymmetrik",
|
||||
"atlassian",
|
||||
"audible",
|
||||
"autoprefixer",
|
||||
"avianex",
|
||||
"aviato",
|
||||
"aws",
|
||||
"bandcamp",
|
||||
"battle-net",
|
||||
"behance",
|
||||
"behance-square",
|
||||
"bimobject",
|
||||
"bitbucket",
|
||||
"bitcoin",
|
||||
"bity",
|
||||
"black-tie",
|
||||
"blackberry",
|
||||
"blogger",
|
||||
"blogger-b",
|
||||
"bluetooth",
|
||||
"bluetooth-b",
|
||||
"bootstrap",
|
||||
"btc",
|
||||
"buffer",
|
||||
"buromobelexperte",
|
||||
"buy-n-large",
|
||||
"buysellads",
|
||||
"canadian-maple-leaf",
|
||||
"cc-amazon-pay",
|
||||
"cc-amex",
|
||||
"cc-apple-pay",
|
||||
"cc-diners-club",
|
||||
"cc-discover",
|
||||
"cc-jcb",
|
||||
"cc-mastercard",
|
||||
"cc-paypal",
|
||||
"cc-stripe",
|
||||
"cc-visa",
|
||||
"centercode",
|
||||
"centos",
|
||||
"chrome",
|
||||
"chromecast",
|
||||
"cloudscale",
|
||||
"cloudsmith",
|
||||
"cloudversify",
|
||||
"codepen",
|
||||
"codiepie",
|
||||
"confluence",
|
||||
"connectdevelop",
|
||||
"contao",
|
||||
"cotton-bureau",
|
||||
"cpanel",
|
||||
"creative-commons",
|
||||
"creative-commons-by",
|
||||
"creative-commons-nc",
|
||||
"creative-commons-nc-eu",
|
||||
"creative-commons-nc-jp",
|
||||
"creative-commons-nd",
|
||||
"creative-commons-pd",
|
||||
"creative-commons-pd-alt",
|
||||
"creative-commons-remix",
|
||||
"creative-commons-sa",
|
||||
"creative-commons-sampling",
|
||||
"creative-commons-sampling-plus",
|
||||
"creative-commons-share",
|
||||
"creative-commons-zero",
|
||||
"critical-role",
|
||||
"css3",
|
||||
"css3-alt",
|
||||
"cuttlefish",
|
||||
"d-and-d",
|
||||
"d-and-d-beyond",
|
||||
"dailymotion",
|
||||
"dashcube",
|
||||
"delicious",
|
||||
"deploydog",
|
||||
"deskpro",
|
||||
"dev",
|
||||
"deviantart",
|
||||
"dhl",
|
||||
"diaspora",
|
||||
"digg",
|
||||
"digital-ocean",
|
||||
"discord",
|
||||
"discourse",
|
||||
"dochub",
|
||||
"docker",
|
||||
"draft2digital",
|
||||
"dribbble",
|
||||
"dribbble-square",
|
||||
"dropbox",
|
||||
"drupal",
|
||||
"dyalog",
|
||||
"earlybirds",
|
||||
"ebay",
|
||||
"edge",
|
||||
"elementor",
|
||||
"ello",
|
||||
"ember",
|
||||
"empire",
|
||||
"envira",
|
||||
"erlang",
|
||||
"ethereum",
|
||||
"etsy",
|
||||
"evernote",
|
||||
"expeditedssl",
|
||||
"facebook",
|
||||
"facebook-f",
|
||||
"facebook-messenger",
|
||||
"facebook-square",
|
||||
"fantasy-flight-games",
|
||||
"fedex",
|
||||
"fedora",
|
||||
"figma",
|
||||
"firefox",
|
||||
"firefox-browser",
|
||||
"first-order",
|
||||
"first-order-alt",
|
||||
"firstdraft",
|
||||
"flickr",
|
||||
"flipboard",
|
||||
"fly",
|
||||
"font-awesome",
|
||||
"font-awesome-alt",
|
||||
"font-awesome-flag",
|
||||
"font-awesome-logo-full",
|
||||
"fonticons",
|
||||
"fonticons-fi",
|
||||
"fort-awesome",
|
||||
"fort-awesome-alt",
|
||||
"forumbee",
|
||||
"foursquare",
|
||||
"free-code-camp",
|
||||
"freebsd",
|
||||
"fulcrum",
|
||||
"galactic-republic",
|
||||
"galactic-senate",
|
||||
"get-pocket",
|
||||
"gg",
|
||||
"gg-circle",
|
||||
"git",
|
||||
"git-alt",
|
||||
"git-square",
|
||||
"github",
|
||||
"github-alt",
|
||||
"github-square",
|
||||
"gitkraken",
|
||||
"gitlab",
|
||||
"gitter",
|
||||
"glide",
|
||||
"glide-g",
|
||||
"gofore",
|
||||
"goodreads",
|
||||
"goodreads-g",
|
||||
"google",
|
||||
"google-drive",
|
||||
"google-play",
|
||||
"google-plus",
|
||||
"google-plus-g",
|
||||
"google-plus-square",
|
||||
"google-wallet",
|
||||
"gratipay",
|
||||
"grav",
|
||||
"gripfire",
|
||||
"grunt",
|
||||
"gulp",
|
||||
"hacker-news",
|
||||
"hacker-news-square",
|
||||
"hackerrank",
|
||||
"hips",
|
||||
"hire-a-helper",
|
||||
"hooli",
|
||||
"hornbill",
|
||||
"hotjar",
|
||||
"houzz",
|
||||
"html5",
|
||||
"hubspot",
|
||||
"ideal",
|
||||
"imdb",
|
||||
"instagram",
|
||||
"instagram-square",
|
||||
"intercom",
|
||||
"internet-explorer",
|
||||
"invision",
|
||||
"ioxhost",
|
||||
"itch-io",
|
||||
"itunes",
|
||||
"itunes-note",
|
||||
"java",
|
||||
"jedi-order",
|
||||
"jenkins",
|
||||
"jira",
|
||||
"joget",
|
||||
"joomla",
|
||||
"js",
|
||||
"js-square",
|
||||
"jsfiddle",
|
||||
"kaggle",
|
||||
"keybase",
|
||||
"keycdn",
|
||||
"kickstarter",
|
||||
"kickstarter-k",
|
||||
"korvue",
|
||||
"laravel",
|
||||
"lastfm",
|
||||
"lastfm-square",
|
||||
"leanpub",
|
||||
"less",
|
||||
"line",
|
||||
"linkedin",
|
||||
"linkedin-in",
|
||||
"linode",
|
||||
"linux",
|
||||
"lyft",
|
||||
"magento",
|
||||
"mailchimp",
|
||||
"mandalorian",
|
||||
"markdown",
|
||||
"mastodon",
|
||||
"maxcdn",
|
||||
"mdb",
|
||||
"medapps",
|
||||
"medium",
|
||||
"medium-m",
|
||||
"medrt",
|
||||
"meetup",
|
||||
"megaport",
|
||||
"mendeley",
|
||||
"microblog",
|
||||
"microsoft",
|
||||
"mix",
|
||||
"mixcloud",
|
||||
"mixer",
|
||||
"mizuni",
|
||||
"modx",
|
||||
"monero",
|
||||
"napster",
|
||||
"neos",
|
||||
"nimblr",
|
||||
"node",
|
||||
"node-js",
|
||||
"npm",
|
||||
"ns8",
|
||||
"nutritionix",
|
||||
"odnoklassniki",
|
||||
"odnoklassniki-square",
|
||||
"old-republic",
|
||||
"opencart",
|
||||
"openid",
|
||||
"opera",
|
||||
"optin-monster",
|
||||
"orcid",
|
||||
"osi",
|
||||
"page4",
|
||||
"pagelines",
|
||||
"palfed",
|
||||
"patreon",
|
||||
"paypal",
|
||||
"penny-arcade",
|
||||
"periscope",
|
||||
"phabricator",
|
||||
"phoenix-framework",
|
||||
"phoenix-squadron",
|
||||
"php",
|
||||
"pied-piper",
|
||||
"pied-piper-alt",
|
||||
"pied-piper-hat",
|
||||
"pied-piper-pp",
|
||||
"pied-piper-square",
|
||||
"pinterest",
|
||||
"pinterest-p",
|
||||
"pinterest-square",
|
||||
"playstation",
|
||||
"product-hunt",
|
||||
"pushed",
|
||||
"python",
|
||||
"qq",
|
||||
"quinscape",
|
||||
"quora",
|
||||
"r-project",
|
||||
"raspberry-pi",
|
||||
"ravelry",
|
||||
"react",
|
||||
"reacteurope",
|
||||
"readme",
|
||||
"rebel",
|
||||
"red-river",
|
||||
"reddit",
|
||||
"reddit-alien",
|
||||
"reddit-square",
|
||||
"redhat",
|
||||
"renren",
|
||||
"replyd",
|
||||
"researchgate",
|
||||
"resolving",
|
||||
"rev",
|
||||
"rocketchat",
|
||||
"rockrms",
|
||||
"safari",
|
||||
"salesforce",
|
||||
"sass",
|
||||
"schlix",
|
||||
"scribd",
|
||||
"searchengin",
|
||||
"sellcast",
|
||||
"sellsy",
|
||||
"servicestack",
|
||||
"shirtsinbulk",
|
||||
"shopify",
|
||||
"shopware",
|
||||
"simplybuilt",
|
||||
"sistrix",
|
||||
"sith",
|
||||
"sketch",
|
||||
"skyatlas",
|
||||
"skype",
|
||||
"slack",
|
||||
"slack-hash",
|
||||
"slideshare",
|
||||
"snapchat",
|
||||
"snapchat-ghost",
|
||||
"snapchat-square",
|
||||
"soundcloud",
|
||||
"sourcetree",
|
||||
"speakap",
|
||||
"speaker-deck",
|
||||
"spotify",
|
||||
"squarespace",
|
||||
"stack-exchange",
|
||||
"stack-overflow",
|
||||
"stackpath",
|
||||
"staylinked",
|
||||
"steam",
|
||||
"steam-square",
|
||||
"steam-symbol",
|
||||
"sticker-mule",
|
||||
"strava",
|
||||
"stripe",
|
||||
"stripe-s",
|
||||
"studiovinari",
|
||||
"stumbleupon",
|
||||
"stumbleupon-circle",
|
||||
"superpowers",
|
||||
"supple",
|
||||
"suse",
|
||||
"swift",
|
||||
"symfony",
|
||||
"teamspeak",
|
||||
"telegram",
|
||||
"telegram-plane",
|
||||
"tencent-weibo",
|
||||
"the-red-yeti",
|
||||
"themeco",
|
||||
"themeisle",
|
||||
"think-peaks",
|
||||
"trade-federation",
|
||||
"trello",
|
||||
"tripadvisor",
|
||||
"tumblr",
|
||||
"tumblr-square",
|
||||
"twitch",
|
||||
"twitter",
|
||||
"twitter-square",
|
||||
"typo3",
|
||||
"uber",
|
||||
"ubuntu",
|
||||
"uikit",
|
||||
"umbraco",
|
||||
"uniregistry",
|
||||
"unity",
|
||||
"untappd",
|
||||
"ups",
|
||||
"usb",
|
||||
"usps",
|
||||
"ussunnah",
|
||||
"vaadin",
|
||||
"viacoin",
|
||||
"viadeo",
|
||||
"viadeo-square",
|
||||
"viber",
|
||||
"vimeo",
|
||||
"vimeo-square",
|
||||
"vimeo-v",
|
||||
"vine",
|
||||
"vk",
|
||||
"vnv",
|
||||
"vuejs",
|
||||
"waze",
|
||||
"weebly",
|
||||
"weibo",
|
||||
"weixin",
|
||||
"whatsapp",
|
||||
"whatsapp-square",
|
||||
"whmcs",
|
||||
"wikipedia-w",
|
||||
"windows",
|
||||
"wix",
|
||||
"wizards-of-the-coast",
|
||||
"wolf-pack-battalion",
|
||||
"wordpress",
|
||||
"wordpress-simple",
|
||||
"wpbeginner",
|
||||
"wpexplorer",
|
||||
"wpforms",
|
||||
"wpressr",
|
||||
"xbox",
|
||||
"xing",
|
||||
"xing-square",
|
||||
"y-combinator",
|
||||
"yahoo",
|
||||
"yammer",
|
||||
"yandex",
|
||||
"yandex-international",
|
||||
"yarn",
|
||||
"yelp",
|
||||
"yoast",
|
||||
"youtube",
|
||||
"youtube-square",
|
||||
"zhihu"
|
||||
)
|
||||
73
R/globals.R
73
R/globals.R
@@ -1,23 +1,70 @@
|
||||
# A scope where we can put mutable global state
|
||||
.globals <- new.env(parent = emptyenv())
|
||||
|
||||
register_s3_method <- function(pkg, generic, class, fun = NULL) {
|
||||
stopifnot(is.character(pkg), length(pkg) == 1)
|
||||
stopifnot(is.character(generic), length(generic) == 1)
|
||||
stopifnot(is.character(class), length(class) == 1)
|
||||
|
||||
if (is.null(fun)) {
|
||||
fun <- get(paste0(generic, ".", class), envir = parent.frame())
|
||||
} else {
|
||||
stopifnot(is.function(fun))
|
||||
}
|
||||
|
||||
if (pkg %in% loadedNamespaces()) {
|
||||
registerS3method(generic, class, fun, envir = asNamespace(pkg))
|
||||
}
|
||||
|
||||
# Always register hook in case pkg is loaded at some
|
||||
# point the future (or, potentially, but less commonly,
|
||||
# unloaded & reloaded)
|
||||
setHook(
|
||||
packageEvent(pkg, "onLoad"),
|
||||
function(...) {
|
||||
registerS3method(generic, class, fun, envir = asNamespace(pkg))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
register_upgrade_message <- function(pkg, version) {
|
||||
|
||||
msg <- sprintf(
|
||||
"This version of Shiny is designed to work with '%s' >= %s.
|
||||
Please upgrade via install.packages('%s').",
|
||||
pkg, version, pkg
|
||||
)
|
||||
|
||||
if (pkg %in% loadedNamespaces() && !is_available(pkg, version)) {
|
||||
packageStartupMessage(msg)
|
||||
}
|
||||
|
||||
# Always register hook in case pkg is loaded at some
|
||||
# point the future (or, potentially, but less commonly,
|
||||
# unloaded & reloaded)
|
||||
setHook(
|
||||
packageEvent(pkg, "onLoad"),
|
||||
function(...) {
|
||||
if (!is_available(pkg, version)) packageStartupMessage(msg)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
.onLoad <- function(libname, pkgname) {
|
||||
# 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(set.seed(NULL))
|
||||
}
|
||||
|
||||
.onAttach <- function(libname, pkgname) {
|
||||
# Check for htmlwidgets version, if installed. As of Shiny 0.12.0 and
|
||||
# htmlwidgets 0.4, both packages switched from RJSONIO to jsonlite. Because of
|
||||
# this change, Shiny 0.12.0 will work only with htmlwidgets >= 0.4, and vice
|
||||
# versa.
|
||||
if (system.file(package = "htmlwidgets") != "" &&
|
||||
utils::packageVersion("htmlwidgets") < "0.4") {
|
||||
packageStartupMessage(
|
||||
"This version of Shiny is designed to work with htmlwidgets >= 0.4. ",
|
||||
"Please upgrade your version of htmlwidgets."
|
||||
)
|
||||
}
|
||||
# Make sure these methods are available to knitr if shiny is loaded but not
|
||||
# attached.
|
||||
register_s3_method("knitr", "knit_print", "reactive")
|
||||
register_s3_method("knitr", "knit_print", "shiny.appobj")
|
||||
register_s3_method("knitr", "knit_print", "shiny.render.function")
|
||||
|
||||
# Shiny 1.4.0 bumps jQuery 1.x to 3.x, which caused a problem
|
||||
# with static-rendering of htmlwidgets, and htmlwidgets 1.5
|
||||
# includes a fix for this problem
|
||||
# https://github.com/rstudio/shiny/issues/2630
|
||||
register_upgrade_message("htmlwidgets", 1.5)
|
||||
}
|
||||
|
||||
582
R/graph.R
582
R/graph.R
@@ -1,21 +1,55 @@
|
||||
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)
|
||||
# Check that the version of an suggested package satisfies the requirements
|
||||
#
|
||||
# @param package The name of the suggested package
|
||||
# @param version The version of the package
|
||||
check_suggested <- function(package, version = NULL) {
|
||||
|
||||
if (is_available(package, version)) {
|
||||
return()
|
||||
}
|
||||
cat(toJSON(log, pretty=TRUE), file=file)
|
||||
|
||||
msg <- paste0(
|
||||
sQuote(package),
|
||||
if (is.na(version %OR% NA)) "" else paste0("(>= ", version, ")"),
|
||||
" must be installed for this functionality."
|
||||
)
|
||||
|
||||
if (interactive()) {
|
||||
message(msg, "\nWould you like to install it?")
|
||||
if (utils::menu(c("Yes", "No")) == 1) {
|
||||
return(utils::install.packages(package))
|
||||
}
|
||||
}
|
||||
|
||||
stop(msg, call. = FALSE)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# domain is like session
|
||||
|
||||
|
||||
# used to help define truly global react id's.
|
||||
# should work across session and in global namespace
|
||||
.globals$reactIdCounter <- 0L
|
||||
nextGlobalReactId <- function() {
|
||||
.globals$reactIdCounter <- .globals$reactIdCounter + 1L
|
||||
reactIdStr(.globals$reactIdCounter)
|
||||
}
|
||||
reactIdStr <- function(num) {
|
||||
paste0("r", num)
|
||||
}
|
||||
|
||||
|
||||
#' Reactive Log Visualizer
|
||||
#'
|
||||
#' Provides an interactive browser-based tool for visualizing reactive
|
||||
#' dependencies and execution in your application.
|
||||
#'
|
||||
#' To use the reactive log visualizer, start with a fresh R session and
|
||||
#' run the command \code{options(shiny.reactlog=TRUE)}; then launch your
|
||||
#' application in the usual way (e.g. using \code{\link{runApp}}). At
|
||||
#' run the command `options(shiny.reactlog=TRUE)`; then launch your
|
||||
#' application in the usual way (e.g. using [runApp()]). At
|
||||
#' any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your
|
||||
#' web browser to launch the reactive log visualization.
|
||||
#'
|
||||
@@ -30,88 +64,500 @@ writeReactLog <- function(file=stdout(), sessionToken = NULL) {
|
||||
#'
|
||||
#' As an alternative to pressing Ctrl/Command+F3--for example, if you
|
||||
#' are using reactives outside of the context of a Shiny
|
||||
#' application--you can run the \code{showReactLog} function, which will
|
||||
#' application--you can run the `reactlogShow` function, which will
|
||||
#' generate the reactive log visualization as a static HTML file and
|
||||
#' launch it in your default browser. In this case, refreshing your
|
||||
#' browser will not load new activity into the report; you will need to
|
||||
#' call \code{showReactLog()} explicitly.
|
||||
#' call `reactlogShow()` explicitly.
|
||||
#'
|
||||
#' For security and performance reasons, do not enable
|
||||
#' \code{shiny.reactlog} in production environments. When the option is
|
||||
#' `shiny.reactlog` in production environments. When the option is
|
||||
#' 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.
|
||||
#' @name reactlog
|
||||
NULL
|
||||
|
||||
|
||||
#' @describeIn reactlog Return a list of reactive information. Can be used in conjunction with
|
||||
#' [reactlog::reactlog_show] to later display the reactlog graph.
|
||||
#' @export
|
||||
reactlog <- function() {
|
||||
rLog$asList()
|
||||
}
|
||||
|
||||
#' @describeIn reactlog Display a full reactlog graph for all sessions.
|
||||
#' @param time A boolean that specifies whether or not to display the
|
||||
#' time that each reactive takes to calculate a result.
|
||||
#' @export
|
||||
reactlogShow <- function(time = TRUE) {
|
||||
check_reactlog()
|
||||
reactlog::reactlog_show(reactlog(), time = time)
|
||||
}
|
||||
#' @describeIn reactlog This function is deprecated. You should use [reactlogShow()]
|
||||
#' @export
|
||||
# legacy purposes
|
||||
showReactLog <- function(time = TRUE) {
|
||||
utils::browseURL(renderReactLog(time = as.logical(time)))
|
||||
shinyDeprecated(new = "`reactlogShow`", version = "1.2.0")
|
||||
reactlogShow(time = time)
|
||||
}
|
||||
#' @describeIn reactlog Resets the entire reactlog stack. Useful for debugging and removing all prior reactive history.
|
||||
#' @export
|
||||
reactlogReset <- function() {
|
||||
rLog$reset()
|
||||
}
|
||||
|
||||
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, 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)
|
||||
# called in "/reactlog" middleware
|
||||
renderReactlog <- function(sessionToken = NULL, time = TRUE) {
|
||||
check_reactlog()
|
||||
reactlog::reactlog_render(
|
||||
reactlog(),
|
||||
session_token = sessionToken,
|
||||
time = time
|
||||
)
|
||||
}
|
||||
check_reactlog <- function() {
|
||||
check_suggested("reactlog", reactlog_version())
|
||||
}
|
||||
# read reactlog version from description file
|
||||
# prevents version mismatch in code and description file
|
||||
reactlog_version <- function() {
|
||||
desc <- read.dcf(system.file("DESCRIPTION", package = "shiny", mustWork = TRUE))
|
||||
suggests <- desc[1,"Suggests"][[1]]
|
||||
suggests_pkgs <- strsplit(suggests, "\n")[[1]]
|
||||
|
||||
.graphAppend <- function(logEntry, domain = getDefaultReactiveDomain()) {
|
||||
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())
|
||||
)))
|
||||
reactlog_info <- suggests_pkgs[grepl("reactlog", suggests_pkgs)]
|
||||
if (length(reactlog_info) == 0) {
|
||||
stop("reactlog can not be found in shiny DESCRIPTION file")
|
||||
}
|
||||
|
||||
if (!is.null(domain)) {
|
||||
domain$reactlog(logEntry)
|
||||
}
|
||||
reactlog_info <- sub("^[^\\(]*\\(", "", reactlog_info)
|
||||
reactlog_info <- sub("\\)[^\\)]*$", "", reactlog_info)
|
||||
reactlog_info <- sub("^[>= ]*", "", reactlog_info)
|
||||
|
||||
package_version(reactlog_info)
|
||||
}
|
||||
|
||||
.graphDependsOn <- function(id, label) {
|
||||
.graphAppend(list(action='dep', id=id, dependsOn=label))
|
||||
}
|
||||
|
||||
.graphDependsOnId <- function(id, dependee) {
|
||||
.graphAppend(list(action='depId', id=id, dependsOn=dependee))
|
||||
}
|
||||
RLog <- R6Class(
|
||||
"RLog",
|
||||
portable = FALSE,
|
||||
private = list(
|
||||
option = "shiny.reactlog",
|
||||
msgOption = "shiny.reactlog.console",
|
||||
|
||||
.graphCreateContext <- function(id, label, type, prevId, domain) {
|
||||
.graphAppend(list(
|
||||
action='ctx', id=id, label=paste(label, collapse='\n'),
|
||||
srcref=as.vector(attr(label, "srcref")), srcfile=attr(label, "srcfile"),
|
||||
type=type, prevId=prevId
|
||||
), domain = domain)
|
||||
}
|
||||
appendEntry = function(domain, logEntry) {
|
||||
if (self$isLogging()) {
|
||||
sessionToken <- if (is.null(domain)) NULL else domain$token
|
||||
logStack$push(c(logEntry, list(
|
||||
session = sessionToken,
|
||||
time = as.numeric(Sys.time())
|
||||
)))
|
||||
}
|
||||
if (!is.null(domain)) domain$reactlog(logEntry)
|
||||
}
|
||||
),
|
||||
public = list(
|
||||
msg = "<MessageLogger>",
|
||||
logStack = "<Stack>",
|
||||
|
||||
.graphEnterContext <- function(id) {
|
||||
.graphAppend(list(action='enter', id=id))
|
||||
}
|
||||
noReactIdLabel = "NoCtxReactId",
|
||||
noReactId = reactIdStr("NoCtxReactId"),
|
||||
dummyReactIdLabel = "DummyReactId",
|
||||
dummyReactId = reactIdStr("DummyReactId"),
|
||||
|
||||
.graphExitContext <- function(id) {
|
||||
.graphAppend(list(action='exit', id=id))
|
||||
}
|
||||
asList = function() {
|
||||
ret <- self$logStack$as_list()
|
||||
attr(ret, "version") <- "1"
|
||||
ret
|
||||
},
|
||||
|
||||
.graphValueChange <- function(label, value) {
|
||||
.graphAppend(list(
|
||||
action = 'valueChange',
|
||||
id = label,
|
||||
value = paste(utils::capture.output(utils::str(value)), collapse='\n')
|
||||
))
|
||||
}
|
||||
ctxIdStr = function(ctxId) {
|
||||
if (is.null(ctxId) || identical(ctxId, "")) return(NULL)
|
||||
paste0("ctx", ctxId)
|
||||
},
|
||||
namesIdStr = function(reactId) {
|
||||
paste0("names(", reactId, ")")
|
||||
},
|
||||
asListIdStr = function(reactId) {
|
||||
paste0("reactiveValuesToList(", reactId, ")")
|
||||
},
|
||||
asListAllIdStr = function(reactId) {
|
||||
paste0("reactiveValuesToList(", reactId, ", all.names = TRUE)")
|
||||
},
|
||||
keyIdStr = function(reactId, key) {
|
||||
paste0(reactId, "$", key)
|
||||
},
|
||||
|
||||
.graphInvalidate <- function(id, domain) {
|
||||
.graphAppend(list(action='invalidate', id=id), domain)
|
||||
}
|
||||
valueStr = function(value, n = 200) {
|
||||
if (!self$isLogging()) {
|
||||
# return a placeholder string to avoid calling str
|
||||
return("<reactlog is turned off>")
|
||||
}
|
||||
output <- try(silent = TRUE, {
|
||||
# only capture the first level of the object
|
||||
utils::capture.output(utils::str(value, max.level = 1))
|
||||
})
|
||||
outputTxt <- paste0(output, collapse="\n")
|
||||
msg$shortenString(outputTxt, n = n)
|
||||
},
|
||||
|
||||
initialize = function(rlogOption = "shiny.reactlog", msgOption = "shiny.reactlog.console") {
|
||||
private$option <- rlogOption
|
||||
private$msgOption <- msgOption
|
||||
|
||||
self$reset()
|
||||
},
|
||||
reset = function() {
|
||||
.globals$reactIdCounter <- 0L
|
||||
|
||||
self$logStack <- Stack$new()
|
||||
self$msg <- MessageLogger$new(option = private$msgOption)
|
||||
|
||||
# setup dummy and missing react information
|
||||
self$msg$setReact(force = TRUE, list(reactId = self$noReactId, label = self$noReactIdLabel))
|
||||
self$msg$setReact(force = TRUE, list(reactId = self$dummyReactId, label = self$dummyReactIdLabel))
|
||||
},
|
||||
isLogging = function() {
|
||||
isTRUE(getOption(private$option, FALSE))
|
||||
},
|
||||
|
||||
define = function(reactId, value, label, type, domain) {
|
||||
valueStr <- self$valueStr(value)
|
||||
if (msg$hasReact(reactId)) {
|
||||
stop("react definition for id: ", reactId, " already found!!", "Label: ", label, "Type: ", type)
|
||||
}
|
||||
msg$setReact(list(reactId = reactId, label = label))
|
||||
msg$log("define:", msg$reactStr(reactId), msg$typeStr(type = type), msg$valueStr(valueStr))
|
||||
private$appendEntry(domain, list(
|
||||
action = "define",
|
||||
reactId = reactId,
|
||||
label = msg$shortenString(label),
|
||||
type = type,
|
||||
value = valueStr
|
||||
))
|
||||
},
|
||||
defineNames = function(reactId, value, label, domain) {
|
||||
self$define(self$namesIdStr(reactId), value, self$namesIdStr(label), "reactiveValuesNames", domain)
|
||||
},
|
||||
defineAsList = function(reactId, value, label, domain) {
|
||||
self$define(self$asListIdStr(reactId), value, self$asListIdStr(label), "reactiveValuesAsList", domain)
|
||||
},
|
||||
defineAsListAll = function(reactId, value, label, domain) {
|
||||
self$define(self$asListAllIdStr(reactId), value, self$asListAllIdStr(label), "reactiveValuesAsListAll", domain)
|
||||
},
|
||||
defineKey = function(reactId, value, key, label, domain) {
|
||||
self$define(self$keyIdStr(reactId, key), value, self$keyIdStr(label, key), "reactiveValuesKey", domain)
|
||||
},
|
||||
defineObserver = function(reactId, label, domain) {
|
||||
self$define(reactId, value = NULL, label, "observer", domain)
|
||||
},
|
||||
|
||||
dependsOn = function(reactId, depOnReactId, ctxId, domain) {
|
||||
if (is.null(reactId)) return()
|
||||
ctxId <- ctxIdStr(ctxId)
|
||||
msg$log("dependsOn:", msg$reactStr(reactId), " on", msg$reactStr(depOnReactId), msg$ctxStr(ctxId))
|
||||
private$appendEntry(domain, list(
|
||||
action = "dependsOn",
|
||||
reactId = reactId,
|
||||
depOnReactId = depOnReactId,
|
||||
ctxId = ctxId
|
||||
))
|
||||
},
|
||||
dependsOnKey = function(reactId, depOnReactId, key, ctxId, domain) {
|
||||
self$dependsOn(reactId, self$keyIdStr(depOnReactId, key), ctxId, domain)
|
||||
},
|
||||
|
||||
dependsOnRemove = function(reactId, depOnReactId, ctxId, domain) {
|
||||
ctxId <- self$ctxIdStr(ctxId)
|
||||
msg$log("dependsOnRemove:", msg$reactStr(reactId), " on", msg$reactStr(depOnReactId), msg$ctxStr(ctxId))
|
||||
private$appendEntry(domain, list(
|
||||
action = "dependsOnRemove",
|
||||
reactId = reactId,
|
||||
depOnReactId = depOnReactId,
|
||||
ctxId = ctxId
|
||||
))
|
||||
},
|
||||
dependsOnKeyRemove = function(reactId, depOnReactId, key, ctxId, domain) {
|
||||
self$dependsOnRemove(reactId, self$keyIdStr(depOnReactId, key), ctxId, domain)
|
||||
},
|
||||
|
||||
createContext = function(ctxId, label, type, prevCtxId, domain) {
|
||||
ctxId <- self$ctxIdStr(ctxId)
|
||||
prevCtxId <- self$ctxIdStr(prevCtxId)
|
||||
msg$log("createContext:", msg$ctxPrevCtxStr(preCtxIdTxt = " ", ctxId, prevCtxId, type))
|
||||
private$appendEntry(domain, list(
|
||||
action = "createContext",
|
||||
ctxId = ctxId,
|
||||
label = msg$shortenString(label),
|
||||
type = type,
|
||||
prevCtxId = prevCtxId,
|
||||
srcref = as.vector(attr(label, "srcref")), srcfile=attr(label, "srcfile")
|
||||
))
|
||||
},
|
||||
|
||||
enter = function(reactId, ctxId, type, domain) {
|
||||
ctxId <- self$ctxIdStr(ctxId)
|
||||
if (identical(type, "isolate")) {
|
||||
msg$log("isolateEnter:", msg$reactStr(reactId), msg$ctxStr(ctxId))
|
||||
msg$depthIncrement()
|
||||
private$appendEntry(domain, list(
|
||||
action = "isolateEnter",
|
||||
reactId = reactId,
|
||||
ctxId = ctxId
|
||||
))
|
||||
} else {
|
||||
msg$log("enter:", msg$reactStr(reactId), msg$ctxStr(ctxId, type))
|
||||
msg$depthIncrement()
|
||||
private$appendEntry(domain, list(
|
||||
action = "enter",
|
||||
reactId = reactId,
|
||||
ctxId = ctxId,
|
||||
type = type
|
||||
))
|
||||
}
|
||||
},
|
||||
exit = function(reactId, ctxId, type, domain) {
|
||||
ctxId <- self$ctxIdStr(ctxId)
|
||||
if (identical(type, "isolate")) {
|
||||
msg$depthDecrement()
|
||||
msg$log("isolateExit:", msg$reactStr(reactId), msg$ctxStr(ctxId))
|
||||
private$appendEntry(domain, list(
|
||||
action = "isolateExit",
|
||||
reactId = reactId,
|
||||
ctxId = ctxId
|
||||
))
|
||||
} else {
|
||||
msg$depthDecrement()
|
||||
msg$log("exit:", msg$reactStr(reactId), msg$ctxStr(ctxId, type))
|
||||
private$appendEntry(domain, list(
|
||||
action = "exit",
|
||||
reactId = reactId,
|
||||
ctxId = ctxId,
|
||||
type = type
|
||||
))
|
||||
}
|
||||
},
|
||||
|
||||
valueChange = function(reactId, value, domain) {
|
||||
valueStr <- self$valueStr(value)
|
||||
msg$log("valueChange:", msg$reactStr(reactId), msg$valueStr(valueStr))
|
||||
private$appendEntry(domain, list(
|
||||
action = "valueChange",
|
||||
reactId = reactId,
|
||||
value = valueStr
|
||||
))
|
||||
},
|
||||
valueChangeNames = function(reactId, nameValues, domain) {
|
||||
self$valueChange(self$namesIdStr(reactId), nameValues, domain)
|
||||
},
|
||||
valueChangeAsList = function(reactId, listValue, domain) {
|
||||
self$valueChange(self$asListIdStr(reactId), listValue, domain)
|
||||
},
|
||||
valueChangeAsListAll = function(reactId, listValue, domain) {
|
||||
self$valueChange(self$asListAllIdStr(reactId), listValue, domain)
|
||||
},
|
||||
valueChangeKey = function(reactId, key, value, domain) {
|
||||
self$valueChange(self$keyIdStr(reactId, key), value, domain)
|
||||
},
|
||||
|
||||
|
||||
invalidateStart = function(reactId, ctxId, type, domain) {
|
||||
ctxId <- self$ctxIdStr(ctxId)
|
||||
if (identical(type, "isolate")) {
|
||||
msg$log("isolateInvalidateStart:", msg$reactStr(reactId), msg$ctxStr(ctxId))
|
||||
msg$depthIncrement()
|
||||
private$appendEntry(domain, list(
|
||||
action = "isolateInvalidateStart",
|
||||
reactId = reactId,
|
||||
ctxId = ctxId
|
||||
))
|
||||
} else {
|
||||
msg$log("invalidateStart:", msg$reactStr(reactId), msg$ctxStr(ctxId, type))
|
||||
msg$depthIncrement()
|
||||
private$appendEntry(domain, list(
|
||||
action = "invalidateStart",
|
||||
reactId = reactId,
|
||||
ctxId = ctxId,
|
||||
type = type
|
||||
))
|
||||
}
|
||||
},
|
||||
invalidateEnd = function(reactId, ctxId, type, domain) {
|
||||
ctxId <- self$ctxIdStr(ctxId)
|
||||
if (identical(type, "isolate")) {
|
||||
msg$depthDecrement()
|
||||
msg$log("isolateInvalidateEnd:", msg$reactStr(reactId), msg$ctxStr(ctxId))
|
||||
private$appendEntry(domain, list(
|
||||
action = "isolateInvalidateEnd",
|
||||
reactId = reactId,
|
||||
ctxId = ctxId
|
||||
))
|
||||
} else {
|
||||
msg$depthDecrement()
|
||||
msg$log("invalidateEnd:", msg$reactStr(reactId), msg$ctxStr(ctxId, type))
|
||||
private$appendEntry(domain, list(
|
||||
action = "invalidateEnd",
|
||||
reactId = reactId,
|
||||
ctxId = ctxId,
|
||||
type = type
|
||||
))
|
||||
}
|
||||
},
|
||||
|
||||
invalidateLater = function(reactId, runningCtx, millis, domain) {
|
||||
msg$log("invalidateLater: ", millis, "ms", msg$reactStr(reactId), msg$ctxStr(runningCtx))
|
||||
private$appendEntry(domain, list(
|
||||
action = "invalidateLater",
|
||||
reactId = reactId,
|
||||
ctxId = runningCtx,
|
||||
millis = millis
|
||||
))
|
||||
},
|
||||
|
||||
idle = function(domain = NULL) {
|
||||
msg$log("idle")
|
||||
private$appendEntry(domain, list(
|
||||
action = "idle"
|
||||
))
|
||||
},
|
||||
|
||||
asyncStart = function(domain = NULL) {
|
||||
msg$log("asyncStart")
|
||||
private$appendEntry(domain, list(
|
||||
action = "asyncStart"
|
||||
))
|
||||
},
|
||||
asyncStop = function(domain = NULL) {
|
||||
msg$log("asyncStop")
|
||||
private$appendEntry(domain, list(
|
||||
action = "asyncStop"
|
||||
))
|
||||
},
|
||||
|
||||
freezeReactiveVal = function(reactId, domain) {
|
||||
msg$log("freeze:", msg$reactStr(reactId))
|
||||
private$appendEntry(domain, list(
|
||||
action = "freeze",
|
||||
reactId = reactId
|
||||
))
|
||||
},
|
||||
freezeReactiveKey = function(reactId, key, domain) {
|
||||
self$freezeReactiveVal(self$keyIdStr(reactId, key), domain)
|
||||
},
|
||||
|
||||
thawReactiveVal = function(reactId, domain) {
|
||||
msg$log("thaw:", msg$reactStr(reactId))
|
||||
private$appendEntry(domain, list(
|
||||
action = "thaw",
|
||||
reactId = reactId
|
||||
))
|
||||
},
|
||||
thawReactiveKey = function(reactId, key, domain) {
|
||||
self$thawReactiveVal(self$keyIdStr(reactId, key), domain)
|
||||
},
|
||||
|
||||
userMark = function(domain = NULL) {
|
||||
msg$log("userMark")
|
||||
private$appendEntry(domain, list(
|
||||
action = "userMark"
|
||||
))
|
||||
}
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
MessageLogger = R6Class(
|
||||
"MessageLogger",
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
depth = 0L,
|
||||
reactCache = list(),
|
||||
option = "shiny.reactlog.console",
|
||||
|
||||
initialize = function(option = "shiny.reactlog.console", depth = 0L) {
|
||||
if (!missing(depth)) self$depth <- depth
|
||||
if (!missing(option)) self$option <- option
|
||||
},
|
||||
|
||||
isLogging = function() {
|
||||
isTRUE(getOption(self$option))
|
||||
},
|
||||
isNotLogging = function() {
|
||||
! isTRUE(getOption(self$option))
|
||||
},
|
||||
depthIncrement = function() {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
self$depth <- self$depth + 1L
|
||||
},
|
||||
depthDecrement = function() {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
self$depth <- self$depth - 1L
|
||||
},
|
||||
hasReact = function(reactId) {
|
||||
if (self$isNotLogging()) return(FALSE)
|
||||
!is.null(self$getReact(reactId))
|
||||
},
|
||||
getReact = function(reactId, force = FALSE) {
|
||||
if (identical(force, FALSE) && self$isNotLogging()) return(NULL)
|
||||
self$reactCache[[reactId]]
|
||||
},
|
||||
setReact = function(reactObj, force = FALSE) {
|
||||
if (identical(force, FALSE) && self$isNotLogging()) return(NULL)
|
||||
self$reactCache[[reactObj$reactId]] <- reactObj
|
||||
},
|
||||
shortenString = function(txt, n = 250) {
|
||||
if (is.null(txt) || isTRUE(is.na(txt))) {
|
||||
return("")
|
||||
}
|
||||
if (nchar(txt) > n) {
|
||||
return(
|
||||
paste0(substr(txt, 1, n - 3), "...")
|
||||
)
|
||||
}
|
||||
return(txt)
|
||||
},
|
||||
singleLine = function(txt) {
|
||||
gsub("[^\\]\\n", "\\\\n", txt)
|
||||
},
|
||||
valueStr = function(valueStr) {
|
||||
paste0(
|
||||
" '", self$shortenString(self$singleLine(valueStr)), "'"
|
||||
)
|
||||
},
|
||||
reactStr = function(reactId) {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
reactInfo <- self$getReact(reactId)
|
||||
if (is.null(reactInfo)) return(" <UNKNOWN_REACTID>")
|
||||
paste0(
|
||||
" ", reactInfo$reactId, ":'", self$shortenString(self$singleLine(reactInfo$label)), "'"
|
||||
)
|
||||
},
|
||||
typeStr = function(type = NULL) {
|
||||
self$ctxStr(ctxId = NULL, type = type)
|
||||
},
|
||||
ctxStr = function(ctxId = NULL, type = NULL) {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
self$ctxPrevCtxStr(ctxId = ctxId, prevCtxId = NULL, type = type)
|
||||
},
|
||||
ctxPrevCtxStr = function(ctxId = NULL, prevCtxId = NULL, type = NULL, preCtxIdTxt = " in ") {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
paste0(
|
||||
if (!is.null(ctxId)) paste0(preCtxIdTxt, ctxId),
|
||||
if (!is.null(prevCtxId)) paste0(" from ", prevCtxId),
|
||||
if (!is.null(type) && !identical(type, "other")) paste0(" - ", type)
|
||||
)
|
||||
},
|
||||
log = function(...) {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
msg <- paste0(
|
||||
paste0(rep("= ", depth), collapse = ""), "- ", paste0(..., collapse = ""),
|
||||
collapse = ""
|
||||
)
|
||||
message(msg)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#' @include stack.R
|
||||
.graphStack <- Stack$new()
|
||||
rLog <- RLog$new("shiny.reactlog", "shiny.reactlog.console")
|
||||
|
||||
16
R/history.R
16
R/history.R
@@ -15,21 +15,21 @@ NULL
|
||||
#' 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
|
||||
#' `updateQueryString(_yourNewQueryString_, mode = "push")`. The default
|
||||
#' `mode` for `updateQueryString` is `"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
|
||||
#' *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¶m2=value2} becomes \code{list(param1 =
|
||||
#' value1, param2 = value2)}. For \code{getUrlHash}, a character vector with
|
||||
#' the hash (including the leading \code{#} symbol).
|
||||
#' @return For `getQueryString`, a named list. For example, the query
|
||||
#' string `?param1=value1¶m2=value2` becomes `list(param1 =
|
||||
#' value1, param2 = value2)`. For `getUrlHash`, a character vector with
|
||||
#' the hash (including the leading `#` symbol).
|
||||
#'
|
||||
#' @seealso \code{\link{updateQueryString}}
|
||||
#' @seealso [updateQueryString()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
#'
|
||||
#' Ensure that a file-based HTML dependency (from the htmltools package) can be
|
||||
#' served over Shiny's HTTP server. This function works by using
|
||||
#' \code{\link{addResourcePath}} to map the HTML dependency's directory to a
|
||||
#' [addResourcePath()] to map the HTML dependency's directory to a
|
||||
#' URL.
|
||||
#'
|
||||
#' @param dependency A single HTML dependency object, created using
|
||||
#' \code{\link[htmltools]{htmlDependency}}. If the \code{src} value is named,
|
||||
#' then \code{href} and/or \code{file} names must be present.
|
||||
#' @param scrubFile If TRUE (the default), remove \code{src$file} for the
|
||||
#' [htmltools::htmlDependency()]. If the `src` value is named,
|
||||
#' then `href` and/or `file` names must be present.
|
||||
#' @param scrubFile If TRUE (the default), remove `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
|
||||
#' `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}.
|
||||
#' @return A single HTML dependency object that has an `href`-named element
|
||||
#' in its `src`.
|
||||
#' @export
|
||||
createWebDependency <- function(dependency, scrubFile = TRUE) {
|
||||
if (is.null(dependency))
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#' @export a br code div em h1 h2 h3 h4 h5 h6 hr HTML img p pre span strong
|
||||
#' @export includeCSS includeHTML includeMarkdown includeScript includeText
|
||||
#' @export is.singleton singleton
|
||||
#' @export tag tagAppendAttributes tagAppendChild tagAppendChildren tagList tags tagSetChildren withTags
|
||||
#' @import htmltools
|
||||
#' @export tags p h1 h2 h3 h4 h5 h6 a br div span pre code img strong em hr
|
||||
#' @export tag tagList tagAppendAttributes tagHasAttribute tagGetAttribute tagAppendChild tagAppendChildren tagSetChildren
|
||||
#' @export HTML
|
||||
#' @export includeHTML includeText includeMarkdown includeCSS includeScript
|
||||
#' @export singleton is.singleton
|
||||
#' @export validateCssUnit
|
||||
#' @export knit_print.html knit_print.shiny.tag knit_print.shiny.tag.list
|
||||
#' @export htmlTemplate suppressDependencies
|
||||
#' @export htmlTemplate
|
||||
#' @export suppressDependencies
|
||||
#' @export withTags
|
||||
NULL
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
#' Create an object representing click options
|
||||
#' Control interactive plot point events
|
||||
#'
|
||||
#' This generates an object representing click options, to be passed as the
|
||||
#' \code{click} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#' These functions give control over the `click`, `dblClick` and
|
||||
#' `hover` events generated by [imageOutput()] and [plotOutput()].
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_click"},
|
||||
#' then the click coordinates will be available as \code{input$plot_click}.
|
||||
#' @param clip Should the click area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive click events even when the mouse is outside
|
||||
#' the plotting area, as long as it is still inside the image.
|
||||
#' @param id Input value name. For example, if the value is `"plot_click"`,
|
||||
#' then the event data will be available as `input$plot_click`.
|
||||
#' @param clip Should the click area be clipped to the plotting area? If
|
||||
#' `FALSE`, then the server will receive click events even when the mouse is
|
||||
#' outside the plotting area, as long as it is still inside the image.
|
||||
#' @param delay For `dblClickOpts()`: the maximum delay (in ms) between a
|
||||
#' pair clicks for them to be counted as a double-click.
|
||||
#'
|
||||
#' For `hoverOpts()`: how long to delay (in ms) when debouncing or throttling
|
||||
#' before sending the mouse location to the server.
|
||||
#' @param delayType The type of algorithm for limiting the number of hover
|
||||
#' events. Use `"throttle"` to limit the number of hover events to one
|
||||
#' every `delay` milliseconds. Use `"debounce"` to suspend events
|
||||
#' while the cursor is moving, and wait until the cursor has been at rest for
|
||||
#' `delay` milliseconds before sending an event.
|
||||
#' @seealso [brushOpts()] for brushing events.
|
||||
#' @export
|
||||
clickOpts <- function(id = NULL, clip = TRUE) {
|
||||
#' @keywords internal
|
||||
clickOpts <- function(id, clip = TRUE) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
@@ -21,22 +32,9 @@ clickOpts <- function(id = NULL, clip = TRUE) {
|
||||
}
|
||||
|
||||
|
||||
#' Create an object representing double-click options
|
||||
#'
|
||||
#' This generates an object representing dobule-click options, to be passed as
|
||||
#' the \code{dblclick} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is
|
||||
#' \code{"plot_dblclick"}, then the click coordinates will be available as
|
||||
#' \code{input$plot_dblclick}.
|
||||
#' @param clip Should the click area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive double-click events even when the mouse is
|
||||
#' outside the plotting area, as long as it is still inside the image.
|
||||
#' @param delay Maximum delay (in ms) between a pair clicks for them to be
|
||||
#' counted as a double-click.
|
||||
#' @export
|
||||
dblclickOpts <- function(id = NULL, clip = TRUE, delay = 400) {
|
||||
#' @rdname clickOpts
|
||||
dblclickOpts <- function(id, clip = TRUE, delay = 400) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
@@ -47,29 +45,12 @@ dblclickOpts <- function(id = NULL, clip = TRUE, delay = 400) {
|
||||
)
|
||||
}
|
||||
|
||||
#' Create an object representing hover options
|
||||
#'
|
||||
#' This generates an object representing hovering options, to be passed as the
|
||||
#' \code{hover} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_hover"},
|
||||
#' then the hover coordinates will be available as \code{input$plot_hover}.
|
||||
#' @param delay How long to delay (in milliseconds) when debouncing or
|
||||
#' throttling, before sending the mouse location to the server.
|
||||
#' @param delayType The type of algorithm for limiting the number of hover
|
||||
#' events. Use \code{"throttle"} to limit the number of hover events to one
|
||||
#' every \code{delay} milliseconds. Use \code{"debounce"} to suspend events
|
||||
#' while the cursor is moving, and wait until the cursor has been at rest for
|
||||
#' \code{delay} milliseconds before sending an event.
|
||||
#' @param clip Should the hover area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive hover events even when the mouse is outside
|
||||
#' the plotting area, as long as it is still inside the image.
|
||||
#' @param nullOutside If \code{TRUE} (the default), the value will be set to
|
||||
#' \code{NULL} when the mouse exits the plotting area. If \code{FALSE}, the
|
||||
#' @param nullOutside If `TRUE` (the default), the value will be set to
|
||||
#' `NULL` when the mouse exits the plotting area. If `FALSE`, the
|
||||
#' value will stop changing when the cursor exits the plotting area.
|
||||
#' @export
|
||||
hoverOpts <- function(id = NULL, delay = 300,
|
||||
#' @rdname clickOpts
|
||||
hoverOpts <- function(id, delay = 300,
|
||||
delayType = c("debounce", "throttle"), clip = TRUE,
|
||||
nullOutside = TRUE) {
|
||||
if (is.null(id))
|
||||
@@ -87,37 +68,38 @@ hoverOpts <- function(id = NULL, delay = 300,
|
||||
#' Create an object representing brushing options
|
||||
#'
|
||||
#' This generates an object representing brushing options, to be passed as the
|
||||
#' \code{brush} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#' `brush` argument of [imageOutput()] or
|
||||
#' [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}. Multiple
|
||||
#' \code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
|
||||
#' @param id Input value name. For example, if the value is `"plot_brush"`,
|
||||
#' then the coordinates will be available as `input$plot_brush`. Multiple
|
||||
#' `imageOutput`/`plotOutput` calls may share the same `id`
|
||||
#' value; brushing one image or plot will cause any other brushes with the
|
||||
#' same \code{id} to disappear.
|
||||
#' same `id` to disappear.
|
||||
#' @param fill Fill color of the brush.
|
||||
#' @param stroke Outline color of the brush.
|
||||
#' @param opacity Opacity of the brush
|
||||
#' @param delay How long to delay (in milliseconds) when debouncing or
|
||||
#' throttling, before sending the brush data to the server.
|
||||
#' @param delayType The type of algorithm for limiting the number of brush
|
||||
#' events. Use \code{"throttle"} to limit the number of brush events to one
|
||||
#' every \code{delay} milliseconds. Use \code{"debounce"} to suspend events
|
||||
#' events. Use `"throttle"` to limit the number of brush events to one
|
||||
#' every `delay` milliseconds. Use `"debounce"` to suspend events
|
||||
#' while the cursor is moving, and wait until the cursor has been at rest for
|
||||
#' \code{delay} milliseconds before sending an event.
|
||||
#' `delay` milliseconds before sending an event.
|
||||
#' @param clip Should the brush area be clipped to the plotting area? If FALSE,
|
||||
#' then the user will be able to brush outside the plotting area, as long as
|
||||
#' it is still inside the image.
|
||||
#' @param direction The direction for brushing. If \code{"xy"}, the brush can be
|
||||
#' drawn and moved in both x and y directions. If \code{"x"}, or \code{"y"},
|
||||
#' @param direction The direction for brushing. If `"xy"`, the brush can be
|
||||
#' drawn and moved in both x and y directions. If `"x"`, or `"y"`,
|
||||
#' the brush wil work horizontally or vertically.
|
||||
#' @param resetOnNew When a new image is sent to the browser (via
|
||||
#' \code{\link{renderImage}}), should the brush be reset? The default,
|
||||
#' \code{FALSE}, is useful if you want to update the plot while keeping the
|
||||
#' brush. Using \code{TRUE} is useful if you want to clear the brush whenever
|
||||
#' [renderImage()]), should the brush be reset? The default,
|
||||
#' `FALSE`, is useful if you want to update the plot while keeping the
|
||||
#' brush. Using `TRUE` is useful if you want to clear the brush whenever
|
||||
#' the plot is updated.
|
||||
#' @seealso [clickOpts()] for clicking events.
|
||||
#' @export
|
||||
brushOpts <- function(id = NULL, fill = "#9cf", stroke = "#036",
|
||||
brushOpts <- function(id, fill = "#9cf", stroke = "#036",
|
||||
opacity = 0.25, delay = 300,
|
||||
delayType = c("debounce", "throttle"), clip = TRUE,
|
||||
direction = c("xy", "x", "y"),
|
||||
|
||||
@@ -1,59 +1,76 @@
|
||||
#' Find rows of data that are selected by a brush
|
||||
#' Find rows of data selected on an interactive plot.
|
||||
#'
|
||||
#' This function returns rows from a data frame which are under a brush used
|
||||
#' with \code{\link{plotOutput}}.
|
||||
#' @description
|
||||
#' `brushedPoints()` returns rows from a data frame which are under a brush.
|
||||
#' `nearPoints()` returns rows from a data frame which are near a click, hover,
|
||||
#' or double-click. Alternatively, set `allRows = TRUE` to return all rows from
|
||||
#' the input data with an additional column `selected_` that indicates which
|
||||
#' rows of the would be selected.
|
||||
#'
|
||||
#' It is also possible for this function to return all rows from the input data
|
||||
#' frame, but with an additional column \code{selected_}, which indicates which
|
||||
#' rows of the input data frame are selected by the brush (\code{TRUE} for
|
||||
#' selected, \code{FALSE} for not-selected). This is enabled by setting
|
||||
#' \code{allRows=TRUE} option.
|
||||
#' @section ggplot2:
|
||||
#' For plots created with ggplot2, it is not necessary to specify the
|
||||
#' column names to `xvar`, `yvar`, `panelvar1`, and `panelvar2` as that
|
||||
#' information can be automatically derived from the plot specification.
|
||||
#'
|
||||
#' The \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2}
|
||||
#' arguments specify which columns in the data correspond to the x variable, y
|
||||
#' variable, and panel variables of the plot. For example, if your plot is
|
||||
#' \code{plot(x=cars$speed, y=cars$dist)}, and your brush is named
|
||||
#' \code{"cars_brush"}, then you would use \code{brushedPoints(cars,
|
||||
#' input$cars_brush, "speed", "dist")}.
|
||||
#'
|
||||
#' For plots created with ggplot2, it should not be necessary to specify the
|
||||
#' column names; that information will already be contained in the brush,
|
||||
#' provided that variables are in the original data, and not computed. For
|
||||
#' example, with \code{ggplot(cars, aes(x=speed, y=dist)) + geom_point()}, you
|
||||
#' could use \code{brushedPoints(cars, input$cars_brush)}. If, however, you use
|
||||
#' a computed column, like \code{ggplot(cars, aes(x=speed/2, y=dist)) +
|
||||
#' geom_point()}, then it will not be able to automatically extract column names
|
||||
#' and filter on them. If you want to use this function to filter data, it is
|
||||
#' recommended that you not use computed columns; instead, modify the data
|
||||
#' Note, however, that this will not work if you use a computed column, like
|
||||
#' `aes(speed/2, dist))`. Instead, we recommend that you modify the data
|
||||
#' first, and then make the plot with "raw" columns in the modified data.
|
||||
#'
|
||||
#' If a specified x or y column is a factor, then it will be coerced to an
|
||||
#' integer vector. If it is a character vector, then it will be coerced to a
|
||||
#' factor and then integer vector. This means that the brush will be considered
|
||||
#' to cover a given character/factor value when it covers the center value.
|
||||
#' @section Brushing:
|
||||
#' If x or y column is a factor, then it will be coerced to an integer vector.
|
||||
#' If it is a character vector, then it will be coerced to a factor and then
|
||||
#' integer vector. This means that the brush will be considered to cover a
|
||||
#' given character/factor value when it covers the center value.
|
||||
#'
|
||||
#' If the brush is operating in just the x or y directions (e.g., with
|
||||
#' \code{brushOpts(direction = "x")}, then this function will filter out points
|
||||
#' `brushOpts(direction = "x")`, then this function will filter out points
|
||||
#' using just the x or y variable, whichever is appropriate.
|
||||
#'
|
||||
#' @param brush The data from a brush, such as \code{input$plot_brush}.
|
||||
#' @param df A data frame from which to select rows.
|
||||
#' @param xvar,yvar A string with the name of the variable on the x or y axis.
|
||||
#' This must also be the name of a column in \code{df}. If absent, then this
|
||||
#' function will try to infer the variable from the brush (only works for
|
||||
#' ggplot2).
|
||||
#' @param panelvar1,panelvar2 Each of these is a string with the name of a panel
|
||||
#' variable. For example, if with ggplot2, you facet on a variable called
|
||||
#' \code{cyl}, then you can use \code{"cyl"} here. However, specifying the
|
||||
#' panel variable should not be necessary with ggplot2; Shiny should be able
|
||||
#' to auto-detect the panel variable.
|
||||
#' @param allRows If \code{FALSE} (the default) return a data frame containing
|
||||
#' the selected rows. If \code{TRUE}, the input data frame will have a new
|
||||
#' column, \code{selected_}, which indicates whether the row was inside the
|
||||
#' brush (\code{TRUE}) or outside the brush (\code{FALSE}).
|
||||
#' @returns
|
||||
#' A data frame based on `df`, containing the observations selected by the
|
||||
#' brush or near the click event. For `nearPoints()`, the rows will be sorted
|
||||
#' by distance to the event.
|
||||
#'
|
||||
#' @seealso \code{\link{plotOutput}} for example usage.
|
||||
#' If `allRows = TRUE`, then all rows will returned, along with a new
|
||||
#' `selected_` column that indicates whether or not the point was selected.
|
||||
#' The output from `nearPoints()` will no longer be sorted, but you can
|
||||
#' set `addDist = TRUE` to get an additional column that gives the pixel
|
||||
#' distance to the pointer.
|
||||
#'
|
||||
#' @param df A data frame from which to select rows.
|
||||
#' @param brush,coordinfo The data from a brush or click/dblclick/hover event
|
||||
#' e.g. `input$plot_brush`, `input$plot_click`.
|
||||
#' @param xvar,yvar A string giving the name of the variable on the x or y axis.
|
||||
#' These are only required for base graphics, and must be the name of
|
||||
#' a column in `df`.
|
||||
#' @param panelvar1,panelvar2 A string giving the name of a panel variable.
|
||||
#' For expert use only; in most cases these will be automatically
|
||||
#' derived from the ggplot2 spec.
|
||||
#' @param allRows If `FALSE` (the default) return a data frame containing
|
||||
#' the selected rows. If `TRUE`, the input data frame will have a new
|
||||
#' column, `selected_`, which indicates whether the row was selected or not.
|
||||
#' @param threshold A maximum distance (in pixels) to the pointer location.
|
||||
#' Rows in the data frame will be selected if the distance to the pointer is
|
||||
#' less than `threshold`.
|
||||
#' @param maxpoints Maximum number of rows to return. If `NULL` (the default),
|
||||
#' will return all rows within the threshold distance.
|
||||
#' @param addDist If TRUE, add a column named `dist_` that contains the
|
||||
#' distance from the coordinate to the point, in pixels. When no pointer
|
||||
#' event has yet occurred, the value of `dist_` will be `NA`.
|
||||
#' @seealso [plotOutput()] for example usage.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Note that in practice, these examples would need to go in reactives
|
||||
#' # or observers.
|
||||
#'
|
||||
#' # This would select all points within 5 pixels of the click
|
||||
#' nearPoints(mtcars, input$plot_click)
|
||||
#'
|
||||
#' # Select just the nearest point within 10 pixels of the click
|
||||
#' nearPoints(mtcars, input$plot_click, threshold = 10, maxpoints = 1)
|
||||
#'
|
||||
#' }
|
||||
brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
|
||||
panelvar1 = NULL, panelvar2 = NULL,
|
||||
allRows = FALSE) {
|
||||
@@ -86,15 +103,16 @@ brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
|
||||
if (use_x) {
|
||||
if (is.null(xvar))
|
||||
stop("brushedPoints: not able to automatically infer `xvar` from brush")
|
||||
# Extract data values from the data frame
|
||||
x <- asNumber(df[[xvar]])
|
||||
keep_rows <- keep_rows & (x >= brush$xmin & x <= brush$xmax)
|
||||
if (!(xvar %in% names(df)))
|
||||
stop("brushedPoints: `xvar` ('", xvar ,"') not in names of input")
|
||||
keep_rows <- keep_rows & within_brush(df[[xvar]], brush, "x")
|
||||
}
|
||||
if (use_y) {
|
||||
if (is.null(yvar))
|
||||
stop("brushedPoints: not able to automatically infer `yvar` from brush")
|
||||
y <- asNumber(df[[yvar]])
|
||||
keep_rows <- keep_rows & (y >= brush$ymin & y <= brush$ymax)
|
||||
if (!(yvar %in% names(df)))
|
||||
stop("brushedPoints: `yvar` ('", yvar ,"') not in names of input")
|
||||
keep_rows <- keep_rows & within_brush(df[[yvar]], brush, "y")
|
||||
}
|
||||
|
||||
# Find which rows are matches for the panel vars (if present)
|
||||
@@ -118,6 +136,19 @@ brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
|
||||
# $ xmax : num 4.22
|
||||
# $ ymin : num 13.9
|
||||
# $ ymax : num 19.8
|
||||
# $ coords_css:List of 4
|
||||
# ..$ xmin: int 260
|
||||
# ..$ xmax: int 298
|
||||
# ..$ ymin: num 112
|
||||
# ..$ ymax: num 205
|
||||
# $ coords_img:List of 4
|
||||
# ..$ xmin: int 325
|
||||
# ..$ xmax: num 372
|
||||
# ..$ ymin: num 140
|
||||
# ..$ ymax: num 257
|
||||
# $ img_css_ratio:List of 2
|
||||
# ..$ x: num 1.25
|
||||
# ..$ y: num 1.25
|
||||
# $ mapping: Named list()
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.36
|
||||
@@ -143,6 +174,19 @@ brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
|
||||
# $ ymax : num 20.4
|
||||
# $ panelvar1: int 6
|
||||
# $ panelvar2: int 0
|
||||
# $ coords_css:List of 4
|
||||
# ..$ xmin: int 260
|
||||
# ..$ xmax: int 298
|
||||
# ..$ ymin: num 112
|
||||
# ..$ ymax: num 205
|
||||
# $ coords_img:List of 4
|
||||
# ..$ xmin: int 325
|
||||
# ..$ xmax: num 372
|
||||
# ..$ ymin: num 140
|
||||
# ..$ ymax: num 257
|
||||
# $ img_css_ratio:List of 2
|
||||
# ..$ x: num 1.25
|
||||
# ..$ y: num 1.25
|
||||
# $ mapping :List of 4
|
||||
# ..$ x : chr "wt"
|
||||
# ..$ y : chr "mpg"
|
||||
@@ -164,56 +208,8 @@ brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
|
||||
# $ direction: chr "y"
|
||||
|
||||
|
||||
#'Find rows of data that are near a click/hover/double-click
|
||||
#'
|
||||
#'This function returns rows from a data frame which are near a click, hover, or
|
||||
#'double-click, when used with \code{\link{plotOutput}}. The rows will be sorted
|
||||
#'by their distance to the mouse event.
|
||||
#'
|
||||
#'It is also possible for this function to return all rows from the input data
|
||||
#'frame, but with an additional column \code{selected_}, which indicates which
|
||||
#'rows of the input data frame are selected by the brush (\code{TRUE} for
|
||||
#'selected, \code{FALSE} for not-selected). This is enabled by setting
|
||||
#'\code{allRows=TRUE} option. If this is used, the resulting data frame will not
|
||||
#'be sorted by distance to the mouse event.
|
||||
#'
|
||||
#'The \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2} arguments
|
||||
#'specify which columns in the data correspond to the x variable, y variable,
|
||||
#'and panel variables of the plot. For example, if your plot is
|
||||
#'\code{plot(x=cars$speed, y=cars$dist)}, and your click variable is named
|
||||
#'\code{"cars_click"}, then you would use \code{nearPoints(cars,
|
||||
#'input$cars_brush, "speed", "dist")}.
|
||||
#'
|
||||
#'@inheritParams brushedPoints
|
||||
#'@param coordinfo The data from a mouse event, such as \code{input$plot_click}.
|
||||
#'@param threshold A maxmimum distance to the click point; rows in the data
|
||||
#' frame where the distance to the click is less than \code{threshold} will be
|
||||
#' returned.
|
||||
#'@param maxpoints Maximum number of rows to return. If NULL (the default),
|
||||
#' return all rows that are within the threshold distance.
|
||||
#'@param addDist If TRUE, add a column named \code{dist_} that contains the
|
||||
#' distance from the coordinate to the point, in pixels. When no mouse event
|
||||
#' has yet occured, the value of \code{dist_} will be \code{NA}.
|
||||
#'@param allRows If \code{FALSE} (the default) return a data frame containing
|
||||
#' the selected rows. If \code{TRUE}, the input data frame will have a new
|
||||
#' column, \code{selected_}, which indicates whether the row was inside the
|
||||
#' selected by the mouse event (\code{TRUE}) or not (\code{FALSE}).
|
||||
#'
|
||||
#'@seealso \code{\link{plotOutput}} for more examples.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Note that in practice, these examples would need to go in reactives
|
||||
#' # or observers.
|
||||
#'
|
||||
#' # This would select all points within 5 pixels of the click
|
||||
#' nearPoints(mtcars, input$plot_click)
|
||||
#'
|
||||
#' # Select just the nearest point within 10 pixels of the click
|
||||
#' nearPoints(mtcars, input$plot_click, threshold = 10, maxpoints = 1)
|
||||
#'
|
||||
#' }
|
||||
#'@export
|
||||
#' @export
|
||||
#' @rdname brushedPoints
|
||||
nearPoints <- function(df, coordinfo, xvar = NULL, yvar = NULL,
|
||||
panelvar1 = NULL, panelvar2 = NULL,
|
||||
threshold = 5, maxpoints = NULL, addDist = FALSE,
|
||||
@@ -245,18 +241,29 @@ nearPoints <- function(df, coordinfo, xvar = NULL, yvar = NULL,
|
||||
if (is.null(yvar))
|
||||
stop("nearPoints: not able to automatically infer `yvar` from coordinfo")
|
||||
|
||||
if (!(xvar %in% names(df)))
|
||||
stop("nearPoints: `xvar` ('", xvar ,"') not in names of input")
|
||||
if (!(yvar %in% names(df)))
|
||||
stop("nearPoints: `yvar` ('", yvar ,"') not in names of input")
|
||||
|
||||
# Extract data values from the data frame
|
||||
x <- asNumber(df[[xvar]])
|
||||
y <- asNumber(df[[yvar]])
|
||||
x <- asNumber(df[[xvar]], coordinfo$domain$discrete_limits$x)
|
||||
y <- asNumber(df[[yvar]], coordinfo$domain$discrete_limits$y)
|
||||
|
||||
# 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 <- coordinfo$coords_img
|
||||
|
||||
# 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$img_css_ratio$x,
|
||||
y = (data_img$y - point_img$y) / coordinfo$img_css_ratio$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,56 +305,90 @@ 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
|
||||
# $ coords_css:List of 2
|
||||
# ..$ x: int 286
|
||||
# ..$ y: int 192
|
||||
# $ coords_img:List of 2
|
||||
# ..$ x: num 358
|
||||
# ..$ y: int 240
|
||||
# $ img_css_ratio:List of 2
|
||||
# ..$ x: num 1.25
|
||||
# ..$ y: num 1.25
|
||||
# $ 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
|
||||
# $ coords_css:List of 2
|
||||
# ..$ x: int 286
|
||||
# ..$ y: int 192
|
||||
# $ coords_img:List of 2
|
||||
# ..$ x: num 358
|
||||
# ..$ y: int 240
|
||||
# $ img_css_ratio:List of 2
|
||||
# ..$ x: num 1.25
|
||||
# ..$ y: num 1.25
|
||||
# $ 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
|
||||
|
||||
# Helper to determine if data values are within the limits of
|
||||
# an input brush
|
||||
within_brush <- function(vals, brush, var = "x") {
|
||||
var <- match.arg(var, c("x", "y"))
|
||||
vals <- asNumber(vals, brush$domain$discrete_limits[[var]])
|
||||
# It's possible for a non-missing data values to not
|
||||
# map to the axis limits, for example:
|
||||
# https://github.com/rstudio/shiny/pull/2410#issuecomment-488100881
|
||||
!is.na(vals) &
|
||||
vals >= brush[[paste0(var, "min")]] &
|
||||
vals <= brush[[paste0(var, "max")]]
|
||||
}
|
||||
|
||||
# Coerce various types of variables to numbers. This works for Date, POSIXt,
|
||||
# characters, and factors. Used because the mouse coords are numeric.
|
||||
asNumber <- function(x) {
|
||||
# The `levels` argument should be used when mapping this variable to
|
||||
# a known set of discrete levels, which is needed for ggplot2 since
|
||||
# it allows you to control ordering and possible values of a discrete
|
||||
# positional scale (#2410)
|
||||
asNumber <- function(x, levels = NULL) {
|
||||
if (length(levels)) return(match(x, levels))
|
||||
if (is.character(x)) x <- as.factor(x)
|
||||
if (is.factor(x)) x <- as.integer(x)
|
||||
as.numeric(x)
|
||||
|
||||
@@ -1,17 +1,47 @@
|
||||
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")) {
|
||||
# shiny.useragg is an experimental option that isn't officially supported or
|
||||
# documented. It's here in the off chance that someone really wants
|
||||
# to use ragg (say, instead of showtext, for custom font rendering).
|
||||
# In the next shiny release, this option will likely be superseded in
|
||||
# favor of a fully customizable graphics device option
|
||||
if ((getOption('shiny.useragg') %OR% FALSE) && is_available("ragg")) {
|
||||
pngfun <- ragg::agg_png
|
||||
} else if (capabilities("aqua")) {
|
||||
# i.e., png(type = 'quartz')
|
||||
pngfun <- grDevices::png
|
||||
} else if ((getOption('shiny.usecairo') %OR% TRUE) &&
|
||||
nchar(system.file(package = "Cairo"))) {
|
||||
} else if ((getOption('shiny.usecairo') %OR% TRUE) && is_available("Cairo")) {
|
||||
pngfun <- Cairo::CairoPNG
|
||||
} else {
|
||||
# i.e., png(type = 'cairo')
|
||||
pngfun <- grDevices::png
|
||||
}
|
||||
|
||||
pngfun(filename=filename, width=width, height=height, res=res, ...)
|
||||
args <- rlang::list2(filename=filename, width=width, height=height, res=res, ...)
|
||||
|
||||
# Set a smarter default for the device's bg argument (based on thematic's global state).
|
||||
# Note that, technically, this is really only needed for CairoPNG, since the other
|
||||
# devices allow their bg arg to be overridden by par(bg=...), which thematic does prior
|
||||
# to plot-time, but it shouldn't hurt to inform other the device directly as well
|
||||
if (is.null(args$bg) && isNamespaceLoaded("thematic")) {
|
||||
# TODO: use :: once thematic is on CRAN
|
||||
args$bg <- utils::getFromNamespace("thematic_get_option", "thematic")("bg", "white")
|
||||
# auto vals aren't resolved until plot time, so if we see one, resolve it
|
||||
if (isTRUE("auto" == args$bg)) {
|
||||
args$bg <- getCurrentOutputInfo()[["bg"]]()
|
||||
}
|
||||
}
|
||||
|
||||
# Handle both bg and background device arg
|
||||
# https://github.com/r-lib/ragg/issues/35
|
||||
fmls <- names(formals(pngfun))
|
||||
if (("background" %in% fmls) && (!"bg" %in% fmls)) {
|
||||
if (is.null(args$background)) {
|
||||
args$background <- args$bg
|
||||
}
|
||||
args$bg <- NULL
|
||||
}
|
||||
|
||||
do.call(pngfun, args)
|
||||
# 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.
|
||||
@@ -31,29 +61,29 @@ startPNG <- function(filename, width, height, res, ...) {
|
||||
#' Run a plotting function and save the output as a PNG
|
||||
#'
|
||||
#' This function returns the name of the PNG file that it generates. In
|
||||
#' essence, it calls \code{png()}, then \code{func()}, then \code{dev.off()}.
|
||||
#' So \code{func} must be a function that will generate a plot when used this
|
||||
#' essence, it calls `png()`, then `func()`, then `dev.off()`.
|
||||
#' So `func` must be a function that will generate a plot when used this
|
||||
#' way.
|
||||
#'
|
||||
#' For output, it will try to use the following devices, in this order:
|
||||
#' quartz (via \code{\link[grDevices]{png}}), then \code{\link[Cairo]{CairoPNG}},
|
||||
#' and finally \code{\link[grDevices]{png}}. This is in order of quality of
|
||||
#' output. Notably, plain \code{png} output on Linux and Windows may not
|
||||
#' quartz (via [grDevices::png()]), then [Cairo::CairoPNG()],
|
||||
#' and finally [grDevices::png()]. This is in order of quality of
|
||||
#' output. Notably, plain `png` output on Linux and Windows may not
|
||||
#' antialias some point shapes, resulting in poor quality output.
|
||||
#'
|
||||
#' In some cases, \code{Cairo()} provides output that looks worse than
|
||||
#' \code{png()}. To disable Cairo output for an app, use
|
||||
#' \code{options(shiny.usecairo=FALSE)}.
|
||||
#' In some cases, `Cairo()` provides output that looks worse than
|
||||
#' `png()`. To disable Cairo output for an app, use
|
||||
#' `options(shiny.usecairo=FALSE)`.
|
||||
#'
|
||||
#' @param func A function that generates a plot.
|
||||
#' @param filename The name of the output file. Defaults to a temp file with
|
||||
#' extension \code{.png}.
|
||||
#' extension `.png`.
|
||||
#' @param width Width in pixels.
|
||||
#' @param height Height in pixels.
|
||||
#' @param res Resolution in pixels per inch. This value is passed to
|
||||
#' \code{\link[grDevices]{png}}. Note that this affects the resolution of PNG rendering in
|
||||
#' [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}}.
|
||||
#' @param ... Arguments to be passed through to [grDevices::png()].
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#' @export
|
||||
plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#' @inheritParams textInput
|
||||
#' @param label The contents of the button or link--usually a text label, but
|
||||
#' you could also use any other HTML, like an image.
|
||||
#' @param icon An optional \code{\link{icon}} to appear on the button.
|
||||
#' @param icon An optional [icon()] to appear on the button.
|
||||
#' @param ... Named attributes to be applied to the button or link.
|
||||
#'
|
||||
#' @family input elements
|
||||
@@ -16,7 +16,7 @@
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("obs", "Number of observations", 0, 1000, 500),
|
||||
#' actionButton("goButton", "Go!"),
|
||||
#' actionButton("goButton", "Go!", class = "btn-success"),
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#'
|
||||
@@ -36,7 +36,18 @@
|
||||
#'
|
||||
#' }
|
||||
#'
|
||||
#' @seealso \code{\link{observeEvent}} and \code{\link{eventReactive}}
|
||||
#' ## Example of adding extra class values
|
||||
#' actionButton("largeButton", "Large Primary Button", class = "btn-primary btn-lg")
|
||||
#' actionLink("infoLink", "Information Link", class = "btn-info")
|
||||
#'
|
||||
#' @seealso [observeEvent()] and [eventReactive()]
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' An integer of class `"shinyActionButtonValue"`. This class differs from
|
||||
#' ordinary integers in that a value of 0 is considered "falsy".
|
||||
#' This implies two things:
|
||||
#' * Event handlers (e.g., [observeEvent()], [eventReactive()]) won't execute on initial load.
|
||||
#' * Input validation (e.g., [req()], [need()]) will fail on initial load.
|
||||
#' @export
|
||||
actionButton <- function(inputId, label, icon = NULL, width = NULL, ...) {
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
#' Create a checkbox that can be used to specify logical values.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param value Initial value (\code{TRUE} or \code{FALSE}).
|
||||
#' @param value Initial value (`TRUE` or `FALSE`).
|
||||
#' @return A checkbox control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{checkboxGroupInput}}, \code{\link{updateCheckboxInput}}
|
||||
#' @seealso [checkboxGroupInput()], [updateCheckboxInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -22,6 +22,10 @@
|
||||
#' }
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' `TRUE` if checked, `FALSE` otherwise.
|
||||
#'
|
||||
#' @export
|
||||
checkboxInput <- function(inputId, label, value = FALSE, width = NULL) {
|
||||
|
||||
|
||||
@@ -7,25 +7,25 @@
|
||||
#' @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. If
|
||||
#' this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
#' this argument is provided, then `choiceNames` and `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 inline If `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}
|
||||
#' choice (for this reason, `choiceNames` and `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
|
||||
#' provided, then the other *must* be provided and `choices`
|
||||
#' *must not* be provided. The advantage of using both of these over
|
||||
#' a named list for `choices` is that `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}}
|
||||
#' @seealso [checkboxInput()], [updateCheckboxGroupInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -67,6 +67,9 @@
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @section Server value:
|
||||
#' Character vector of values corresponding to the boxes that are checked.
|
||||
#'
|
||||
#' @export
|
||||
checkboxGroupInput <- function(inputId, label, choices = NULL, selected = NULL,
|
||||
inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) {
|
||||
@@ -94,7 +97,7 @@ checkboxGroupInput <- function(inputId, label, choices = NULL, selected = NULL,
|
||||
tags$div(id = inputId,
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
class = divClass,
|
||||
controlLabel(inputId, label),
|
||||
shinyInputLabel(inputId, label),
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
131
R/input-date.R
131
R/input-date.R
@@ -3,32 +3,32 @@
|
||||
#' Creates a text input which, when clicked on, brings up a calendar that
|
||||
#' the user can click on to select dates.
|
||||
#'
|
||||
#' The date \code{format} string specifies how the date will be displayed in
|
||||
#' The date `format` string specifies how the date will be displayed in
|
||||
#' the browser. It allows the following values:
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \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 (1-12)
|
||||
#' \item \code{M} Abbreviated month name
|
||||
#' \item \code{MM} Full month name
|
||||
#' \item \code{dd} Day of month with leading zero
|
||||
#' \item \code{d} Day of month without leading zero
|
||||
#' \item \code{D} Abbreviated weekday name
|
||||
#' \item \code{DD} Full weekday name
|
||||
#' \item `yy` Year without century (12)
|
||||
#' \item `yyyy` Year with century (2012)
|
||||
#' \item `mm` Month number, with leading zero (01-12)
|
||||
#' \item `m` Month number, without leading zero (1-12)
|
||||
#' \item `M` Abbreviated month name
|
||||
#' \item `MM` Full month name
|
||||
#' \item `dd` Day of month with leading zero
|
||||
#' \item `d` Day of month without leading zero
|
||||
#' \item `D` Abbreviated weekday name
|
||||
#' \item `DD` Full weekday name
|
||||
#' }
|
||||
#'
|
||||
#' @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
|
||||
#' `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.
|
||||
#' `yyyy-mm-dd` format.
|
||||
#' @param max The maximum allowed date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' `yyyy-mm-dd` format.
|
||||
#' @param format The format of the date to display in the browser. Defaults to
|
||||
#' \code{"yyyy-mm-dd"}.
|
||||
#' `"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 weekstart Which day is the start of the week. Should be an integer
|
||||
@@ -43,9 +43,13 @@
|
||||
#' "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 `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}}
|
||||
#' @seealso [dateRangeInput()], [updateDateInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -70,21 +74,32 @@
|
||||
#'
|
||||
#' # Start with decade view instead of default month view
|
||||
#' dateInput("date6", "Date:",
|
||||
#' startview = "decade")
|
||||
#' 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) { })
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' A [Date] vector of length 1.
|
||||
#'
|
||||
#' @export
|
||||
dateInput <- function(inputId, label, value = NULL, min = NULL, max = NULL,
|
||||
format = "yyyy-mm-dd", startview = "month", weekstart = 0, language = "en",
|
||||
width = NULL, autoclose = TRUE) {
|
||||
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")
|
||||
value <- dateYMD(value, "value")
|
||||
min <- dateYMD(min, "min")
|
||||
max <- dateYMD(max, "max")
|
||||
datesdisabled <- dateYMD(datesdisabled, "datesdisabled")
|
||||
|
||||
value <- restoreInput(id = inputId, default = value)
|
||||
|
||||
@@ -92,9 +107,13 @@ dateInput <- function(inputId, label, value = NULL, min = NULL, max = NULL,
|
||||
class = "shiny-date-input form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
|
||||
controlLabel(inputId, label),
|
||||
shinyInputLabel(inputId, label),
|
||||
tags$input(type = "text",
|
||||
class = "form-control",
|
||||
# `aria-labelledby` attribute is required for accessibility to avoid doubled labels (#2951).
|
||||
`aria-labelledby` = paste0(inputId, "-label"),
|
||||
# title attribute is announced for screen readers for date format.
|
||||
title = paste("Date format:", format),
|
||||
`data-date-language` = language,
|
||||
`data-date-week-start` = weekstart,
|
||||
`data-date-format` = format,
|
||||
@@ -102,21 +121,55 @@ dateInput <- function(inputId, label, value = NULL, min = NULL, max = NULL,
|
||||
`data-min-date` = min,
|
||||
`data-max-date` = max,
|
||||
`data-initial-date` = value,
|
||||
`data-date-autoclose` = if (autoclose) "true" else "false"
|
||||
`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()
|
||||
)
|
||||
}
|
||||
|
||||
datePickerDependency <- htmlDependency(
|
||||
"bootstrap-datepicker", "1.6.4", c(href = "shared/datepicker"),
|
||||
script = "js/bootstrap-datepicker.min.js",
|
||||
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>"
|
||||
)
|
||||
|
||||
datePickerVersion <- "1.9.0"
|
||||
|
||||
datePickerDependency <- function(theme) {
|
||||
list(
|
||||
htmlDependency(
|
||||
name = "bootstrap-datepicker-js",
|
||||
version = datePickerVersion,
|
||||
src = c(href = "shared/datepicker"),
|
||||
script = "js/bootstrap-datepicker.min.js",
|
||||
# Need to enable noConflict mode. See #1346.
|
||||
head = "<script>(function() {
|
||||
var datepicker = $.fn.datepicker.noConflict();
|
||||
$.fn.bsDatepicker = datepicker;
|
||||
})();
|
||||
</script>"
|
||||
),
|
||||
bootstraplib::bs_dependency_defer(datePickerCSS)
|
||||
)
|
||||
}
|
||||
|
||||
datePickerCSS <- function(theme) {
|
||||
if (!is_bs_theme(theme)) {
|
||||
return(htmlDependency(
|
||||
name = "bootstrap-datepicker-css",
|
||||
version = datePickerVersion,
|
||||
src = c(href = "shared/datepicker"),
|
||||
stylesheet = "css/bootstrap-datepicker3.min.css"
|
||||
))
|
||||
}
|
||||
|
||||
scss_file <- system.file(package = "shiny", "www/shared/datepicker/scss/build3.scss")
|
||||
|
||||
bootstraplib::bs_dependency(
|
||||
input = sass::sass_file(scss_file),
|
||||
theme = theme,
|
||||
name = "bootstrap-datepicker",
|
||||
version = datePickerVersion,
|
||||
cache_key_extra = utils::packageVersion("shiny")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,33 +3,33 @@
|
||||
#' Creates a pair of text inputs which, when clicked on, bring up calendars that
|
||||
#' the user can click on to select dates.
|
||||
#'
|
||||
#' The date \code{format} string specifies how the date will be displayed in
|
||||
#' The date `format` string specifies how the date will be displayed in
|
||||
#' the browser. It allows the following values:
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \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 (1-12)
|
||||
#' \item \code{M} Abbreviated month name
|
||||
#' \item \code{MM} Full month name
|
||||
#' \item \code{dd} Day of month with leading zero
|
||||
#' \item \code{d} Day of month without leading zero
|
||||
#' \item \code{D} Abbreviated weekday name
|
||||
#' \item \code{DD} Full weekday name
|
||||
#' \item `yy` Year without century (12)
|
||||
#' \item `yyyy` Year with century (2012)
|
||||
#' \item `mm` Month number, with leading zero (01-12)
|
||||
#' \item `m` Month number, without leading zero (1-12)
|
||||
#' \item `M` Abbreviated month name
|
||||
#' \item `MM` Full month name
|
||||
#' \item `dd` Day of month with leading zero
|
||||
#' \item `d` Day of month without leading zero
|
||||
#' \item `D` Abbreviated weekday name
|
||||
#' \item `DD` Full weekday name
|
||||
#' }
|
||||
#'
|
||||
#' @inheritParams dateInput
|
||||
#' @param start The initial start date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
|
||||
#' `yyyy-mm-dd` format. If NULL (the default), will use the current
|
||||
#' date in the client's time zone.
|
||||
#' @param end The initial end date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
|
||||
#' `yyyy-mm-dd` format. If NULL (the default), will use the current
|
||||
#' date in the client's time zone.
|
||||
#' @param separator String to display between the start and end input boxes.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{dateInput}}, \code{\link{updateDateRangeInput}}
|
||||
#' @seealso [dateInput()], [updateDateRangeInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -70,18 +70,20 @@
|
||||
#'
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' A [Date] vector of length 2.
|
||||
#'
|
||||
#' @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,
|
||||
autoclose = TRUE) {
|
||||
|
||||
# If start and end are date objects, convert to a string with yyyy-mm-dd format
|
||||
# Same for min and max
|
||||
if (inherits(start, "Date")) start <- format(start, "%Y-%m-%d")
|
||||
if (inherits(end, "Date")) end <- format(end, "%Y-%m-%d")
|
||||
if (inherits(min, "Date")) min <- format(min, "%Y-%m-%d")
|
||||
if (inherits(max, "Date")) max <- format(max, "%Y-%m-%d")
|
||||
start <- dateYMD(start, "start")
|
||||
end <- dateYMD(end, "end")
|
||||
min <- dateYMD(min, "min")
|
||||
max <- dateYMD(max, "max")
|
||||
|
||||
restored <- restoreInput(id = inputId, default = list(start, end))
|
||||
start <- restored[[1]]
|
||||
@@ -92,12 +94,16 @@ dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
|
||||
class = "shiny-date-range-input form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
|
||||
controlLabel(inputId, label),
|
||||
shinyInputLabel(inputId, label),
|
||||
# input-daterange class is needed for dropdown behavior
|
||||
div(class = "input-daterange input-group",
|
||||
div(class = "input-daterange input-group input-group-sm",
|
||||
tags$input(
|
||||
class = "input-sm form-control",
|
||||
class = "form-control",
|
||||
type = "text",
|
||||
# `aria-labelledby` attribute is required for accessibility to avoid doubled labels (#2951).
|
||||
`aria-labelledby` = paste0(inputId, "-label"),
|
||||
# title attribute is announced for screen readers for date format.
|
||||
title = paste("Date format:", format),
|
||||
`data-date-language` = language,
|
||||
`data-date-week-start` = weekstart,
|
||||
`data-date-format` = format,
|
||||
@@ -107,10 +113,19 @@ dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
|
||||
`data-initial-date` = start,
|
||||
`data-date-autoclose` = if (autoclose) "true" else "false"
|
||||
),
|
||||
span(class = "input-group-addon", separator),
|
||||
# input-group-prepend and input-group-append are for bootstrap 4 forward compat
|
||||
span(class = "input-group-addon input-group-prepend input-group-append",
|
||||
span(class = "input-group-text",
|
||||
separator
|
||||
)
|
||||
),
|
||||
tags$input(
|
||||
class = "input-sm form-control",
|
||||
class = "form-control",
|
||||
type = "text",
|
||||
# `aria-labelledby` attribute is required for accessibility to avoid doubled labels (#2951).
|
||||
`aria-labelledby` = paste0(inputId, "-label"),
|
||||
# title attribute is announced for screen readers for date format.
|
||||
title = paste("Date format:", format),
|
||||
`data-date-language` = language,
|
||||
`data-date-week-start` = weekstart,
|
||||
`data-date-format` = format,
|
||||
@@ -122,6 +137,6 @@ dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
|
||||
)
|
||||
)
|
||||
),
|
||||
datePickerDependency
|
||||
datePickerDependency()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,30 +3,23 @@
|
||||
#' Create a file upload control that can be used to upload one or more files.
|
||||
#'
|
||||
#' Whenever a file upload completes, the corresponding input variable is set
|
||||
#' to a dataframe. This dataframe contains one row for each selected file, and
|
||||
#' the following columns:
|
||||
#' \describe{
|
||||
#' \item{\code{name}}{The filename provided by the web browser. This is
|
||||
#' \strong{not} the path to read to get at the actual data that was uploaded
|
||||
#' (see
|
||||
#' \code{datapath} column).}
|
||||
#' \item{\code{size}}{The size of the uploaded data, in
|
||||
#' bytes.}
|
||||
#' \item{\code{type}}{The MIME type reported by the browser (for example,
|
||||
#' \code{text/plain}), or empty string if the browser didn't know.}
|
||||
#' \item{\code{datapath}}{The path to a temp file that contains the data that was
|
||||
#' uploaded. This file may be deleted if the user performs another upload
|
||||
#' operation.}
|
||||
#' }
|
||||
#' to a dataframe. See the `Server value` section.
|
||||
#'
|
||||
#' @family input elements
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param multiple Whether the user should be allowed to select and upload
|
||||
#' multiple files at once. \bold{Does not work on older browsers, including
|
||||
#' 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.
|
||||
#' multiple files at once. **Does not work on older browsers, including
|
||||
#' Internet Explorer 9 and earlier.**
|
||||
#' @param accept A character vector of "unique file type specifiers" which
|
||||
#' gives the browser a hint as to the type of file the server expects.
|
||||
#' Many browsers use this prevent the user from selecting an invalid file.
|
||||
#'
|
||||
#' A unique file type specifier can be:
|
||||
#' * A case insensitive extension like `.csv` or `.rds`.
|
||||
#' * A valid MIME type, like `text/plain` or `application/pdf`
|
||||
#' * One of `audio/*`, `video/*`, or `image/*` meaning any audio, video,
|
||||
#' or image type, respectively.
|
||||
#' @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.
|
||||
@@ -38,13 +31,7 @@
|
||||
#' ui <- fluidPage(
|
||||
#' sidebarLayout(
|
||||
#' sidebarPanel(
|
||||
#' fileInput("file1", "Choose CSV File",
|
||||
#' accept = c(
|
||||
#' "text/csv",
|
||||
#' "text/comma-separated-values,text/plain",
|
||||
#' ".csv")
|
||||
#' ),
|
||||
#' tags$hr(),
|
||||
#' fileInput("file1", "Choose CSV File", accept = ".csv"),
|
||||
#' checkboxInput("header", "Header", TRUE)
|
||||
#' ),
|
||||
#' mainPanel(
|
||||
@@ -55,22 +42,35 @@
|
||||
#'
|
||||
#' 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
|
||||
#' file <- input$file1
|
||||
#' ext <- tools::file_ext(file$datapath)
|
||||
#'
|
||||
#' if (is.null(inFile))
|
||||
#' return(NULL)
|
||||
#' req(file)
|
||||
#' validate(need(ext == "csv", "Please upload a csv file"))
|
||||
#'
|
||||
#' read.csv(inFile$datapath, header = input$header)
|
||||
#' read.csv(file$datapath, header = input$header)
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' A `data.frame` that contains one row for each selected file, and following columns:
|
||||
#' \describe{
|
||||
#' \item{`name`}{The filename provided by the web browser. This is
|
||||
#' **not** the path to read to get at the actual data that was uploaded
|
||||
#' (see
|
||||
#' `datapath` column).}
|
||||
#' \item{`size`}{The size of the uploaded data, in
|
||||
#' bytes.}
|
||||
#' \item{`type`}{The MIME type reported by the browser (for example,
|
||||
#' `text/plain`), or empty string if the browser didn't know.}
|
||||
#' \item{`datapath`}{The path to a temp file that contains the data that was
|
||||
#' uploaded. This file may be deleted if the user performs another upload
|
||||
#' operation.}
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
|
||||
width = NULL, buttonLabel = "Browse...", placeholder = "No file selected") {
|
||||
@@ -91,7 +91,8 @@ fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
|
||||
id = inputId,
|
||||
name = inputId,
|
||||
type = "file",
|
||||
style = "display: none;",
|
||||
# Don't use "display: none;" style, which causes keyboard accessibility issue; instead use the following workaround: https://css-tricks.com/places-its-tempting-to-use-display-none-but-dont/
|
||||
style = "position: absolute !important; top: -99999px !important; left: -99999px !important;",
|
||||
`data-restore` = restoredValue
|
||||
)
|
||||
|
||||
@@ -103,10 +104,11 @@ fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label),
|
||||
shinyInputLabel(inputId, label),
|
||||
|
||||
div(class = "input-group",
|
||||
tags$label(class = "input-group-btn",
|
||||
# input-group-prepend is for bootstrap 4 compat
|
||||
tags$label(class = "input-group-btn input-group-prepend",
|
||||
span(class = "btn btn-default btn-file",
|
||||
buttonLabel,
|
||||
inputTag
|
||||
@@ -119,7 +121,7 @@ fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
|
||||
|
||||
tags$div(
|
||||
id=paste(inputId, "_progress", sep=""),
|
||||
class="progress progress-striped active shiny-file-input-progress",
|
||||
class="progress active shiny-file-input-progress",
|
||||
tags$div(class="progress-bar")
|
||||
)
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#' @return A numeric input control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateNumericInput}}
|
||||
#' @seealso [updateNumericInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -24,6 +24,10 @@
|
||||
#' }
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' A numeric vector of length 1.
|
||||
#'
|
||||
#' @export
|
||||
numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA,
|
||||
width = NULL) {
|
||||
@@ -42,7 +46,7 @@ numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA,
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label, `for` = inputId),
|
||||
shinyInputLabel(inputId, label),
|
||||
inputTag
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
#' @return A text input control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateTextInput}}
|
||||
#' @seealso [updateTextInput()]
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' A character string of the password input. The default value is `""`
|
||||
#' unless `value` is provided.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -30,7 +34,7 @@ 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),
|
||||
shinyInputLabel(inputId, label),
|
||||
tags$input(id = inputId, type="password", class="form-control", value=value,
|
||||
placeholder = placeholder)
|
||||
)
|
||||
|
||||
@@ -3,33 +3,33 @@
|
||||
#' 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
|
||||
#' the radio buttons to have no options selected by using `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" = "")}.
|
||||
#' having the first of your choices be `c("None selected" = "")`.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param choices List of values to select from (if elements of the list are
|
||||
#' named then that name rather than the value is displayed to the user). If
|
||||
#' this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
#' must not be provided, and vice-versa. 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)
|
||||
#' this argument is provided, then `choiceNames` and `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 it
|
||||
#' defaults to the first item in `choices`. To start with no items selected,
|
||||
#' use `character(0)`.
|
||||
#' @param inline If `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.
|
||||
#' this reason, `choiceNames` and `choiceValues` must have the same length).
|
||||
#' If either of these arguments is provided, then the other *must* be provided
|
||||
#' and `choices` *must not* be provided. The advantage of using both of these
|
||||
#' over a named list for `choices` is that `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}}
|
||||
#' @seealso [updateRadioButtons()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -80,6 +80,11 @@
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value:
|
||||
#'
|
||||
#' A character string containing the value of the selected button.
|
||||
#'
|
||||
#' @export
|
||||
radioButtons <- function(inputId, label, choices = NULL, selected = NULL,
|
||||
inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) {
|
||||
@@ -102,7 +107,7 @@ radioButtons <- function(inputId, label, choices = NULL, selected = NULL,
|
||||
tags$div(id = inputId,
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
class = divClass,
|
||||
controlLabel(inputId, label),
|
||||
shinyInputLabel(inputId, label),
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
216
R/input-select.R
216
R/input-select.R
@@ -3,37 +3,44 @@
|
||||
#' Create a select list that can be used to choose a single or multiple items
|
||||
#' from a list of values.
|
||||
#'
|
||||
#' By default, \code{selectInput()} 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}.
|
||||
#' By default, `selectInput()` and `selectizeInput()` use the JavaScript library
|
||||
#' \pkg{selectize.js} (<https://github.com/selectize/selectize.js>) instead of
|
||||
#' the basic select input element. To use the standard HTML select input
|
||||
#' element, use `selectInput()` with `selectize=FALSE`.
|
||||
#'
|
||||
#' In selectize mode, if the first element in \code{choices} has a value of
|
||||
#' \code{""}, its name will be treated as a placeholder prompt. For example:
|
||||
#' \code{selectInput("letter", "Letter", c("Choose one" = "", LETTERS))}
|
||||
#' In selectize mode, if the first element in `choices` has a value of `""`, its
|
||||
#' name will be treated as a placeholder prompt. For example:
|
||||
#' `selectInput("letter", "Letter", c("Choose one" = "", LETTERS))`
|
||||
#'
|
||||
#' **Performance note:** `selectInput()` and `selectizeInput()` can slow down
|
||||
#' significantly when thousands of choices are used; with legacy browsers like
|
||||
#' Internet Explorer, the user interface may hang for many seconds. For large
|
||||
#' numbers of choices, Shiny offers a "server-side selectize" option that
|
||||
#' massively improves performance and efficiency; see
|
||||
#' [this selectize article](https://shiny.rstudio.com/articles/selectize.html)
|
||||
#' on the Shiny Dev Center for details.
|
||||
#'
|
||||
#' @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.
|
||||
#' 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.
|
||||
#' named, then that name --- rather than the value --- is displayed to the
|
||||
#' user. It's also possible to group related inputs by providing a named list
|
||||
#' whose elements are (either named or unnamed) lists, vectors, or factors. In
|
||||
#' this case, the outermost names will be used as the group labels (leveraging
|
||||
#' the `<optgroup>` HTML tag) for the elements in the respective sublist. See
|
||||
#' the example section for a small demo of this feature.
|
||||
#' @param selected The initially selected value (or multiple values if `multiple
|
||||
#' = TRUE`). If not specified then defaults to the first value for
|
||||
#' single-select lists and no values for multiple select lists.
|
||||
#' @param multiple Is selection of multiple items allowed?
|
||||
#' @param selectize Whether to use \pkg{selectize.js} or not.
|
||||
#' @param size Number of items to show in the selection box; a larger number
|
||||
#' will result in a taller box. Not compatible with \code{selectize=TRUE}.
|
||||
#' Normally, when \code{multiple=FALSE}, a select input will be a drop-down
|
||||
#' list, but when \code{size} is set, it will be a box instead.
|
||||
#' will result in a taller box. Not compatible with `selectize=TRUE`.
|
||||
#' Normally, when `multiple=FALSE`, a select input will be a drop-down list,
|
||||
#' but when `size` is set, it will be a box instead.
|
||||
#' @return A select list control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateSelectInput}} \code{\link{varSelectInput}}
|
||||
#' @seealso [updateSelectInput()] [varSelectInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -55,7 +62,7 @@
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' # demoing optgroup support in the `choices` arg
|
||||
#' # demoing group support in the `choices` arg
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' selectInput("state", "Choose a state:",
|
||||
@@ -72,6 +79,11 @@
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value: A vector of character strings, usually of length
|
||||
#' 1, with the value of the selected items. When `multiple=TRUE` and
|
||||
#' nothing is selected, this value will be `NULL`.
|
||||
#'
|
||||
#' @export
|
||||
selectInput <- function(inputId, label, choices, selected = NULL,
|
||||
multiple = FALSE, selectize = TRUE, width = NULL,
|
||||
@@ -96,7 +108,7 @@ selectInput <- function(inputId, label, choices, selected = NULL,
|
||||
id = inputId,
|
||||
class = if (!selectize) "form-control",
|
||||
size = size,
|
||||
selectOptions(choices, selected)
|
||||
selectOptions(choices, selected, inputId, selectize)
|
||||
)
|
||||
if (multiple)
|
||||
selectTag$attribs$multiple <- "multiple"
|
||||
@@ -105,7 +117,7 @@ selectInput <- function(inputId, label, choices, selected = NULL,
|
||||
res <- div(
|
||||
class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
controlLabel(inputId, label),
|
||||
shinyInputLabel(inputId, label),
|
||||
div(selectTag)
|
||||
)
|
||||
|
||||
@@ -121,16 +133,22 @@ firstChoice <- function(choices) {
|
||||
}
|
||||
|
||||
# Create tags for each of the options; use <optgroup> if necessary.
|
||||
# This returns a HTML string instead of tags, because of the 'selected'
|
||||
# attribute.
|
||||
selectOptions <- function(choices, selected = NULL) {
|
||||
# This returns a HTML string instead of tags for performance reasons.
|
||||
selectOptions <- function(choices, selected = NULL, inputId, perfWarning = FALSE) {
|
||||
if (length(choices) >= 1000) {
|
||||
warning("The select input \"", inputId, "\" contains a large number of ",
|
||||
"options; consider using server-side selectize for massively improved ",
|
||||
"performance. See the Details section of the ?selectizeInput help topic.",
|
||||
call. = FALSE)
|
||||
}
|
||||
|
||||
html <- mapply(choices, names(choices), FUN = function(choice, label) {
|
||||
if (is.list(choice)) {
|
||||
# If sub-list, create an optgroup and recurse into the sublist
|
||||
sprintf(
|
||||
'<optgroup label="%s">\n%s\n</optgroup>',
|
||||
htmlEscape(label, TRUE),
|
||||
selectOptions(choice, selected)
|
||||
selectOptions(choice, selected, inputId, perfWarning)
|
||||
)
|
||||
|
||||
} else {
|
||||
@@ -153,21 +171,21 @@ needOptgroup <- function(choices) {
|
||||
}
|
||||
|
||||
#' @rdname selectInput
|
||||
#' @param ... Arguments passed to \code{selectInput()}.
|
||||
#' @param ... Arguments passed to `selectInput()`.
|
||||
#' @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 possible options (character option values inside [base::I()] will
|
||||
#' be treated as literal JavaScript code; see [renderDataTable()]
|
||||
#' for details).
|
||||
#' @param width The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
#' see \code{\link{validateCssUnit}}.
|
||||
#' @note The selectize input created from \code{selectizeInput()} allows
|
||||
#' @param width The width of the input, e.g. `'400px'`, or `'100%'`;
|
||||
#' see [validateCssUnit()].
|
||||
#' @note The selectize input created from `selectizeInput()` 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
|
||||
#' `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)}.
|
||||
#' `choices` argument. This is to keep compatibility with
|
||||
#' `selectInput(..., selectize = FALSE)`.
|
||||
#' @export
|
||||
selectizeInput <- function(inputId, ..., options = NULL, width = NULL) {
|
||||
selectizeIt(
|
||||
@@ -179,24 +197,25 @@ selectizeInput <- function(inputId, ..., options = NULL, width = NULL) {
|
||||
|
||||
# given a select input and its id, selectize it
|
||||
selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
|
||||
# Make sure accessibility plugin is included
|
||||
if (!('selectize-plugin-a11y' %in% options$plugins)) {
|
||||
options$plugins <- c(options$plugins, list('selectize-plugin-a11y'))
|
||||
}
|
||||
|
||||
res <- checkAsIs(options)
|
||||
|
||||
selectizeDep <- htmlDependency(
|
||||
"selectize", "0.11.2", c(href = "shared/selectize"),
|
||||
stylesheet = "css/selectize.bootstrap3.css",
|
||||
head = format(tagList(
|
||||
HTML('<!--[if lt IE 9]>'),
|
||||
tags$script(src = 'shared/selectize/js/es5-shim.min.js'),
|
||||
HTML('<![endif]-->'),
|
||||
tags$script(src = 'shared/selectize/js/selectize.min.js')
|
||||
))
|
||||
)
|
||||
selectizeDep <- selectizeDependency()
|
||||
|
||||
if ('drag_drop' %in% options$plugins) {
|
||||
selectizeDep <- list(selectizeDep, htmlDependency(
|
||||
'jqueryui', '1.12.1', c(href = 'shared/jqueryui'),
|
||||
script = 'jquery-ui.min.js'
|
||||
))
|
||||
selectizeDep <- c(
|
||||
selectizeDep,
|
||||
htmlDependency(
|
||||
'jqueryui',
|
||||
'1.12.1',
|
||||
c(href = 'shared/jqueryui'),
|
||||
script = 'jquery-ui.min.js'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
# Insert script on same level as <select> tag
|
||||
@@ -214,10 +233,50 @@ selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
|
||||
}
|
||||
|
||||
|
||||
selectizeVersion <- "0.12.4"
|
||||
|
||||
selectizeDependency <- function(theme) {
|
||||
list(
|
||||
htmlDependency(
|
||||
"selectize-js",
|
||||
selectizeVersion,
|
||||
src = c(href = "shared/selectize"),
|
||||
script = c(
|
||||
"js/selectize.min.js",
|
||||
# Accessibility plugin for screen readers (https://github.com/SLMNBJ/selectize-plugin-a11y):
|
||||
"accessibility/js/selectize-plugin-a11y.min.js"
|
||||
)
|
||||
),
|
||||
bootstraplib::bs_dependency_defer(selectizeCSS)
|
||||
)
|
||||
}
|
||||
|
||||
selectizeCSS <- function(theme) {
|
||||
if (!is_bs_theme(theme)) {
|
||||
return(htmlDependency(
|
||||
name = "selectize-css",
|
||||
version = selectizeVersion,
|
||||
src = c(href = "shared/selectize"),
|
||||
stylesheet = "css/selectize.bootstrap3.css"
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
scss_file <- system.file(
|
||||
package = "shiny", "www/shared/selectize/scss",
|
||||
if ("3" %in% bootstraplib::theme_version(theme)) {
|
||||
"selectize.bootstrap3.scss"
|
||||
} else {
|
||||
"selectize.bootstrap4.scss"
|
||||
}
|
||||
)
|
||||
bootstraplib::bs_dependency(
|
||||
input = sass::sass_file(scss_file),
|
||||
theme = theme,
|
||||
name = "selectize",
|
||||
version = selectizeVersion,
|
||||
cache_key_extra = utils::packageVersion("shiny")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#' Select variables from a data frame
|
||||
@@ -225,30 +284,31 @@ selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
|
||||
#' 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
|
||||
#' By default, `varSelectInput()` and `selectizeInput()` use the
|
||||
#' JavaScript library \pkg{selectize.js}
|
||||
#' (\url{https://github.com/selectize/selectize.js}) to instead of the basic
|
||||
#' (<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}.
|
||||
#' `selectInput()` with `selectize=FALSE`.
|
||||
#'
|
||||
#' @inheritParams selectInput
|
||||
#' @param data A data frame. Used to retrieve the column names as choices for a \code{\link{selectInput}}
|
||||
#' @param data A data frame. Used to retrieve the column names as choices for a [selectInput()]
|
||||
#' @return A variable select list control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateSelectInput}}
|
||||
#' @seealso [updateSelectInput()]
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' The resulting server `input` value will be returned as:
|
||||
#'
|
||||
#' * A symbol if `multiple = FALSE`. The `input` value should be
|
||||
#' used with rlang's [rlang::!!()]. For example,
|
||||
#' `ggplot2::aes(!!input$variable)`.
|
||||
#' * A list of symbols if `multiple = TRUE`. The `input` value
|
||||
#' should be used with rlang's [rlang::!!!()] to expand
|
||||
#' the symbol list as individual arguments. For example,
|
||||
#' `dplyr::select(mtcars, !!!input$variabls)` which is
|
||||
#' equivalent to `dplyr::select(mtcars, !!input$variabls[[1]], !!input$variabls[[2]], ..., !!input$variabls[[length(input$variabls)]])`.
|
||||
#'
|
||||
#' @examples
|
||||
#'
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -321,21 +381,21 @@ varSelectInput <- function(
|
||||
|
||||
|
||||
#' @rdname varSelectInput
|
||||
#' @param ... Arguments passed to \code{varSelectInput()}.
|
||||
#' @param ... Arguments passed to `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 possible options (character option values inside [base::I()] will
|
||||
#' be treated as literal JavaScript code; see [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
|
||||
#' @param width The width of the input, e.g. `'400px'`, or `'100%'`;
|
||||
#' see [validateCssUnit()].
|
||||
#' @note The variable selectize input created from `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
|
||||
#' `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)}.
|
||||
#' `choices` argument. This is to keep compatibility with
|
||||
#' `selectInput(..., selectize = FALSE)`.
|
||||
#' @export
|
||||
varSelectizeInput <- function(inputId, ..., options = NULL, width = NULL) {
|
||||
selectizeIt(
|
||||
|
||||
114
R/input-slider.R
114
R/input-slider.R
@@ -8,45 +8,45 @@
|
||||
#' @param value The initial value of the slider. A numeric vector of length one
|
||||
#' will create a regular slider; a numeric vector of length two will create a
|
||||
#' double-ended range slider. A warning will be issued if the value doesn't
|
||||
#' fit between \code{min} and \code{max}.
|
||||
#' fit between `min` and `max`.
|
||||
#' @param step Specifies the interval between each selectable value on the
|
||||
#' slider (if \code{NULL}, a heuristic is used to determine the step size). If
|
||||
#' the values are dates, \code{step} is in days; if the values are times
|
||||
#' (POSIXt), \code{step} is in seconds.
|
||||
#' @param round \code{TRUE} to round all values to the nearest integer;
|
||||
#' \code{FALSE} if no rounding is desired; or an integer to round to that
|
||||
#' slider (if `NULL`, a heuristic is used to determine the step size). If
|
||||
#' the values are dates, `step` is in days; if the values are times
|
||||
#' (POSIXt), `step` is in seconds.
|
||||
#' @param round `TRUE` to round all values to the nearest integer;
|
||||
#' `FALSE` if no rounding is desired; or an integer to round to that
|
||||
#' number of digits (for example, 1 will round to the nearest 10, and -2 will
|
||||
#' round to the nearest .01). Any rounding will be applied after snapping to
|
||||
#' the nearest step.
|
||||
#' @param format Deprecated.
|
||||
#' @param locale Deprecated.
|
||||
#' @param ticks \code{FALSE} to hide tick marks, \code{TRUE} to show them
|
||||
#' @param ticks `FALSE` to hide tick marks, `TRUE` to show them
|
||||
#' according to some simple heuristics.
|
||||
#' @param animate \code{TRUE} to show simple animation controls with default
|
||||
#' settings; \code{FALSE} not to; or a custom settings list, such as those
|
||||
#' created using \code{\link{animationOptions}}.
|
||||
#' @param animate `TRUE` to show simple animation controls with default
|
||||
#' settings; `FALSE` not to; or a custom settings list, such as those
|
||||
#' created using [animationOptions()].
|
||||
#' @param sep Separator between thousands places in numbers.
|
||||
#' @param pre A prefix string to put in front of the value.
|
||||
#' @param post A suffix string to put after the value.
|
||||
#' @param dragRange This option is used only if it is a range slider (with two
|
||||
#' values). If \code{TRUE} (the default), the range can be dragged. In other
|
||||
#' words, the min and max can be dragged together. If \code{FALSE}, the range
|
||||
#' values). If `TRUE` (the default), the range can be dragged. In other
|
||||
#' words, the min and max can be dragged together. If `FALSE`, the range
|
||||
#' cannot be dragged.
|
||||
#' @param timeFormat Only used if the values are Date or POSIXt objects. A time
|
||||
#' format string, to be passed to the Javascript strftime library. See
|
||||
#' \url{https://github.com/samsonjs/strftime} for more details. The allowed
|
||||
#' <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[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"}).
|
||||
#' [base::strftime()] function. For Dates, the default is `"%F"`
|
||||
#' (like `"2015-07-01"`), and for POSIXt, the default is `"%F %T"`
|
||||
#' (like `"2015-07-01 15:32:10"`).
|
||||
#' @param timezone Only used if the values are POSIXt objects. A string
|
||||
#' specifying the time zone offset for the displayed times, in the format
|
||||
#' \code{"+HHMM"} or \code{"-HHMM"}. If \code{NULL} (the default), times will
|
||||
#' be displayed in the browser's time zone. The value \code{"+0000"} will
|
||||
#' `"+HHMM"` or `"-HHMM"`. If `NULL` (the default), times will
|
||||
#' be displayed in the browser's time zone. The value `"+0000"` will
|
||||
#' result in UTC time.
|
||||
#' @inheritParams selectizeInput
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateSliderInput}}
|
||||
#' @seealso [updateSliderInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -70,13 +70,16 @@
|
||||
#' # Complete app with UI and server components
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' A number, or in the case of slider range, a vector of two numbers.
|
||||
#'
|
||||
#' @export
|
||||
sliderInput <- function(inputId, label, min, max, value, step = NULL,
|
||||
round = FALSE, format = NULL, locale = NULL,
|
||||
ticks = TRUE, animate = FALSE, width = NULL, sep = ",",
|
||||
pre = NULL, post = NULL, timeFormat = NULL,
|
||||
timezone = NULL, dragRange = TRUE)
|
||||
{
|
||||
timezone = NULL, dragRange = TRUE) {
|
||||
if (!missing(format)) {
|
||||
shinyDeprecated(msg = "The `format` argument to sliderInput is deprecated. Use `sep`, `pre`, and `post` instead.",
|
||||
version = "0.10.2.2")
|
||||
@@ -140,6 +143,7 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
|
||||
sliderProps <- dropNulls(list(
|
||||
class = "js-range-slider",
|
||||
id = inputId,
|
||||
`data-skin` = "shiny",
|
||||
`data-type` = if (length(value) > 1) "double",
|
||||
`data-min` = formatNoSci(min),
|
||||
`data-max` = formatNoSci(max),
|
||||
@@ -172,7 +176,7 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
|
||||
|
||||
sliderTag <- div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
if (!is.null(label)) controlLabel(inputId, label),
|
||||
shinyInputLabel(inputId, label),
|
||||
do.call(tags$input, sliderProps)
|
||||
)
|
||||
|
||||
@@ -201,20 +205,59 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
|
||||
)
|
||||
}
|
||||
|
||||
dep <- list(
|
||||
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.
|
||||
stylesheet = c("css/ion.rangeSlider.css",
|
||||
"css/ion.rangeSlider.skinShiny.css")
|
||||
attachDependencies(sliderTag, ionRangeSliderDependency())
|
||||
}
|
||||
|
||||
|
||||
ionRangeSliderVersion <- "2.3.1"
|
||||
|
||||
ionRangeSliderDependency <- function() {
|
||||
list(
|
||||
# ion.rangeSlider also needs normalize.css, which is already included in Bootstrap.
|
||||
htmlDependency(
|
||||
"ionrangeslider-javascript", ionRangeSliderVersion,
|
||||
src = c(href = "shared/ionrangeslider"),
|
||||
script = "js/ion.rangeSlider.min.js"
|
||||
),
|
||||
htmlDependency("strftime", "0.9.2", c(href="shared/strftime"),
|
||||
htmlDependency(
|
||||
"strftime", "0.9.2",
|
||||
src = c(href = "shared/strftime"),
|
||||
script = "strftime-min.js"
|
||||
),
|
||||
bootstraplib::bs_dependency_defer(ionRangeSliderDependencyCSS)
|
||||
)
|
||||
}
|
||||
|
||||
ionRangeSliderDependencyCSS <- function(theme) {
|
||||
if (!is_bs_theme(theme)) {
|
||||
return(htmlDependency(
|
||||
"ionrangeslider-css",
|
||||
ionRangeSliderVersion,
|
||||
src = c(href = "shared/ionrangeslider"),
|
||||
stylesheet = "css/ion.rangeSlider.css"
|
||||
))
|
||||
}
|
||||
|
||||
# Remap some variable names for ionRangeSlider's scss
|
||||
sass_input <- list(
|
||||
list(
|
||||
bg = "$input-bg",
|
||||
fg = "$input-color",
|
||||
accent = "$component-active-bg",
|
||||
`font-family` = "$font-family-base"
|
||||
),
|
||||
sass::sass_file(
|
||||
system.file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
|
||||
)
|
||||
)
|
||||
|
||||
attachDependencies(sliderTag, dep)
|
||||
bootstraplib::bs_dependency(
|
||||
input = sass_input,
|
||||
theme = theme,
|
||||
name = "ionRangeSlider",
|
||||
version = ionRangeSliderVersion,
|
||||
cache_key_extra = utils::packageVersion("shiny")
|
||||
)
|
||||
}
|
||||
|
||||
hasDecimals <- function(value) {
|
||||
@@ -222,7 +265,6 @@ 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)
|
||||
@@ -253,13 +295,13 @@ findStepSize <- function(min, max, step) {
|
||||
#' @rdname sliderInput
|
||||
#'
|
||||
#' @param interval The interval, in milliseconds, between each animation step.
|
||||
#' @param loop \code{TRUE} to automatically restart the animation when it
|
||||
#' @param loop `TRUE` to automatically restart the animation when it
|
||||
#' reaches the end.
|
||||
#' @param playButton Specifies the appearance of the play button. Valid values
|
||||
#' are a one-element character vector (for a simple text label), an HTML tag
|
||||
#' 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.
|
||||
#' or list of tags (using [tag()] and friends), or raw HTML (using
|
||||
#' [HTML()]).
|
||||
#' @param pauseButton Similar to `playButton`, but for the pause button.
|
||||
#' @export
|
||||
animationOptions <- function(interval=1000,
|
||||
loop=FALSE,
|
||||
|
||||
@@ -3,30 +3,30 @@
|
||||
#' 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).
|
||||
#' The use of `submitButton` is generally discouraged in favor of
|
||||
#' the more versatile [actionButton()] (see details below).
|
||||
#'
|
||||
#' Submit buttons are unusual Shiny inputs, and we recommend using
|
||||
#' \code{\link{actionButton}} instead of \code{submitButton} when you
|
||||
#' [actionButton()] instead of `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}).
|
||||
#' See [this
|
||||
#' article](http://shiny.rstudio.com/articles/action-buttons.html) for more information (including a demo of how to "translate"
|
||||
#' code using a `submitButton` to code using an `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,
|
||||
#' instance, that if there are *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.
|
||||
#' created submit buttons (for example, with [renderUI()]
|
||||
#' or [insertUI()]) will not work.
|
||||
#'
|
||||
#' @param text Button caption
|
||||
#' @param icon Optional \code{\link{icon}} to appear on the button
|
||||
#' @param width The width of the button, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
#' see \code{\link{validateCssUnit}}.
|
||||
#' @param icon Optional [icon()] to appear on the button
|
||||
#' @param width The width of the button, e.g. `'400px'`, or `'100%'`;
|
||||
#' see [validateCssUnit()].
|
||||
#' @return A submit button that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
#'
|
||||
#' Create an input control for entry of unstructured text values
|
||||
#'
|
||||
#' @param inputId The \code{input} slot that will be used to access the value.
|
||||
#' @param label Display label for the control, or \code{NULL} for no label.
|
||||
#' @param inputId The `input` slot that will be used to access the value.
|
||||
#' @param label Display label for the control, or `NULL` for no label.
|
||||
#' @param value Initial value.
|
||||
#' @param width The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
#' see \code{\link{validateCssUnit}}.
|
||||
#' @param width The width of the input, e.g. `'400px'`, or `'100%'`;
|
||||
#' see [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}}
|
||||
#' @seealso [updateTextInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -28,6 +28,11 @@
|
||||
#' }
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' A character string of the text input. The default value is `""`
|
||||
#' unless `value` is provided.
|
||||
#'
|
||||
#' @export
|
||||
textInput <- function(inputId, label, value = "", width = NULL,
|
||||
placeholder = NULL) {
|
||||
@@ -36,7 +41,7 @@ textInput <- function(inputId, label, value = "", width = NULL,
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label, `for` = inputId),
|
||||
shinyInputLabel(inputId, label),
|
||||
tags$input(id = inputId, type="text", class="form-control", value=value,
|
||||
placeholder = placeholder)
|
||||
)
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
#' 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 height The height of the input, e.g. `'400px'`, or `'100%'`; see
|
||||
#' [validateCssUnit()].
|
||||
#' @param cols Value of the visible character columns of the input, e.g. `80`.
|
||||
#' This argument will only take effect if there is not a CSS `width` rule
|
||||
#' defined for this element; such a rule could come from the `width` argument
|
||||
#' of this function or from a containing page layout such as
|
||||
#' [fluidPage()].
|
||||
#' @param rows The value of the visible character rows of the input, e.g. `6`.
|
||||
#' If the `height` argument is specified, `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.
|
||||
#' `"both"`, `"none"`, `"vertical"`, and `"horizontal"`. The default, `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}}
|
||||
#' @seealso [updateTextAreaInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -34,6 +35,11 @@
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' A character string of the text input. The default value is `""`
|
||||
#' unless `value` is provided.
|
||||
#'
|
||||
#' @export
|
||||
textAreaInput <- function(inputId, label, value = "", width = NULL, height = NULL,
|
||||
cols = NULL, rows = NULL, placeholder = NULL, resize = NULL) {
|
||||
@@ -45,7 +51,8 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL
|
||||
}
|
||||
|
||||
style <- paste(
|
||||
if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
# The width is specified on the parent div.
|
||||
if (!is.null(width)) paste0("width: ", "100%", ";"),
|
||||
if (!is.null(height)) paste0("height: ", validateCssUnit(height), ";"),
|
||||
if (!is.null(resize)) paste0("resize: ", resize, ";")
|
||||
)
|
||||
@@ -55,7 +62,8 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL
|
||||
if (length(style) == 0) style <- NULL
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
label %AND% tags$label(label, `for` = inputId),
|
||||
shinyInputLabel(inputId, label),
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
tags$textarea(
|
||||
id = inputId,
|
||||
class = "form-control",
|
||||
|
||||
123
R/input-utils.R
123
R/input-utils.R
@@ -1,5 +1,12 @@
|
||||
controlLabel <- function(controlName, label) {
|
||||
label %AND% tags$label(class = "control-label", `for` = controlName, label)
|
||||
shinyInputLabel <- function(inputId, label = NULL) {
|
||||
tags$label(
|
||||
label,
|
||||
class = "control-label",
|
||||
class = if (is.null(label)) "shiny-label-null",
|
||||
# `id` attribute is required for `aria-labelledby` used by screen readers:
|
||||
id = paste0(inputId, "-label"),
|
||||
`for` = inputId
|
||||
)
|
||||
}
|
||||
|
||||
# This function takes in either a list or vector for `choices` (and
|
||||
@@ -85,45 +92,83 @@ generateOptions <- function(inputId, selected, inline, type = 'checkbox',
|
||||
div(class = "shiny-options-group", options)
|
||||
}
|
||||
|
||||
# True when a choice list item represents a group of related inputs.
|
||||
isGroup <- function(choice) {
|
||||
is.list(choice) ||
|
||||
!is.null(names(choice)) ||
|
||||
length(choice) > 1 ||
|
||||
length(choice) == 0
|
||||
}
|
||||
|
||||
# Takes a vector or list, and adds names (same as the value) to any entries
|
||||
# True when choices is a list and contains at least one group of related inputs.
|
||||
hasGroups <- function(choices) {
|
||||
is.list(choices) && any(vapply(choices, isGroup, logical(1)))
|
||||
}
|
||||
|
||||
# Assigns empty names to x if it's unnamed, and then fills any empty names with
|
||||
# the corresponding value coerced to a character(1).
|
||||
setDefaultNames <- function(x) {
|
||||
x <- asNamed(x)
|
||||
emptyNames <- names(x) == ""
|
||||
names(x)[emptyNames] <- as.character(x)[emptyNames]
|
||||
x
|
||||
}
|
||||
|
||||
# Makes a character vector out of x in a way that preserves names.
|
||||
asCharacter <- function(x) {
|
||||
stats::setNames(as.character(x), names(x))
|
||||
}
|
||||
|
||||
# Processes a "flat" set of choices, or a collection of choices not containing
|
||||
# any named groups. choices should be a list without any list children, or an
|
||||
# atomic vector. choices may be named or unnamed. Any empty names are replaced
|
||||
# with the corresponding value coerced to a character.
|
||||
processFlatChoices <- function(choices) {
|
||||
choices <- setDefaultNames(asCharacter(choices))
|
||||
as.list(choices)
|
||||
}
|
||||
|
||||
# Processes a "nested" set of choices, or a collection of choices that contains
|
||||
# one or more named groups of related choices and zero or more "flat" choices.
|
||||
# choices should be a named list, and any choice group must have a non-empty
|
||||
# name. Empty names of remaining "flat" choices are replaced with that choice's
|
||||
# value coerced to a character.
|
||||
processGroupedChoices <- function(choices) {
|
||||
# We assert choices is a list, since only a list may contain a group.
|
||||
stopifnot(is.list(choices))
|
||||
# The list might be unnamed by this point. We add default names of "" so that
|
||||
# names(choices) is not zero-length and mapply can work. Within mapply, we
|
||||
# error if any group's name is ""
|
||||
choices <- asNamed(choices)
|
||||
choices <- mapply(function(name, choice) {
|
||||
choiceIsGroup <- isGroup(choice)
|
||||
if (choiceIsGroup && name == "") {
|
||||
# If the choice is a group, and if its name is empty, produce an error. We
|
||||
# error here because the composite nature of the choice prevents us from
|
||||
# meaningfully automatically naming it. Note that while not documented,
|
||||
# groups are not necessarily lists (aka generic vectors) but can also be
|
||||
# any named atomic vector, or any atomic vector of length > 1.
|
||||
stop('All sub-lists in "choices" must be named.')
|
||||
} else if (choiceIsGroup) {
|
||||
# The choice is a group, but it is named. Process it using the same
|
||||
# function we use for "top level" choices.
|
||||
processFlatChoices(choice)
|
||||
} else {
|
||||
# The choice was not named and is not a group; it is a "leaf".
|
||||
as.character(choice)
|
||||
}
|
||||
}, names(choices), choices, SIMPLIFY = FALSE)
|
||||
# By this point, any leaves in the choices list might still have empty names,
|
||||
# so we're sure to automatically name them.
|
||||
setDefaultNames(choices)
|
||||
}
|
||||
|
||||
# Takes a vector/list/factor, and adds names (same as the value) to any entries
|
||||
# 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,
|
||||
# convert it to a named list with blank names.
|
||||
listify <- function(obj) {
|
||||
# If a list/vector is unnamed, give it blank names
|
||||
makeNamed <- function(x) {
|
||||
if (is.null(names(x))) names(x) <- character(length(x))
|
||||
x
|
||||
}
|
||||
|
||||
res <- lapply(obj, function(val) {
|
||||
if (is.list(val))
|
||||
listify(val)
|
||||
else if (length(val) == 1 && is.null(names(val)))
|
||||
as.character(val)
|
||||
else
|
||||
makeNamed(as.list(val))
|
||||
})
|
||||
|
||||
makeNamed(res)
|
||||
if (hasGroups(choices)) {
|
||||
processGroupedChoices(choices)
|
||||
} else {
|
||||
processFlatChoices(choices)
|
||||
}
|
||||
|
||||
choices <- listify(choices)
|
||||
if (length(choices) == 0) return(choices)
|
||||
|
||||
# Recurse into any subgroups
|
||||
choices <- mapply(choices, names(choices), FUN = function(choice, name) {
|
||||
if (!is.list(choice)) return(choice)
|
||||
if (name == "") stop('All sub-lists in "choices" must be named.')
|
||||
choicesWithNames(choice)
|
||||
}, SIMPLIFY = FALSE)
|
||||
|
||||
# default missing names to choice values
|
||||
missing <- names(choices) == ""
|
||||
names(choices)[missing] <- as.character(choices)[missing]
|
||||
|
||||
choices
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
#' 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}}.
|
||||
#' Dynamically insert or remove a [tabPanel()] (or a
|
||||
#' [navbarMenu()]) from an existing [tabsetPanel()],
|
||||
#' [navlistPanel()] or [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}.
|
||||
#' should use `insertTab`. When you want to prepend a tab (i.e. add a
|
||||
#' tab to the beginning of the `tabsetPanel`), use `prependTab`.
|
||||
#' When you want to append a tab (i.e. add a tab to the end of the
|
||||
#' \code{tabsetPanel}), use \code{appendTab}.
|
||||
#' `tabsetPanel`), use `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).
|
||||
#' For `navbarPage`, you can insert/remove conventional
|
||||
#' `tabPanel`s (whether at the top level or nested inside a
|
||||
#' `navbarMenu`), as well as an entire [navbarMenu()].
|
||||
#' For the latter case, `target` should be the `menuName` that
|
||||
#' you gave your `navbarMenu` when you first created it (by default,
|
||||
#' this is equal to the value of the `title` argument).
|
||||
#'
|
||||
#' @param inputId The \code{id} of the \code{tabsetPanel} (or
|
||||
#' \code{navlistPanel} or \code{navbarPage}) into which \code{tab} will
|
||||
#' @param inputId The `id` of the `tabsetPanel` (or
|
||||
#' `navlistPanel` or `navbarPage`) into which `tab` will
|
||||
#' be inserted/removed.
|
||||
#'
|
||||
#' @param tab The item to be added (must be created with \code{tabPanel},
|
||||
#' or with \code{navbarMenu}).
|
||||
#' @param tab The item to be added (must be created with `tabPanel`,
|
||||
#' or with `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
|
||||
#' @param target If inserting: the `value` of an existing
|
||||
#' `tabPanel`, next to which `tab` will be added.
|
||||
#' If removing: the `value` of the `tabPanel` that
|
||||
#' you want to remove. See Details if you want to insert next to/remove
|
||||
#' an entire \code{navbarMenu} instead.
|
||||
#' an entire `navbarMenu` instead.
|
||||
#'
|
||||
#' @param position Should \code{tab} be added before or after the
|
||||
#' \code{target} tab?
|
||||
#' @param position Should `tab` be added before or after the
|
||||
#' `target` tab?
|
||||
#'
|
||||
#' @param select Should \code{tab} be selected upon being inserted?
|
||||
#' @param select Should `tab` be selected upon being inserted?
|
||||
#'
|
||||
#' @param session The shiny session within which to call this function.
|
||||
#'
|
||||
#' @seealso \code{\link{showTab}}
|
||||
#' @seealso [showTab()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
@@ -144,16 +144,16 @@ insertTab <- function(inputId, tab, target,
|
||||
}
|
||||
|
||||
#' @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}
|
||||
#' prepend (or append) `tab` to the beginning (or end) of an
|
||||
#' existing [navbarMenu()] (which must itself be part of
|
||||
#' an existing [navbarPage()]). In this case, this argument
|
||||
#' should be the `menuName` that you gave your `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.
|
||||
#' of the `title` argument). Note that you still need to set the
|
||||
#' `inputId` argument to whatever the `id` of the parent
|
||||
#' `navbarPage` is. If `menuName` is left as `NULL`,
|
||||
#' `tab` will be prepended (or appended) to whatever
|
||||
#' `inputId` is.
|
||||
#'
|
||||
#' @rdname insertTab
|
||||
#' @export
|
||||
@@ -221,30 +221,30 @@ removeTab <- function(inputId, target,
|
||||
|
||||
#' 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}}.
|
||||
#' Dynamically hide or show a [tabPanel()] (or a
|
||||
#' [navbarMenu()])from an existing [tabsetPanel()],
|
||||
#' [navlistPanel()] or [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).
|
||||
#' For `navbarPage`, you can hide/show conventional
|
||||
#' `tabPanel`s (whether at the top level or nested inside a
|
||||
#' `navbarMenu`), as well as an entire [navbarMenu()].
|
||||
#' For the latter case, `target` should be the `menuName` that
|
||||
#' you gave your `navbarMenu` when you first created it (by default,
|
||||
#' this is equal to the value of the `title` argument).
|
||||
#'
|
||||
#' @param inputId The \code{id} of the \code{tabsetPanel} (or
|
||||
#' \code{navlistPanel} or \code{navbarPage}) in which to find
|
||||
#' \code{target}.
|
||||
#' @param inputId The `id` of the `tabsetPanel` (or
|
||||
#' `navlistPanel` or `navbarPage`) in which to find
|
||||
#' `target`.
|
||||
#'
|
||||
#' @param target The \code{value} of the \code{tabPanel} to be
|
||||
#' @param target The `value` of the `tabPanel` to be
|
||||
#' hidden/shown. See Details if you want to hide/show an entire
|
||||
#' \code{navbarMenu} instead.
|
||||
#' `navbarMenu` instead.
|
||||
#'
|
||||
#' @param select Should \code{target} be selected upon being shown?
|
||||
#' @param select Should `target` be selected upon being shown?
|
||||
#'
|
||||
#' @param session The shiny session within which to call this function.
|
||||
#'
|
||||
#' @seealso \code{\link{insertTab}}
|
||||
#' @seealso [insertTab()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
|
||||
162
R/insert-ui.R
162
R/insert-ui.R
@@ -1,55 +1,54 @@
|
||||
#' Insert UI objects
|
||||
#' Insert and remove 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
|
||||
#' These functions allow you to dynamically add and remove arbirary UI
|
||||
#' into your app, whenever you want, as many times as you want.
|
||||
#' Unlike [renderUI()], the UI generated with `insertUI()` is persistent:
|
||||
#' once it's created, it stays there until removed by `removeUI()`. Each
|
||||
#' new call to `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}}.
|
||||
#' appropriate `render` function or a customized `reactive`
|
||||
#' 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) relative to which you want to insert your
|
||||
#' UI object.
|
||||
#' It's particularly useful to pair `removeUI` with `insertUI()`, but there is
|
||||
#' no restriction on what you can use 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 `s` to be placed in a `$(s)` jQuery call).
|
||||
#'
|
||||
#' For `insertUI()` this determines the element(s) relative to which you
|
||||
#' want to insert your UI object. For `removeUI()` this 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 `<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 `<div>` with an id.)
|
||||
#' @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}.
|
||||
#'
|
||||
#' \describe{
|
||||
#' \item{`beforeBegin`}{Before the selector element itself}
|
||||
#' \item{`afterBegin`}{Just inside the selector element, before its
|
||||
#' first child}
|
||||
#' \item{`beforeEnd`}{Just inside the selector element, after its
|
||||
#' last child (default)}
|
||||
#' \item{`afterEnd`}{After the selector element itself}
|
||||
#' }
|
||||
#' Adapted from <https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML>.
|
||||
#' @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()}.
|
||||
#'
|
||||
#' you usually put inside your apps's `ui` function. If you're inserting
|
||||
#' multiple elements in one call, make sure to wrap them in either a
|
||||
#' `tagList()` or a `tags$div()` (the latter option has the
|
||||
#' advantage that you can give it an `id` to make it easier to
|
||||
#' reference or remove it later on). If you want to insert raw html, use
|
||||
#' `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}}
|
||||
#'
|
||||
#' `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
|
||||
#' or removed, or whether Shiny should wait until all outputs have been
|
||||
#' updated and all observers have been run (default).
|
||||
#' @param session The shiny session. Advanced use only.
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
@@ -73,6 +72,26 @@
|
||||
#' # Complete app with UI and server components
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' 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
|
||||
insertUI <- function(selector,
|
||||
where = c("beforeBegin", "afterBegin", "beforeEnd", "afterEnd"),
|
||||
@@ -100,60 +119,7 @@ insertUI <- function(selector,
|
||||
}
|
||||
|
||||
|
||||
#' 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)
|
||||
#' }
|
||||
#' @rdname insertUI
|
||||
#' @export
|
||||
removeUI <- function(selector,
|
||||
multiple = FALSE,
|
||||
|
||||
38
R/jqueryui.R
38
R/jqueryui.R
@@ -2,32 +2,32 @@
|
||||
#'
|
||||
#' Creates a panel whose contents are absolutely positioned.
|
||||
#'
|
||||
#' The \code{absolutePanel} function creates a \code{<div>} tag whose CSS
|
||||
#' position is set to \code{absolute} (or fixed if \code{fixed = TRUE}). The way
|
||||
#' The `absolutePanel` function creates a `<div>` tag whose CSS
|
||||
#' position is set to `absolute` (or fixed if `fixed = TRUE`). The way
|
||||
#' absolute positioning works in HTML is that absolute coordinates are specified
|
||||
#' relative to its nearest parent element whose position is not set to
|
||||
#' \code{static} (which is the default), and if no such parent is found, then
|
||||
#' `static` (which is the default), and if no such parent is found, then
|
||||
#' relative to the page borders. If you're not sure what that means, just keep
|
||||
#' in mind that you may get strange results if you use \code{absolutePanel} from
|
||||
#' in mind that you may get strange results if you use `absolutePanel` from
|
||||
#' inside of certain types of panels.
|
||||
#'
|
||||
#' The \code{fixedPanel} function is the same as \code{absolutePanel} with
|
||||
#' \code{fixed = TRUE}.
|
||||
#' The `fixedPanel` function is the same as `absolutePanel` with
|
||||
#' `fixed = TRUE`.
|
||||
#'
|
||||
#' The position (\code{top}, \code{left}, \code{right}, \code{bottom}) and size
|
||||
#' (\code{width}, \code{height}) parameters are all optional, but you should
|
||||
#' specify exactly two of \code{top}, \code{bottom}, and \code{height} and
|
||||
#' exactly two of \code{left}, \code{right}, and \code{width} for predictable
|
||||
#' The position (`top`, `left`, `right`, `bottom`) and size
|
||||
#' (`width`, `height`) parameters are all optional, but you should
|
||||
#' specify exactly two of `top`, `bottom`, and `height` and
|
||||
#' exactly two of `left`, `right`, and `width` for predictable
|
||||
#' results.
|
||||
#'
|
||||
#' Like most other distance parameters in Shiny, the position and size
|
||||
#' parameters take a number (interpreted as pixels) or a valid CSS size string,
|
||||
#' such as \code{"100px"} (100 pixels) or \code{"25\%"}.
|
||||
#' such as `"100px"` (100 pixels) or `"25%"`.
|
||||
#'
|
||||
#' For arcane HTML reasons, to have the panel fill the page or parent you should
|
||||
#' specify \code{0} for \code{top}, \code{left}, \code{right}, and \code{bottom}
|
||||
#' rather than the more obvious \code{width = "100\%"} and \code{height =
|
||||
#' "100\%"}.
|
||||
#' specify `0` for `top`, `left`, `right`, and `bottom`
|
||||
#' rather than the more obvious `width = "100%"` and `height =
|
||||
#' "100%"`.
|
||||
#'
|
||||
#' @param ... Attributes (named arguments) or children (unnamed arguments) that
|
||||
#' should be included in the panel.
|
||||
@@ -42,16 +42,16 @@
|
||||
#' page or parent container.
|
||||
#' @param width Width of the panel.
|
||||
#' @param height Height of the panel.
|
||||
#' @param draggable If \code{TRUE}, allows the user to move the panel by
|
||||
#' @param draggable If `TRUE`, allows the user to move the panel by
|
||||
#' clicking and dragging.
|
||||
#' @param fixed Positions the panel relative to the browser window and prevents
|
||||
#' it from being scrolled with the rest of the page.
|
||||
#' @param cursor The type of cursor that should appear when the user mouses over
|
||||
#' the panel. Use \code{"move"} for a north-east-south-west icon,
|
||||
#' \code{"default"} for the usual cursor arrow, or \code{"inherit"} for the
|
||||
#' the panel. Use `"move"` for a north-east-south-west icon,
|
||||
#' `"default"` for the usual cursor arrow, or `"inherit"` for the
|
||||
#' usual cursor behavior (including changing to an I-beam when the cursor is
|
||||
#' over text). The default is \code{"auto"}, which is equivalent to
|
||||
#' \code{ifelse(draggable, "move", "inherit")}.
|
||||
#' over text). The default is `"auto"`, which is equivalent to
|
||||
#' `ifelse(draggable, "move", "inherit")`.
|
||||
#' @return An HTML element or list of elements.
|
||||
#' @export
|
||||
absolutePanel <- function(...,
|
||||
|
||||
80
R/knitr.R
Normal file
80
R/knitr.R
Normal file
@@ -0,0 +1,80 @@
|
||||
#' Knitr S3 methods
|
||||
#'
|
||||
#' These S3 methods are necessary to help Shiny applications and UI chunks embed
|
||||
#' themselves in knitr/rmarkdown documents.
|
||||
#'
|
||||
#' @name knitr_methods
|
||||
#' @param x Object to knit_print
|
||||
#' @param ... Additional knit_print arguments
|
||||
NULL
|
||||
|
||||
# If there's an R Markdown runtime option set but it isn't set to Shiny, then
|
||||
# return a warning indicating the runtime is inappropriate for this object.
|
||||
# Returns NULL in all other cases.
|
||||
shiny_rmd_warning <- function() {
|
||||
runtime <- knitr::opts_knit$get("rmarkdown.runtime")
|
||||
if (!is.null(runtime) && runtime != "shiny")
|
||||
# note that the RStudio IDE checks for this specific string to detect Shiny
|
||||
# applications in static document
|
||||
list(structure(
|
||||
"Shiny application in a static R Markdown document",
|
||||
class = "rmd_warning"))
|
||||
else
|
||||
NULL
|
||||
}
|
||||
|
||||
#' @rdname knitr_methods
|
||||
knit_print.shiny.appobj <- function(x, ...) {
|
||||
opts <- x$options %OR% list()
|
||||
width <- if (is.null(opts$width)) "100%" else opts$width
|
||||
height <- if (is.null(opts$height)) "400" else opts$height
|
||||
|
||||
runtime <- knitr::opts_knit$get("rmarkdown.runtime")
|
||||
if (!is.null(runtime) && runtime != "shiny") {
|
||||
# If not rendering to a Shiny document, create a box exactly the same
|
||||
# dimensions as the Shiny app would have had (so the document continues to
|
||||
# flow as it would have with the app), and display a diagnostic message
|
||||
width <- validateCssUnit(width)
|
||||
height <- validateCssUnit(height)
|
||||
output <- tags$div(
|
||||
style=paste("width:", width, "; height:", height, "; text-align: center;",
|
||||
"box-sizing: border-box;", "-moz-box-sizing: border-box;",
|
||||
"-webkit-box-sizing: border-box;"),
|
||||
class="muted well",
|
||||
"Shiny applications not supported in static R Markdown documents")
|
||||
}
|
||||
else {
|
||||
path <- addSubApp(x)
|
||||
output <- deferredIFrame(path, width, height)
|
||||
}
|
||||
|
||||
# If embedded Shiny apps ever have JS/CSS dependencies (like pym.js) we'll
|
||||
# need to grab those and put them in meta, like in knit_print.shiny.tag. But
|
||||
# for now it's not an issue, so just return the HTML and warning.
|
||||
|
||||
knitr::asis_output(htmlPreserve(format(output, indent=FALSE)),
|
||||
meta = shiny_rmd_warning(), cacheable = FALSE)
|
||||
}
|
||||
|
||||
# Let us use a nicer syntax in knitr chunks than literally
|
||||
# calling output$value <- renderFoo(...) and fooOutput().
|
||||
#' @rdname knitr_methods
|
||||
#' @param inline Whether the object is printed inline.
|
||||
knit_print.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
x <- htmltools::as.tags(x, inline = inline)
|
||||
output <- knitr::knit_print(tagList(x))
|
||||
attr(output, "knit_cacheable") <- FALSE
|
||||
attr(output, "knit_meta") <- append(attr(output, "knit_meta"),
|
||||
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
|
||||
knit_print.reactive <- function(x, ..., inline = FALSE) {
|
||||
renderFunc <- if (inline) renderText else renderPrint
|
||||
knitr::knit_print(renderFunc({
|
||||
x()
|
||||
}), inline = inline)
|
||||
}
|
||||
47
R/map.R
47
R/map.R
@@ -9,63 +9,58 @@
|
||||
# Remove of unknown key does nothing
|
||||
# Setting a key twice always results in last-one-wins
|
||||
# /TESTS
|
||||
|
||||
# Note that Map objects can't be saved in one R session and restored in
|
||||
# another, because they are based on fastmap, which uses an external pointer,
|
||||
# and external pointers can't be saved and restored in another session.
|
||||
#' @importFrom fastmap fastmap
|
||||
Map <- R6Class(
|
||||
'Map',
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
initialize = function() {
|
||||
private$env <- new.env(parent=emptyenv())
|
||||
private$map <<- fastmap()
|
||||
},
|
||||
get = function(key) {
|
||||
env[[key]]
|
||||
map$get(key)
|
||||
},
|
||||
set = function(key, value) {
|
||||
env[[key]] <- value
|
||||
map$set(key, value)
|
||||
value
|
||||
},
|
||||
mget = function(keys) {
|
||||
base::mget(keys, env)
|
||||
map$mget(keys)
|
||||
},
|
||||
mset = function(...) {
|
||||
args <- list(...)
|
||||
if (length(args) == 0)
|
||||
return()
|
||||
|
||||
arg_names <- names(args)
|
||||
if (is.null(arg_names) || any(!nzchar(arg_names)))
|
||||
stop("All elements must be named")
|
||||
|
||||
list2env(args, envir = env)
|
||||
map$mset(...)
|
||||
},
|
||||
remove = function(key) {
|
||||
if (!self$containsKey(key))
|
||||
if (!map$has(key))
|
||||
return(NULL)
|
||||
|
||||
result <- env[[key]]
|
||||
rm(list=key, envir=env, inherits=FALSE)
|
||||
result <- map$get(key)
|
||||
map$remove(key)
|
||||
result
|
||||
},
|
||||
containsKey = function(key) {
|
||||
exists(key, envir=env, inherits=FALSE)
|
||||
map$has(key)
|
||||
},
|
||||
keys = function() {
|
||||
# Sadly, this is much faster than ls(), because it doesn't sort the keys.
|
||||
names(as.list(env, all.names=TRUE))
|
||||
keys = function(sort = FALSE) {
|
||||
map$keys(sort = sort)
|
||||
},
|
||||
values = function() {
|
||||
as.list(env, all.names=TRUE)
|
||||
values = function(sort = FALSE) {
|
||||
map$as_list(sort = sort)
|
||||
},
|
||||
clear = function() {
|
||||
private$env <- new.env(parent=emptyenv())
|
||||
invisible(NULL)
|
||||
map$reset()
|
||||
},
|
||||
size = function() {
|
||||
length(env)
|
||||
map$size()
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
env = 'environment'
|
||||
map = NULL
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -2,19 +2,64 @@
|
||||
NULL
|
||||
|
||||
reactLogHandler <- function(req) {
|
||||
if (!identical(req$PATH_INFO, '/reactlog'))
|
||||
return(NULL)
|
||||
if (! rLog$isLogging()) {
|
||||
if (
|
||||
identical(req$PATH_INFO, "/reactlog/mark") ||
|
||||
identical(req$PATH_INFO, "/reactlog")
|
||||
) {
|
||||
# is not logging, but is a reactlog path...
|
||||
|
||||
return(
|
||||
httpResponse(
|
||||
# Not Implemented
|
||||
# - The server either does not recognize the request method, or it lacks the ability to fulfil the request.
|
||||
status = 501,
|
||||
content_type = "text/plain; charset=utf-8",
|
||||
content = "To enable reactlog, set the following option before running the application: \n\noptions(shiny.reactlog = TRUE)"
|
||||
)
|
||||
)
|
||||
|
||||
} else {
|
||||
# continue on like normal
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
if (!isTRUE(getOption('shiny.reactlog'))) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
sessionToken <- parseQueryString(req$QUERY_STRING)$s
|
||||
if (identical(req$PATH_INFO, "/reactlog/mark")) {
|
||||
sessionToken <- parseQueryString(req$QUERY_STRING)$s
|
||||
shinysession <- appsByToken$get(sessionToken)
|
||||
|
||||
return(httpResponse(
|
||||
status=200,
|
||||
content=list(file=renderReactLog(sessionToken), owned=TRUE)
|
||||
))
|
||||
# log time
|
||||
withReactiveDomain(shinysession, {
|
||||
rLog$userMark(getDefaultReactiveDomain())
|
||||
})
|
||||
|
||||
return(httpResponse(
|
||||
status = 200,
|
||||
content = "marked",
|
||||
content_type = "text/plain"
|
||||
))
|
||||
|
||||
} else if (identical(req$PATH_INFO, "/reactlog")){
|
||||
|
||||
sessionToken <- parseQueryString(req$QUERY_STRING)$s
|
||||
|
||||
# `renderReactLog` will check/throw if reactlog doesn't exist
|
||||
reactlogFile <- renderReactlog(sessionToken)
|
||||
|
||||
return(httpResponse(
|
||||
status = 200,
|
||||
content = list(
|
||||
file = reactlogFile,
|
||||
owned = TRUE
|
||||
)
|
||||
))
|
||||
|
||||
} else {
|
||||
# continue on like normal
|
||||
return(NULL)
|
||||
}
|
||||
}
|
||||
|
||||
sessionHandler <- function(req) {
|
||||
|
||||
@@ -14,7 +14,26 @@
|
||||
# returns `NULL`, or an `httpResponse`.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
httpResponse <- function(status = 200,
|
||||
|
||||
#' Create an HTTP response object
|
||||
#'
|
||||
#' @param status HTTP status code for the response.
|
||||
#' @param content_type The value for the `Content-Type` header.
|
||||
#' @param content The body of the response, given as a single-element character
|
||||
#' vector (will be encoded as UTF-8) or a raw vector.
|
||||
#' @param headers A named list of additional headers to include. Do not include
|
||||
#' `Content-Length` (as it is automatically calculated) or `Content-Type` (the
|
||||
#' `content_type` argument is used instead).
|
||||
#'
|
||||
#' @examples
|
||||
#' httpResponse(status = 405L,
|
||||
#' content_type = "text/plain",
|
||||
#' content = "The requested method was not allowed"
|
||||
#' )
|
||||
#'
|
||||
#' @keywords internal
|
||||
#' @export
|
||||
httpResponse <- function(status = 200L,
|
||||
content_type = "text/html; charset=UTF-8",
|
||||
content = "",
|
||||
headers = list()) {
|
||||
@@ -199,6 +218,9 @@ staticHandler <- function(root) {
|
||||
if (path == '/')
|
||||
path <- '/index.html'
|
||||
|
||||
if (grepl('\\', path, fixed = TRUE))
|
||||
return(NULL)
|
||||
|
||||
abs.path <- resolve(root, path)
|
||||
if (is.null(abs.path))
|
||||
return(NULL)
|
||||
@@ -308,34 +330,49 @@ HandlerManager <- R6Class("HandlerManager",
|
||||
},
|
||||
call = .httpServer(
|
||||
function (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)
|
||||
hybrid_chain(
|
||||
hybrid_chain(
|
||||
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)
|
||||
}
|
||||
}
|
||||
),
|
||||
catch = function(err) {
|
||||
httpResponse(status = 500L,
|
||||
content_type = "text/html",
|
||||
content = as.character(htmltools::htmlTemplate(
|
||||
system.file("template", "error.html", package = "shiny"),
|
||||
message = conditionMessage(err)
|
||||
))
|
||||
)
|
||||
}
|
||||
),
|
||||
function(resp) {
|
||||
maybeInjectAutoreload(resp)
|
||||
}
|
||||
)
|
||||
},
|
||||
getOption('shiny.sharedSecret')
|
||||
loadSharedSecret()
|
||||
),
|
||||
onWSOpen = function(ws) {
|
||||
return(wsHandlers$invoke(ws))
|
||||
}
|
||||
)
|
||||
},
|
||||
.httpServer = function(handler, sharedSecret) {
|
||||
.httpServer = function(handler, checkSharedSecret) {
|
||||
filter <- getOption('shiny.http.response.filter')
|
||||
if (is.null(filter))
|
||||
filter <- function(req, response) response
|
||||
|
||||
function(req) {
|
||||
if (!is.null(sharedSecret)
|
||||
&& !identical(sharedSecret, req$HTTP_SHINY_SHARED_SECRET)) {
|
||||
if (!checkSharedSecret(req$HTTP_SHINY_SHARED_SECRET)) {
|
||||
return(list(status=403,
|
||||
body='<h1>403 Forbidden</h1><p>Shared secret mismatch</p>',
|
||||
headers=list('Content-Type' = 'text/html')))
|
||||
@@ -388,6 +425,22 @@ HandlerManager <- R6Class("HandlerManager",
|
||||
)
|
||||
)
|
||||
|
||||
maybeInjectAutoreload <- function(resp) {
|
||||
if (getOption("shiny.autoreload", FALSE) &&
|
||||
isTRUE(grepl("^text/html($|;)", resp$content_type)) &&
|
||||
is.character(resp$content)) {
|
||||
|
||||
resp$content <- gsub(
|
||||
"</head>",
|
||||
"<script src=\"shared/shiny-autoreload.js\"></script>\n</head>",
|
||||
resp$content,
|
||||
fixed = TRUE
|
||||
)
|
||||
}
|
||||
|
||||
resp
|
||||
}
|
||||
|
||||
# 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
|
||||
|
||||
720
R/mock-session.R
Normal file
720
R/mock-session.R
Normal file
@@ -0,0 +1,720 @@
|
||||
# Promise helpers taken from:
|
||||
# https://github.com/rstudio/promises/blob/master/tests/testthat/common.R
|
||||
# Block until all pending later tasks have executed
|
||||
wait_for_it <- function() {
|
||||
while (!later::loop_empty()) {
|
||||
later::run_now(0.1)
|
||||
}
|
||||
}
|
||||
|
||||
# Block until the promise is resolved/rejected. If resolved, return the value.
|
||||
# If rejected, throw (yes throw, not return) the error.
|
||||
#' @importFrom promises %...!%
|
||||
#' @importFrom promises %...>%
|
||||
extract <- function(promise) {
|
||||
promise_value <- NULL
|
||||
error <- NULL
|
||||
promise %...>%
|
||||
(function(value) promise_value <<- value) %...!%
|
||||
(function(reason) error <<- reason)
|
||||
|
||||
wait_for_it()
|
||||
if (!is.null(error))
|
||||
stop(error)
|
||||
else
|
||||
promise_value
|
||||
}
|
||||
|
||||
# TODO: is there a way to get this behavior without exporting these functions? R6?
|
||||
# TODO: clientData is documented as a reactiveValues, which this is not. Is it possible that
|
||||
# users are currently assigning into clientData? That would not work as expected here.
|
||||
#' @noRd
|
||||
#' @export
|
||||
`$.mockclientdata` <- function(x, name) {
|
||||
if (name == "pixelratio") { return(1) }
|
||||
if (name == "url_protocol") { return("http:") }
|
||||
if (name == "url_hostname") { return("mocksession") }
|
||||
if (name == "url_port") { return(1234) }
|
||||
if (name == "url_pathname") { return("/mockpath") }
|
||||
if (name == "url_hash") { return("#mockhash") }
|
||||
if (name == "url_hash_initial") { return("#mockhash") }
|
||||
if (name == "url_search") { return("?mocksearch=1") }
|
||||
|
||||
clientRE <- "^output_(.+)_([^_]+)$"
|
||||
if(grepl(clientRE, name)) {
|
||||
# TODO: use proper regex group matching here instead of redundantly parsing
|
||||
el <- sub(clientRE, "\\1", name)
|
||||
att <- sub(clientRE, "\\2", name)
|
||||
|
||||
if (att == "width") {
|
||||
return(600)
|
||||
} else if (att == "height") {
|
||||
return(400)
|
||||
} else if (att == "hidden") {
|
||||
return(FALSE)
|
||||
}
|
||||
}
|
||||
warning("Unexpected clientdata attribute accessed: ", name)
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
#' @noRd
|
||||
#' @export
|
||||
`[[.mockclientdata` <- `$.mockclientdata`
|
||||
|
||||
#' @noRd
|
||||
#' @export
|
||||
`[.mockclientdata` <- function(values, name) {
|
||||
stop("Single-bracket indexing of mockclientdata is not allowed.")
|
||||
}
|
||||
|
||||
#' @noRd
|
||||
mapNames <- function(func, vals) {
|
||||
names(vals) <- vapply(names(vals), func, character(1))
|
||||
vals
|
||||
}
|
||||
|
||||
#' Returns a noop implementation of the public method `name` of ShinySession.
|
||||
#' @include shiny.R
|
||||
#' @noRd
|
||||
makeNoop <- function(name, msg = paste0(name, " is a noop.")) {
|
||||
if (!(name %in% names(ShinySession$public_methods)))
|
||||
stop(name, " is not public method of ShinySession.")
|
||||
impl <- ShinySession$public_methods[[name]]
|
||||
body(impl) <- rlang::expr({
|
||||
# Force arguments
|
||||
!!lapply(formalArgs(impl), rlang::sym)
|
||||
# Evade "no visible binding" note for reference to `private`
|
||||
(!!as.symbol("private"))$noopWarn(!!name, !!msg)
|
||||
invisible()
|
||||
})
|
||||
impl
|
||||
}
|
||||
|
||||
#' Accepts a series of symbols as arguments and generates corresponding noop
|
||||
#' implementations.
|
||||
#' @noRd
|
||||
makeWarnNoops <- function(...) {
|
||||
methods <- as.character(list(...))
|
||||
names(methods) <- methods
|
||||
lapply(methods, makeNoop)
|
||||
}
|
||||
|
||||
#' Returns an implementation of a ShinySession public method that signals an
|
||||
#' error.
|
||||
#' @include shiny.R
|
||||
#' @noRd
|
||||
makeError <- function(name, msg = paste0(name, " is for internal use only.")) {
|
||||
if (!(name %in% names(ShinySession$public_methods)))
|
||||
stop(name, " is not public method of ShinySession.")
|
||||
impl <- ShinySession$public_methods[[name]]
|
||||
body(impl) <- rlang::expr({
|
||||
base::stop(!!msg)
|
||||
})
|
||||
impl
|
||||
}
|
||||
|
||||
#' Accepts a series of named arguments. Each name corresponds to a ShinySession
|
||||
#' public method that should signal an error, and each argument corresponds to
|
||||
#' an error message.
|
||||
#' @noRd
|
||||
makeErrors <- function(...) {
|
||||
errors <- rlang::list2(...)
|
||||
mapply(makeError, names(errors), errors, USE.NAMES = TRUE, SIMPLIFY = FALSE)
|
||||
}
|
||||
|
||||
#' @noRd
|
||||
makeExtraMethods <- function() {
|
||||
c(makeWarnNoops(
|
||||
"allowReconnect",
|
||||
"decrementBusyCount",
|
||||
"doBookmark",
|
||||
"exportTestValues",
|
||||
"flushOutput",
|
||||
"getBookmarkExclude",
|
||||
"getTestSnapshotUrl",
|
||||
"incrementBusyCount",
|
||||
"manageHiddenOutputs",
|
||||
"manageInputs",
|
||||
"onBookmark",
|
||||
"onBookmarked",
|
||||
"onInputReceived",
|
||||
"onRestore",
|
||||
"onRestored",
|
||||
"outputOptions",
|
||||
"reactlog",
|
||||
# TODO Consider implementing this. Would require a new method like
|
||||
# session$getDataObj() to access in a test expression.
|
||||
"registerDataObj",
|
||||
"reload",
|
||||
"resetBrush",
|
||||
"sendBinaryMessage",
|
||||
"sendChangeTabVisibility",
|
||||
"sendCustomMessage",
|
||||
"sendInputMessage",
|
||||
"sendInsertTab",
|
||||
"sendInsertUI",
|
||||
"sendModal",
|
||||
"setCurrentTheme",
|
||||
"sendNotification",
|
||||
"sendProgress",
|
||||
"sendRemoveTab",
|
||||
"sendRemoveUI",
|
||||
"setBookmarkExclude",
|
||||
"setShowcase",
|
||||
"showProgress",
|
||||
"updateQueryString"
|
||||
), makeErrors(
|
||||
`@uploadEnd` = "for internal use only",
|
||||
`@uploadInit` = "for internal use only",
|
||||
createBookmarkObservers = "for internal use only",
|
||||
dispatch = "for internal use only",
|
||||
handleRequest = "for internal use only",
|
||||
requestFlush = "for internal use only",
|
||||
startTiming = "for internal use only",
|
||||
wsClosed = "for internal use only"
|
||||
))
|
||||
}
|
||||
|
||||
#' @description Adds generated instance methods to a MockShinySession instance.
|
||||
#' Note that `lock_objects = FALSE` must be set in the call to `R6Class()`
|
||||
#' that produced the generator object of the instance.
|
||||
#' @param instance instance of an R6 object, generally a `MockShinySession`.
|
||||
#' @param methods named list of method names to method implementation functions.
|
||||
#' In our typical usage, each function is derived from a public method of
|
||||
#' `ShinySession`. The environment of each implementation function is set to
|
||||
#' `instance$.__enclos_env` before the method is added.
|
||||
#' @noRd
|
||||
addGeneratedInstanceMethods <- function(instance, methods = makeExtraMethods()) {
|
||||
mapply(function(name, impl) {
|
||||
environment(impl) <- instance$.__enclos_env__
|
||||
instance[[name]] <- impl
|
||||
}, names(methods), methods)
|
||||
}
|
||||
|
||||
#' Mock Shiny Session
|
||||
#'
|
||||
#' @description An R6 class suitable for testing purposes. Simulates, to the
|
||||
#' extent possible, the behavior of the `ShinySession` class. The `session`
|
||||
#' parameter provided to Shiny server functions and modules is an instance of
|
||||
#' a `ShinySession` in normal operation.
|
||||
#'
|
||||
#' Most kinds of module and server testing do not require this class be
|
||||
#' instantiated manually. See instead [testServer()].
|
||||
#'
|
||||
#' In order to support advanced usage, instances of `MockShinySession` are
|
||||
#' **unlocked** so that public methods and fields of instances may be
|
||||
#' modified. For example, in order to test authentication workflows, the
|
||||
#' `user` or `groups` fields may be overridden. Modified instances of
|
||||
#' `MockShinySession` may then be passed explicitly as the `session` argument
|
||||
#' of [testServer()].
|
||||
#'
|
||||
#' @include timer.R
|
||||
#' @export
|
||||
MockShinySession <- R6Class(
|
||||
'MockShinySession',
|
||||
portable = FALSE,
|
||||
lock_objects = FALSE,
|
||||
public = list(
|
||||
#' @field env The environment associated with the session.
|
||||
env = NULL,
|
||||
#' @field returned The value returned by the module under test.
|
||||
returned = NULL,
|
||||
#' @field singletons Hardcoded as empty. Needed for rendering HTML (i.e. renderUI).
|
||||
singletons = character(0),
|
||||
#' @field clientData Mock client data that always returns a size for plots.
|
||||
clientData = structure(list(), class="mockclientdata"),
|
||||
#' @field output The shinyoutputs associated with the session.
|
||||
output = NULL,
|
||||
#' @field input The reactive inputs associated with the session.
|
||||
input = NULL,
|
||||
#' @field userData An environment initialized as empty.
|
||||
userData = NULL,
|
||||
#' @field progressStack A stack of progress objects.
|
||||
progressStack = 'Stack',
|
||||
#' @field token On a real `ShinySession`, used to identify this instance in URLs.
|
||||
token = 'character',
|
||||
#' @field cache The session cache MemoryCache.
|
||||
cache = NULL,
|
||||
#' @field appcache The app cache MemoryCache.
|
||||
appcache = NULL,
|
||||
#' @field restoreContext Part of bookmarking support in a real
|
||||
#' `ShinySession` but always `NULL` for a `MockShinySession`.
|
||||
restoreContext = NULL,
|
||||
#' @field groups Character vector of groups associated with an authenticated
|
||||
#' user. Always `NULL` for a `MockShinySesion`.
|
||||
groups = NULL,
|
||||
#' @field user The username of an authenticated user. Always `NULL` for a
|
||||
#' `MockShinySession`.
|
||||
user = NULL,
|
||||
#' @field options A list containing session-level shinyOptions.
|
||||
options = NULL,
|
||||
|
||||
#' @description Create a new MockShinySession.
|
||||
initialize = function() {
|
||||
private$.input <- ReactiveValues$new(dedupe = FALSE, label = "input")
|
||||
private$flushCBs <- Callbacks$new()
|
||||
private$flushedCBs <- Callbacks$new()
|
||||
private$endedCBs <- Callbacks$new()
|
||||
|
||||
private$file_generators <- fastmap()
|
||||
|
||||
private$timer <- MockableTimerCallbacks$new()
|
||||
self$progressStack <- Stack$new()
|
||||
|
||||
self$userData <- new.env(parent=emptyenv())
|
||||
|
||||
# create output
|
||||
out <- .createOutputWriter(self)
|
||||
class(out) <- "shinyoutput"
|
||||
self$output <- out
|
||||
|
||||
# Create a read-only copy of the inputs reactive.
|
||||
self$input <- .createReactiveValues(private$.input, readonly = TRUE)
|
||||
|
||||
self$token <- createUniqueId(16)
|
||||
|
||||
# Copy app-level options
|
||||
self$options <- getCurrentAppState()$options
|
||||
|
||||
self$cache <- MemoryCache$new()
|
||||
self$appcache <- MemoryCache$new()
|
||||
|
||||
# Adds various generated noop and error-producing method implementations.
|
||||
# Note that noop methods can be configured to produce warnings by setting
|
||||
# the option shiny.mocksession.warn = TRUE; see $noopWarn() for details.
|
||||
addGeneratedInstanceMethods(self)
|
||||
},
|
||||
#' @description Define a callback to be invoked before a reactive flush
|
||||
#' @param fun The function to invoke
|
||||
#' @param once If `TRUE`, will only run once. Otherwise, will run every time reactives are flushed.
|
||||
onFlush = function(fun, once=TRUE) {
|
||||
if (!isTRUE(once)) {
|
||||
return(private$flushCBs$register(fun))
|
||||
} else {
|
||||
dereg <- private$flushCBs$register(function() {
|
||||
dereg()
|
||||
fun()
|
||||
})
|
||||
return(dereg)
|
||||
}
|
||||
},
|
||||
#' @description Define a callback to be invoked after a reactive flush
|
||||
#' @param fun The function to invoke
|
||||
#' @param once If `TRUE`, will only run once. Otherwise, will run every time reactives are flushed.
|
||||
onFlushed = function(fun, once=TRUE) {
|
||||
if (!isTRUE(once)) {
|
||||
return(private$flushedCBs$register(fun))
|
||||
} else {
|
||||
dereg <- private$flushedCBs$register(function() {
|
||||
dereg()
|
||||
fun()
|
||||
})
|
||||
return(dereg)
|
||||
}
|
||||
},
|
||||
#' @description Define a callback to be invoked when the session ends
|
||||
#' @param sessionEndedCallback The callback to invoke when the session has ended.
|
||||
onEnded = function(sessionEndedCallback) {
|
||||
private$endedCBs$register(sessionEndedCallback)
|
||||
},
|
||||
|
||||
#' @description Returns `FALSE` if the session has not yet been closed
|
||||
isEnded = function(){ private$was_closed },
|
||||
#' @description Returns `FALSE` if the session has not yet been closed
|
||||
isClosed = function(){ private$was_closed },
|
||||
#' @description Closes the session
|
||||
close = function(){
|
||||
for (output in private$output) {
|
||||
output$suspend()
|
||||
}
|
||||
withReactiveDomain(self, {
|
||||
private$endedCBs$invoke(onError = printError, ..stacktraceon = TRUE)
|
||||
})
|
||||
private$was_closed <- TRUE
|
||||
},
|
||||
|
||||
#FIXME: this is wrong. Will need to be more complex.
|
||||
#' @description Unsophisticated mock implementation that merely invokes
|
||||
# the given callback immediately.
|
||||
#' @param callback The callback to be invoked.
|
||||
cycleStartAction = function(callback){ callback() },
|
||||
|
||||
#' @description Base64-encode the given file. Needed for image rendering.
|
||||
#' @param name Not used
|
||||
#' @param file The file to be encoded
|
||||
#' @param contentType The content type of the base64-encoded string
|
||||
fileUrl = function(name, file, contentType='application/octet-stream') {
|
||||
bytes <- file.info(file)$size
|
||||
if (is.na(bytes))
|
||||
return(NULL)
|
||||
|
||||
fileData <- readBin(file, 'raw', n=bytes)
|
||||
b64 <- rawToBase64(fileData)
|
||||
return(paste('data:', contentType, ';base64,', b64, sep=''))
|
||||
},
|
||||
|
||||
#' @description Sets reactive values associated with the `session$inputs`
|
||||
#' object and flushes the reactives.
|
||||
#' @param ... The inputs to set. These arguments are processed with
|
||||
#' [rlang::list2()] and so are _[dynamic][rlang::dyn-dots]_. Input names
|
||||
#' may not be duplicated.
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' session$setInputs(x=1, y=2)
|
||||
#' }
|
||||
setInputs = function(...) {
|
||||
vals <- rlang::dots_list(..., .homonyms = "error")
|
||||
mapply(names(vals), vals, FUN = function(name, value) {
|
||||
private$.input$set(name, value)
|
||||
})
|
||||
private$flush()
|
||||
},
|
||||
|
||||
#' @description An internal method which shouldn't be used by others.
|
||||
#' Schedules `callback` for execution after some number of `millis`
|
||||
#' milliseconds.
|
||||
#' @param millis The number of milliseconds on which to schedule a callback
|
||||
#' @param callback The function to schedule.
|
||||
.scheduleTask = function(millis, callback) {
|
||||
id <- private$timer$schedule(millis, callback)
|
||||
|
||||
# Return a deregistration callback
|
||||
function() {
|
||||
invisible(private$timer$unschedule(id))
|
||||
}
|
||||
},
|
||||
|
||||
#' @description Simulate the passing of time by the given number of milliseconds.
|
||||
#' @param millis The number of milliseconds to advance time.
|
||||
elapse = function(millis) {
|
||||
msLeft <- millis
|
||||
|
||||
while (msLeft > 0){
|
||||
t <- private$timer$timeToNextEvent()
|
||||
|
||||
if (is.infinite(t) || t <= 0 || msLeft < t){
|
||||
# Either there's no good upcoming event or we can't make it to it in the allotted time.
|
||||
break
|
||||
}
|
||||
msLeft <- msLeft - t
|
||||
private$timer$elapse(t)
|
||||
|
||||
# timerCallbacks must run before flushReact.
|
||||
private$timer$executeElapsed()
|
||||
private$flush()
|
||||
}
|
||||
|
||||
private$timer$elapse(msLeft)
|
||||
|
||||
# Run again in case our callbacks resulted in a scheduled
|
||||
# function that needs executing.
|
||||
private$timer$executeElapsed()
|
||||
private$flush()
|
||||
},
|
||||
|
||||
#' @description An internal method which shouldn't be used by others.
|
||||
#' @return Elapsed time in milliseconds.
|
||||
.now = function() {
|
||||
private$timer$getElapsed()
|
||||
},
|
||||
|
||||
#' @description An internal method which shouldn't be used by others.
|
||||
#' Defines an output in a way that sets private$currentOutputName
|
||||
#' appropriately.
|
||||
#' @param name The name of the output.
|
||||
#' @param func The render definition.
|
||||
#' @param label Not used.
|
||||
defineOutput = function(name, func, label) {
|
||||
force(name)
|
||||
|
||||
if (!is.null(private$outs[[name]]$obs)) {
|
||||
private$outs[[name]]$obs$destroy()
|
||||
}
|
||||
|
||||
if (is.null(func)) func <- missingOutput
|
||||
|
||||
if (!is.function(func))
|
||||
stop(paste("Unexpected", class(func), "output for", name))
|
||||
|
||||
obs <- observe({
|
||||
# We could just stash the promise, but we get an "unhandled promise error". This bypasses
|
||||
prom <- NULL
|
||||
tryCatch({
|
||||
v <- private$withCurrentOutput(name, func(self, name))
|
||||
if (!promises::is.promise(v)){
|
||||
# Make our sync value into a promise
|
||||
prom <- promises::promise(function(resolve, reject){ resolve(v) })
|
||||
} else {
|
||||
prom <- v
|
||||
}
|
||||
}, error=function(e){
|
||||
# Error running value()
|
||||
prom <<- promises::promise(function(resolve, reject){ reject(e) })
|
||||
})
|
||||
|
||||
private$outs[[name]]$promise <- hybrid_chain(
|
||||
prom,
|
||||
function(v){
|
||||
list(val = v, err = NULL)
|
||||
}, catch=function(e){
|
||||
list(val = NULL, err = e)
|
||||
})
|
||||
})
|
||||
private$outs[[name]] <- list(obs = obs, func = func, promise = NULL)
|
||||
},
|
||||
|
||||
#' @description An internal method which shouldn't be used by others. Forces
|
||||
#' evaluation of any reactive dependencies of the output function.
|
||||
#' @param name The name of the output.
|
||||
#' @return The return value of the function responsible for rendering the
|
||||
#' output.
|
||||
getOutput = function(name) {
|
||||
# Unlike the real outputs, we're going to return the last value rather than the unevaluated function
|
||||
if (is.null(private$outs[[name]])) {
|
||||
stop("The test referenced an output that hasn't been defined yet: output$", name)
|
||||
}
|
||||
|
||||
if (is.null(private$outs[[name]]$promise)) {
|
||||
# Means the output was defined but the observer hasn't had a chance to run
|
||||
# yet. Run flushReact() now to force the observer to run.
|
||||
flushReact()
|
||||
|
||||
if (is.null(private$outs[[name]]$promise)) {
|
||||
stop("output$", name, " encountered an unexpected error resolving its promise")
|
||||
}
|
||||
}
|
||||
|
||||
# Make promise return
|
||||
v <- extract(private$outs[[name]]$promise)
|
||||
if (!is.null(v$err)){
|
||||
stop(v$err)
|
||||
} else if (private$file_generators$has(self$ns(name))) {
|
||||
download <- private$file_generators$get(self$ns(name))
|
||||
private$renderFile(self$ns(name), download)
|
||||
} else {
|
||||
v$val
|
||||
}
|
||||
},
|
||||
|
||||
#' @description Returns the given id prefixed by this namespace's id.
|
||||
#' @param id The id to prefix with a namespace id.
|
||||
#' @return The id with a namespace prefix.
|
||||
ns = function(id) {
|
||||
NS(private$nsPrefix, id)
|
||||
},
|
||||
#' @description Trigger a reactive flush right now.
|
||||
flushReact = function(){
|
||||
private$flush()
|
||||
},
|
||||
#' @description Create and return a namespace-specific session proxy.
|
||||
#' @param namespace Character vector indicating a namespace.
|
||||
#' @return A new session proxy.
|
||||
makeScope = function(namespace) {
|
||||
ns <- NS(namespace)
|
||||
createSessionProxy(
|
||||
self,
|
||||
input = .createReactiveValues(private$.input, readonly = TRUE, ns = ns),
|
||||
output = structure(.createOutputWriter(self, ns = ns), class = "shinyoutput"),
|
||||
makeScope = function(namespace) self$makeScope(ns(namespace)),
|
||||
ns = function(namespace) ns(namespace),
|
||||
setInputs = function(...) {
|
||||
self$setInputs(!!!mapNames(ns, rlang::dots_list(..., .homonyms = "error")))
|
||||
}
|
||||
)
|
||||
},
|
||||
#' @description Set the environment associated with a testServer() call, but
|
||||
#' only if it has not previously been set. This ensures that only the
|
||||
#' environment of the outermost module under test is the one retained. In
|
||||
#' other words, the first assignment wins.
|
||||
#' @param env The environment to retain.
|
||||
#' @return The provided `env`.
|
||||
setEnv = function(env) {
|
||||
if (is.null(self$env)) {
|
||||
stopifnot(all(c("input", "output", "session") %in% ls(env)))
|
||||
self$env <- env
|
||||
}
|
||||
},
|
||||
#' @description Set the value returned by the module call and proactively
|
||||
#' flush. Note that this method may be called multiple times if modules
|
||||
#' are nested. The last assignment, corresponding to an invocation of
|
||||
#' setReturned() in the outermost module, wins.
|
||||
#' @param value The value returned from the module
|
||||
#' @return The provided `value`.
|
||||
setReturned = function(value) {
|
||||
self$returned <- value
|
||||
value
|
||||
},
|
||||
#' @description Get the value returned by the module call.
|
||||
#' @return The value returned by the module call
|
||||
getReturned = function() self$returned,
|
||||
#' @description Generate a distinct character identifier for use as a proxy
|
||||
#' namespace.
|
||||
#' @return A character identifier unique to the current session.
|
||||
genId = function() {
|
||||
private$idCounter <- private$idCounter + 1
|
||||
paste0("proxy", private$idCounter)
|
||||
},
|
||||
#' @description Provides a way to access the root `MockShinySession` from
|
||||
#' any descendant proxy.
|
||||
#' @return The root `MockShinySession`.
|
||||
rootScope = function() {
|
||||
self
|
||||
},
|
||||
#' @description Called by observers when a reactive expression errors.
|
||||
#' @param e An error object.
|
||||
unhandledError = function(e) {
|
||||
self$close()
|
||||
},
|
||||
#' @description Freeze a value until the flush cycle completes.
|
||||
#' @param x A `ReactiveValues` object.
|
||||
#' @param name The name of a reactive value within `x`.
|
||||
freezeValue = function(x, name) {
|
||||
if (!is.reactivevalues(x))
|
||||
stop("x must be a reactivevalues object")
|
||||
|
||||
impl <- .subset2(x, 'impl')
|
||||
key <- .subset2(x, 'ns')(name)
|
||||
impl$freeze(key)
|
||||
self$onFlushed(function() impl$thaw(key))
|
||||
},
|
||||
#' @description Registers the given callback to be invoked when the session
|
||||
#' is closed (i.e. the connection to the client has been severed). The
|
||||
#' return value is a function which unregisters the callback. If multiple
|
||||
#' callbacks are registered, the order in which they are invoked is not
|
||||
#' guaranteed.
|
||||
#' @param sessionEndedCallback Function to call when the session ends.
|
||||
onSessionEnded = function(sessionEndedCallback) {
|
||||
self$onEnded(sessionEndedCallback)
|
||||
},
|
||||
#' @description Associated a downloadable file with the session.
|
||||
#' @param name The un-namespaced output name to associate with the
|
||||
#' downloadable file.
|
||||
#' @param filename A string or function designating the name of the file.
|
||||
#' @param contentType A string of the content type of the file. Not used by
|
||||
#' `MockShinySession`.
|
||||
#' @param content A function that takes a single argument file that is a
|
||||
#' file path (string) of a nonexistent temp file, and writes the content
|
||||
#' to that file path. (Reactive values and functions may be used from this
|
||||
#' function.)
|
||||
registerDownload = function(name, filename, contentType, content) {
|
||||
private$file_generators$set(self$ns(name), list(
|
||||
filename = if (is.function(filename)) filename else function() filename,
|
||||
content = content
|
||||
))
|
||||
},
|
||||
#' @description Get information about the output that is currently being
|
||||
#' executed.
|
||||
#' @return A list with with the `name` of the output. If no output is
|
||||
#' currently being executed, this will return `NULL`.
|
||||
getCurrentOutputInfo = function() {
|
||||
name <- private$currentOutputName
|
||||
if (is.null(name)) NULL else list(name = name)
|
||||
}
|
||||
),
|
||||
private = list(
|
||||
# @field .input Internal ReactiveValues object for normal input sent from client.
|
||||
.input = NULL,
|
||||
# @field flushCBs `Callbacks` called before flush.
|
||||
flushCBs = NULL,
|
||||
# @field flushedCBs `Callbacks` called after flush.
|
||||
flushedCBs = NULL,
|
||||
# @field endedCBs `Callbacks` called when session ends.
|
||||
endedCBs = NULL,
|
||||
# @field timer `MockableTimerCallbacks` called at particular times.
|
||||
timer = NULL,
|
||||
# @field was_closed Set to `TRUE` once the session is closed.
|
||||
was_closed = FALSE,
|
||||
# @field outs List of namespaced output names.
|
||||
outs = list(),
|
||||
# @field nsPrefix Prefix with which to namespace inputs and outputs.
|
||||
nsPrefix = "mock-session",
|
||||
# @field idCounter Incremented every time `$genId()` is called.
|
||||
idCounter = 0,
|
||||
# @field file_generators Map of namespaced output names to lists with
|
||||
# `filename` and `output` elements, each a function. Updated by
|
||||
# `$registerDownload()` and read by `$getOutput()`. Files are generated
|
||||
# on demand when the output is accessed.
|
||||
file_generators = NULL,
|
||||
# @field currentOutputName Namespaced name of the currently executing
|
||||
#' output, or `NULL` if no output is currently executing.
|
||||
currentOutputName = NULL,
|
||||
|
||||
# @description Writes a downloadable file to disk. If the `content` function
|
||||
# associated with a download handler does not write a file, an error is
|
||||
# signaled. Created files are deleted upon session close.
|
||||
# @param name The eamespaced output name associated with the downloadable
|
||||
# file.
|
||||
# @param download List with two names, `filename` and `content`. Both should
|
||||
# be functions. `filename` should take no arguments and return a string.
|
||||
# `content` should accept a path argument and create a file at that path.
|
||||
# @return A path to a temp file.
|
||||
renderFile = function(name, download) {
|
||||
# We make our own tempdir here because it's not safe to delete the result
|
||||
# of tempdir().
|
||||
tmpd <- tempfile()
|
||||
dir.create(tmpd, recursive = TRUE)
|
||||
self$onSessionEnded(function() unlink(tmpd, recursive = TRUE))
|
||||
file <- file.path(tmpd, download$filename())
|
||||
download$content(file)
|
||||
if (!file.exists(file))
|
||||
error("downloadHandler for ", name, " did not write a file.")
|
||||
file
|
||||
},
|
||||
|
||||
# @description Calls `shiny:::flushReact()` and executes all callbacks
|
||||
# related to reactivity.
|
||||
flush = function(){
|
||||
isolate(private$flushCBs$invoke(..stacktraceon = TRUE))
|
||||
shiny:::flushReact() # namespace to avoid calling our own method
|
||||
isolate(private$flushedCBs$invoke(..stacktraceon = TRUE))
|
||||
later::run_now()
|
||||
},
|
||||
|
||||
# @description Produces a warning if the option `shiny.mocksession.warn` is
|
||||
# unset and not `FALSE`.
|
||||
# @param name The name of the mocked method.
|
||||
# @param msg A message describing why the method is not implemented.
|
||||
noopWarn = function(name, msg) {
|
||||
if (getOption("shiny.mocksession.warn", FALSE) == FALSE)
|
||||
return(invisible())
|
||||
out <- paste0(name, " is not fully implemented by MockShinySession: ", msg)
|
||||
out <- paste0(out, "\n", "To disable messages like this, run `options(shiny.mocksession.warn=FALSE)`")
|
||||
warning(out, call. = FALSE)
|
||||
},
|
||||
|
||||
# @description Binds a domain to `expr` and uses `createVarPromiseDomain()`
|
||||
# to ensure `private$currentOutputName` is set to `name` around any of
|
||||
# the promise's callbacks. Domains are something like dynamic scopes but
|
||||
# for promise chains instead of the call stack.
|
||||
# @return A promise.
|
||||
withCurrentOutput = function(name, expr) {
|
||||
if (!is.null(private$currentOutputName)) {
|
||||
stop("Nested calls to withCurrentOutput() are not allowed.")
|
||||
}
|
||||
|
||||
promises::with_promise_domain(
|
||||
createVarPromiseDomain(private, "currentOutputName", name),
|
||||
expr
|
||||
)
|
||||
}
|
||||
),
|
||||
active = list(
|
||||
#' @field files For internal use only.
|
||||
files = function() stop("$files is for internal use only."),
|
||||
#' @field downloads For internal use only.
|
||||
downloads = function() stop("$downloads is for internal use only."),
|
||||
#' @field closed Deprecated in `ShinySession` and signals an error.
|
||||
closed = function() stop("$closed is deprecated"),
|
||||
#' @field session Deprecated in ShinySession and signals an error.
|
||||
session = function() stop("$session is deprecated"),
|
||||
#' @field request An empty environment where the request should be. The request isn't meaningfully mocked currently.
|
||||
request = function(value) {
|
||||
if (!missing(value)){
|
||||
stop("session$request can't be assigned to")
|
||||
}
|
||||
warning("session$request doesn't currently simulate a realistic request on MockShinySession")
|
||||
new.env(parent=emptyenv())
|
||||
}
|
||||
)
|
||||
)
|
||||
43
R/modal.R
43
R/modal.R
@@ -1,13 +1,13 @@
|
||||
#' 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}}.
|
||||
#' typically used with [modalDialog()].
|
||||
#'
|
||||
#' @param ui UI content to show in the modal.
|
||||
#' @param session The \code{session} object passed to function given to
|
||||
#' \code{shinyServer}.
|
||||
#' @param session The `session` object passed to function given to
|
||||
#' `shinyServer`.
|
||||
#'
|
||||
#' @seealso \code{\link{modalDialog}} for examples.
|
||||
#' @seealso [modalDialog()] for examples.
|
||||
#' @export
|
||||
showModal <- function(ui, session = getDefaultReactiveDomain()) {
|
||||
res <- processDeps(ui, session)
|
||||
@@ -29,21 +29,27 @@ removeModal <- function(session = getDefaultReactiveDomain()) {
|
||||
|
||||
#' 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.
|
||||
#' @description
|
||||
#' `modalDialog()` 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 user name and
|
||||
#' password input.
|
||||
#'
|
||||
#' `modalButton()` creates a button that will dismiss the dialog when clicked,
|
||||
#' typically used when customising the `footer`.
|
||||
#'
|
||||
#' @inheritParams actionButton
|
||||
#' @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
|
||||
#' @param footer UI for footer. Use `NULL` for no footer.
|
||||
#' @param size One of `"s"` for small, `"m"` (the default) for medium,
|
||||
#' or `"l"` for large.
|
||||
#' @param easyClose If `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
|
||||
#' `FALSE` (the default), the modal dialog can't be dismissed in those
|
||||
#' ways; instead it must be dismissed by clicking on a `modalButton()`, or
|
||||
#' from a call to [removeModal()] on the server.
|
||||
#' @param fade If `FALSE`, the modal dialog will have no fade-in animation
|
||||
#' (it will simply appear rather than fade in to view).
|
||||
#'
|
||||
#' @examples
|
||||
@@ -169,13 +175,8 @@ modalDialog <- function(..., title = NULL, footer = modalButton("Dismiss"),
|
||||
)
|
||||
}
|
||||
|
||||
#' 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
|
||||
#' @rdname modalDialog
|
||||
modalButton <- function(label, icon = NULL) {
|
||||
tags$button(type = "button", class = "btn btn-default",
|
||||
`data-dismiss` = "modal", validateIcon(icon), label
|
||||
|
||||
115
R/modules.R
115
R/modules.R
@@ -36,13 +36,118 @@ createSessionProxy <- function(parentSession, ...) {
|
||||
|
||||
`[[<-.session_proxy` <- `$<-.session_proxy`
|
||||
|
||||
|
||||
#' Invoke a Shiny module
|
||||
#' Shiny modules
|
||||
#'
|
||||
#' 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.
|
||||
#' <http://shiny.rstudio.com/articles/modules.html> to learn more.
|
||||
#'
|
||||
#' Starting in Shiny 1.5.0, we recommend using `moduleServer` instead of
|
||||
#' [`callModule()`], because the syntax is a little easier
|
||||
#' to understand, and modules created with `moduleServer` can be tested with
|
||||
#' [`testServer()`].
|
||||
#'
|
||||
#' @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 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 <http://shiny.rstudio.com/articles/modules.html>
|
||||
#'
|
||||
#' @examples
|
||||
#' # Define the UI for a module
|
||||
#' counterUI <- function(id, label = "Counter") {
|
||||
#' ns <- NS(id)
|
||||
#' tagList(
|
||||
#' actionButton(ns("button"), label = label),
|
||||
#' verbatimTextOutput(ns("out"))
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
#' # Define the server logic for a module
|
||||
#' counterServer <- function(id) {
|
||||
#' moduleServer(
|
||||
#' id,
|
||||
#' function(input, output, session) {
|
||||
#' count <- reactiveVal(0)
|
||||
#' observeEvent(input$button, {
|
||||
#' count(count() + 1)
|
||||
#' })
|
||||
#' output$out <- renderText({
|
||||
#' count()
|
||||
#' })
|
||||
#' count
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
#' # Use the module in an app
|
||||
#' ui <- fluidPage(
|
||||
#' counterUI("counter1", "Counter #1"),
|
||||
#' counterUI("counter2", "Counter #2")
|
||||
#' )
|
||||
#' server <- function(input, output, session) {
|
||||
#' counterServer("counter1")
|
||||
#' counterServer("counter2")
|
||||
#' }
|
||||
#' if (interactive()) {
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
#'
|
||||
#' # If you want to pass extra parameters to the module's server logic, you can
|
||||
#' # add them to your function. In this case `prefix` is text that will be
|
||||
#' # printed before the count.
|
||||
#' counterServer2 <- function(id, prefix = NULL) {
|
||||
#' moduleServer(
|
||||
#' id,
|
||||
#' function(input, output, session) {
|
||||
#' count <- reactiveVal(0)
|
||||
#' observeEvent(input$button, {
|
||||
#' count(count() + 1)
|
||||
#' })
|
||||
#' output$out <- renderText({
|
||||
#' paste0(prefix, count())
|
||||
#' })
|
||||
#' count
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' counterUI("counter", "Counter"),
|
||||
#' )
|
||||
#' server <- function(input, output, session) {
|
||||
#' counterServer2("counter", "The current count is: ")
|
||||
#' }
|
||||
#' if (interactive()) {
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
moduleServer <- function(id, module, session = getDefaultReactiveDomain()) {
|
||||
if (inherits(session, "MockShinySession")) {
|
||||
body(module) <- rlang::expr({
|
||||
session$setEnv(base::environment())
|
||||
!!body(module)
|
||||
})
|
||||
session$setReturned(callModule(module, id, session = session))
|
||||
} else {
|
||||
callModule(module, id, session = session)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#' Invoke a Shiny module
|
||||
#'
|
||||
#' Note: As of Shiny 1.5.0, we recommend using [`moduleServer()`] instead of
|
||||
#' [`callModule()`], because the syntax is a little easier
|
||||
#' to understand, and modules created with `moduleServer` can be tested with
|
||||
#' [`testServer()`].
|
||||
#'
|
||||
#' @param module A Shiny module server function
|
||||
#' @param id An ID string that corresponds with the ID used to call the module's
|
||||
@@ -52,9 +157,11 @@ createSessionProxy <- function(parentSession, ...) {
|
||||
#' 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()) {
|
||||
if (!inherits(session, c("ShinySession", "session_proxy", "MockShinySession"))) {
|
||||
stop("session must be a ShinySession or session_proxy object.")
|
||||
}
|
||||
childScope <- session$makeScope(id)
|
||||
|
||||
withReactiveDomain(childScope, {
|
||||
|
||||
@@ -4,19 +4,21 @@
|
||||
#'
|
||||
#' @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}
|
||||
#' could be a link that the user can click on. This is separate from `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
|
||||
#' disappears. Use `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 closeButton If `TRUE`, display a button which will make the
|
||||
#' notification disappear when clicked. If `FALSE` do not display.
|
||||
#' @param id A unique identifier for the notification.
|
||||
#'
|
||||
#' `id` is optional for `showNotification()`: Shiny will automatically create
|
||||
#' one if needed. If you do supply it, Shiny will update an existing
|
||||
#' notification if it exists, otherwise it will create a new one.
|
||||
#'
|
||||
#' `id` is required for `removeNotification()`.
|
||||
#' @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.
|
||||
@@ -97,10 +99,8 @@ showNotification <- function(ui, action = NULL, duration = 5,
|
||||
|
||||
#' @rdname showNotification
|
||||
#' @export
|
||||
removeNotification <- function(id = NULL, session = getDefaultReactiveDomain()) {
|
||||
if (is.null(id)) {
|
||||
stop("id is required.")
|
||||
}
|
||||
removeNotification <- function(id, session = getDefaultReactiveDomain()) {
|
||||
force(id)
|
||||
session$sendNotification("remove", id)
|
||||
id
|
||||
}
|
||||
|
||||
147
R/progress.R
147
R/progress.R
@@ -3,67 +3,29 @@
|
||||
#' Reports progress to the user during long-running operations.
|
||||
#'
|
||||
#' This package exposes two distinct programming APIs for working with
|
||||
#' progress. \code{\link{withProgress}} and \code{\link{setProgress}}
|
||||
#' progress. [withProgress()] and [setProgress()]
|
||||
#' together provide a simple function-based interface, while the
|
||||
#' \code{Progress} reference class provides an object-oriented API.
|
||||
#' `Progress` reference class provides an object-oriented API.
|
||||
#'
|
||||
#' Instantiating a \code{Progress} object causes a progress panel to be
|
||||
#' created, and it will be displayed the first time the \code{set}
|
||||
#' method is called. Calling \code{close} will cause the progress panel
|
||||
#' Instantiating a `Progress` object causes a progress panel to be
|
||||
#' created, and it will be displayed the first time the `set`
|
||||
#' method is called. Calling `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
|
||||
#' CSS), you can use `style="old"` each time you call
|
||||
#' `Progress$new()`. If you don't want to set the style each time
|
||||
#' `Progress$new` is called, you can instead call
|
||||
#' [`shinyOptions(progress.style="old")`][shinyOptions] just once, inside the server
|
||||
#' function.
|
||||
#'
|
||||
#' \strong{Methods}
|
||||
#' \describe{
|
||||
#' \item{\code{initialize(session, min = 0, max = 1)}}{
|
||||
#' Creates a new progress panel (but does not display it).
|
||||
#' }
|
||||
#' \item{\code{set(value = NULL, message = NULL, detail = NULL)}}{
|
||||
#' Updates the progress panel. When called the first time, the
|
||||
#' progress panel is displayed.
|
||||
#' }
|
||||
#' \item{\code{inc(amount = 0.1, message = NULL, detail = NULL)}}{
|
||||
#' Like \code{set}, this updates the progress panel. The difference is
|
||||
#' that \code{inc} increases the progress bar by \code{amount}, instead
|
||||
#' of setting it to a specific value.
|
||||
#' }
|
||||
#' \item{\code{close()}}{
|
||||
#' Removes the progress panel. Future calls to \code{set} and
|
||||
#' \code{close} will be ignored.
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#' @param session The Shiny session object, as provided by
|
||||
#' \code{shinyServer} to the server function.
|
||||
#' @param min The value that represents the starting point of the
|
||||
#' progress bar. Must be less tham \code{max}.
|
||||
#' @param max The value that represents the end of the progress bar.
|
||||
#' Must be greater than \code{min}.
|
||||
#' @param message A single-element character vector; the message to be
|
||||
#' displayed to the user, or \code{NULL} to hide the current message
|
||||
#' (if any).
|
||||
#' @param detail A single-element character vector; the detail message
|
||||
#' to be 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 value A numeric value at which to set
|
||||
#' the progress bar, relative to \code{min} and \code{max}.
|
||||
#' @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.
|
||||
#' @param amount For the \code{inc()} method, a numeric value to increment the
|
||||
#' progress bar.
|
||||
#' displayed to the user, or `NULL` to hide the current message (if any).
|
||||
#' @param detail A single-element character vector; the detail message to be
|
||||
#' displayed to the user, or `NULL` to hide the current detail message (if
|
||||
#' any). The detail message will be shown with a de-emphasized appearance
|
||||
#' relative to `message`.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -91,7 +53,7 @@
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @seealso \code{\link{withProgress}}
|
||||
#' @seealso [withProgress()]
|
||||
#' @format NULL
|
||||
#' @usage NULL
|
||||
#' @export
|
||||
@@ -99,6 +61,17 @@ Progress <- R6Class(
|
||||
'Progress',
|
||||
public = list(
|
||||
|
||||
#' @description Creates a new progress panel (but does not display it).
|
||||
#' @param session The Shiny session object, as provided by `shinyServer` to
|
||||
#' the server function.
|
||||
#' @param min The value that represents the starting point of the progress
|
||||
#' bar. Must be less than `max`.
|
||||
#' @param max The value that represents the end of the progress bar. Must be
|
||||
#' greater than `min`.
|
||||
#' @param style Progress display style. If `"notification"` (the default),
|
||||
#' the progress indicator will show using Shiny's notification API. If
|
||||
#' `"old"`, use the same HTML and CSS used in Shiny 0.13.2 and below (this
|
||||
#' is for backward-compatibility).
|
||||
initialize = function(session = getDefaultReactiveDomain(),
|
||||
min = 0, max = 1,
|
||||
style = getShinyOption("progress.style", default = "notification"))
|
||||
@@ -117,6 +90,11 @@ Progress <- R6Class(
|
||||
session$sendProgress('open', list(id = private$id, style = private$style))
|
||||
},
|
||||
|
||||
#' @description Updates the progress panel. When called the first time, the
|
||||
#' progress panel is displayed.
|
||||
#' @param value Single-element numeric vector; the value at which to set the
|
||||
#' progress bar, relative to `min` and `max`. `NULL` hides the progress
|
||||
#' bar, if it is currently visible.
|
||||
set = function(value = NULL, message = NULL, detail = NULL) {
|
||||
if (private$closed) {
|
||||
warning("Attempting to set progress, but progress already closed.")
|
||||
@@ -143,6 +121,11 @@ Progress <- R6Class(
|
||||
private$session$sendProgress('update', data)
|
||||
},
|
||||
|
||||
#' @description Like `set`, this updates the progress panel. The difference
|
||||
#' is that `inc` increases the progress bar by `amount`, instead of
|
||||
#' setting it to a specific value.
|
||||
#' @param amount For the `inc()` method, a numeric value to increment the
|
||||
#' progress bar.
|
||||
inc = function(amount = 0.1, message = NULL, detail = NULL) {
|
||||
if (is.null(private$value))
|
||||
private$value <- private$min
|
||||
@@ -151,12 +134,17 @@ Progress <- R6Class(
|
||||
self$set(value, message, detail)
|
||||
},
|
||||
|
||||
#' @description Returns the minimum value.
|
||||
getMin = function() private$min,
|
||||
|
||||
#' @description Returns the maximum value.
|
||||
getMax = function() private$max,
|
||||
|
||||
#' @description Returns the current value.
|
||||
getValue = function() private$value,
|
||||
|
||||
#' @description Removes the progress panel. Future calls to `set` and
|
||||
#' `close` will be ignored.
|
||||
close = function() {
|
||||
if (private$closed) {
|
||||
warning("Attempting to close progress, but progress already closed.")
|
||||
@@ -186,59 +174,60 @@ Progress <- R6Class(
|
||||
#' Reports progress to the user during long-running operations.
|
||||
#'
|
||||
#' This package exposes two distinct programming APIs for working with progress.
|
||||
#' Using \code{withProgress} with \code{incProgress} or \code{setProgress}
|
||||
#' provide a simple function-based interface, while the \code{\link{Progress}}
|
||||
#' Using `withProgress` with `incProgress` or `setProgress`
|
||||
#' provide a simple function-based interface, while the [Progress()]
|
||||
#' reference class provides an object-oriented API.
|
||||
#'
|
||||
#' Use \code{withProgress} to wrap the scope of your work; doing so will cause a
|
||||
#' Use `withProgress` to wrap the scope of your work; doing so will cause a
|
||||
#' new progress panel to be created, and it will be displayed the first time
|
||||
#' \code{incProgress} or \code{setProgress} are called. When \code{withProgress}
|
||||
#' `incProgress` or `setProgress` are called. When `withProgress`
|
||||
#' exits, the corresponding progress panel will be removed.
|
||||
#'
|
||||
#' The \code{incProgress} function increments the status bar by a specified
|
||||
#' amount, whereas the \code{setProgress} function sets it to a specific value,
|
||||
#' The `incProgress` function increments the status bar by a specified
|
||||
#' amount, whereas the `setProgress` function sets it to a specific value,
|
||||
#' and can also set the text displayed.
|
||||
#'
|
||||
#' Generally, \code{withProgress}/\code{incProgress}/\code{setProgress} should
|
||||
#' Generally, `withProgress`/`incProgress`/`setProgress` should
|
||||
#' be sufficient; the exception is if the work to be done is asynchronous (this
|
||||
#' is not common) or otherwise cannot be encapsulated by a single scope. In that
|
||||
#' case, you can use the \code{Progress} reference class.
|
||||
#' case, you can use the `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
|
||||
#' CSS), you can use `style="old"` each time you call
|
||||
#' `withProgress()`. If you don't want to set the style each time
|
||||
#' `withProgress` is called, you can instead call
|
||||
#' [`shinyOptions(progress.style="old")`][shinyOptions] just once, inside the server
|
||||
#' function.
|
||||
#'
|
||||
#' @param session The Shiny session object, as provided by \code{shinyServer} to
|
||||
#' @param session The Shiny session object, as provided by `shinyServer` to
|
||||
#' the server function. The default is to automatically find the session by
|
||||
#' using the current reactive domain.
|
||||
#' @param expr The work to be done. This expression should contain calls to
|
||||
#' \code{setProgress}.
|
||||
#' [setProgress()] or [incProgress()].
|
||||
#' @param min The value that represents the starting point of the progress bar.
|
||||
#' Must be less tham \code{max}. Default is 0.
|
||||
#' Must be less tham `max`. Default is 0.
|
||||
#' @param max The value that represents the end of the progress bar. Must be
|
||||
#' greater than \code{min}. Default is 1.
|
||||
#' @param amount For \code{incProgress}, the amount to increment the status bar.
|
||||
#' greater than `min`. Default is 1.
|
||||
#' @param amount For `incProgress`, the amount to increment the status bar.
|
||||
#' Default is 0.1.
|
||||
#' @param env The environment in which \code{expr} should be evaluated.
|
||||
#' @param quoted Whether \code{expr} is a quoted expression (this is not
|
||||
#' @param env The environment in which `expr` should be evaluated.
|
||||
#' @param quoted Whether `expr` is a quoted expression (this is not
|
||||
#' common).
|
||||
#' @param message A single-element character vector; the message to be displayed
|
||||
#' to the user, or \code{NULL} to hide the current message (if any).
|
||||
#' to the user, or `NULL` to hide the current message (if any).
|
||||
#' @param detail A single-element character vector; the detail message to be
|
||||
#' displayed to the user, or \code{NULL} to hide the current detail message
|
||||
#' displayed to the user, or `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),
|
||||
#' relative to `message`.
|
||||
#' @param style Progress display style. If `"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
|
||||
#' `"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}.
|
||||
#' progress bar, relative to `min` and `max`.
|
||||
#'
|
||||
#' @return The result of `expr`.
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
@@ -263,7 +252,7 @@ Progress <- R6Class(
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @seealso \code{\link{Progress}}
|
||||
#' @seealso [Progress()]
|
||||
#' @rdname withProgress
|
||||
#' @export
|
||||
withProgress <- function(expr, min = 0, max = 1,
|
||||
|
||||
54
R/react.R
54
R/react.R
@@ -16,25 +16,37 @@ processId <- local({
|
||||
}
|
||||
})
|
||||
|
||||
#' @include graph.R
|
||||
Context <- R6Class(
|
||||
'Context',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
id = character(0),
|
||||
.reactId = character(0),
|
||||
.reactType = "other",
|
||||
.label = character(0), # For debug purposes
|
||||
.invalidated = FALSE,
|
||||
.invalidateCallbacks = list(),
|
||||
.flushCallbacks = list(),
|
||||
.domain = NULL,
|
||||
.pid = NULL,
|
||||
.weak = NULL,
|
||||
|
||||
initialize = function(domain, label='', type='other', prevId='') {
|
||||
id <<- .getReactiveEnvironment()$nextId()
|
||||
initialize = function(
|
||||
domain, label='', type='other', prevId='',
|
||||
reactId = rLog$noReactId,
|
||||
id = .getReactiveEnvironment()$nextId(), # For dummy context
|
||||
weak = FALSE
|
||||
) {
|
||||
id <<- id
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.pid <<- processId()
|
||||
.graphCreateContext(id, label, type, prevId, domain)
|
||||
.reactId <<- reactId
|
||||
.reactType <<- type
|
||||
.weak <<- weak
|
||||
rLog$createContext(id, label, type, prevId, domain)
|
||||
},
|
||||
run = function(func) {
|
||||
"Run the provided function under this context."
|
||||
@@ -42,8 +54,8 @@ Context <- R6Class(
|
||||
promises::with_promise_domain(reactivePromiseDomain(), {
|
||||
withReactiveDomain(.domain, {
|
||||
env <- .getReactiveEnvironment()
|
||||
.graphEnterContext(id)
|
||||
on.exit(.graphExitContext(id), add = TRUE)
|
||||
rLog$enter(.reactId, id, .reactType, .domain)
|
||||
on.exit(rLog$exit(.reactId, id, .reactType, .domain), add = TRUE)
|
||||
env$runWith(self, func)
|
||||
})
|
||||
})
|
||||
@@ -60,7 +72,9 @@ Context <- R6Class(
|
||||
return()
|
||||
.invalidated <<- TRUE
|
||||
|
||||
.graphInvalidate(id, .domain)
|
||||
rLog$invalidateStart(.reactId, id, .reactType, .domain)
|
||||
on.exit(rLog$invalidateEnd(.reactId, id, .reactType, .domain), add = TRUE)
|
||||
|
||||
lapply(.invalidateCallbacks, function(func) {
|
||||
func()
|
||||
})
|
||||
@@ -97,6 +111,9 @@ Context <- R6Class(
|
||||
lapply(.flushCallbacks, function(flushCallback) {
|
||||
flushCallback()
|
||||
})
|
||||
},
|
||||
isWeak = function() {
|
||||
.weak
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -149,7 +166,10 @@ ReactiveEnvironment <- R6Class(
|
||||
# If already in a flush, don't start another one
|
||||
if (.inFlush) return(invisible(FALSE))
|
||||
.inFlush <<- TRUE
|
||||
on.exit(.inFlush <<- FALSE)
|
||||
on.exit({
|
||||
.inFlush <<- FALSE
|
||||
rLog$idle(domain = NULL)
|
||||
})
|
||||
|
||||
while (hasPendingFlush()) {
|
||||
ctx <- .pendingFlush$dequeue()
|
||||
@@ -181,18 +201,16 @@ flushReact <- function() {
|
||||
getCurrentContext <- function() {
|
||||
.getReactiveEnvironment()$currentContext()
|
||||
}
|
||||
hasCurrentContext <- function() {
|
||||
!is.null(.getReactiveEnvironment()$.currentContext)
|
||||
}
|
||||
|
||||
getDummyContext <- function() {}
|
||||
local({
|
||||
dummyContext <- NULL
|
||||
getDummyContext <<- function() {
|
||||
if (is.null(dummyContext)) {
|
||||
dummyContext <<- Context$new(getDefaultReactiveDomain(), '[none]',
|
||||
type='isolate')
|
||||
}
|
||||
return(dummyContext)
|
||||
}
|
||||
})
|
||||
getDummyContext <- function() {
|
||||
Context$new(
|
||||
getDefaultReactiveDomain(), '[none]', type = 'isolate',
|
||||
id = "Dummy", reactId = rLog$dummyReactId
|
||||
)
|
||||
}
|
||||
|
||||
wrapForContext <- function(func, ctx) {
|
||||
force(func)
|
||||
|
||||
@@ -168,31 +168,31 @@ onReactiveDomainEnded <- function(domain, callback, failIfNull = FALSE) {
|
||||
#' them ends) and error handling.
|
||||
#'
|
||||
#' At any given time, there can be either a single "default" reactive domain
|
||||
#' object, or none (i.e. the reactive domain object is \code{NULL}). You can
|
||||
#' object, or none (i.e. the reactive domain object is `NULL`). You can
|
||||
#' access the current default reactive domain by calling
|
||||
#' \code{getDefaultReactiveDomain}.
|
||||
#' `getDefaultReactiveDomain`.
|
||||
#'
|
||||
#' Unless you specify otherwise, newly created observers and reactive
|
||||
#' expressions will be assigned to the current default domain (if any). You can
|
||||
#' override this assignment by providing an explicit \code{domain} argument to
|
||||
#' \code{\link{reactive}} or \code{\link{observe}}.
|
||||
#' override this assignment by providing an explicit `domain` argument to
|
||||
#' [reactive()] or [observe()].
|
||||
#'
|
||||
#' For advanced usage, it's possible to override the default domain using
|
||||
#' \code{withReactiveDomain}. The \code{domain} argument will be made the
|
||||
#' default domain while \code{expr} is evaluated.
|
||||
#' `withReactiveDomain`. The `domain` argument will be made the
|
||||
#' default domain while `expr` is evaluated.
|
||||
#'
|
||||
#' Implementers of new reactive primitives can use \code{onReactiveDomainEnded}
|
||||
#' Implementers of new reactive primitives can use `onReactiveDomainEnded`
|
||||
#' as a convenience function for registering callbacks. If the reactive domain
|
||||
#' is \code{NULL} and \code{failIfNull} is \code{FALSE}, then the callback will
|
||||
#' is `NULL` and `failIfNull` is `FALSE`, then the callback will
|
||||
#' never be invoked.
|
||||
#'
|
||||
#' @name domains
|
||||
#' @param domain A valid domain object (for example, a Shiny session), or
|
||||
#' \code{NULL}
|
||||
#' @param expr An expression to evaluate under \code{domain}
|
||||
#' `NULL`
|
||||
#' @param expr An expression to evaluate under `domain`
|
||||
#' @param callback A callback function to be invoked
|
||||
#' @param failIfNull If \code{TRUE} then an error is given if the \code{domain}
|
||||
#' is \code{NULL}
|
||||
#' @param failIfNull If `TRUE` then an error is given if the `domain`
|
||||
#' is `NULL`
|
||||
NULL
|
||||
|
||||
#
|
||||
|
||||
829
R/reactives.R
829
R/reactives.R
File diff suppressed because it is too large
Load Diff
195
R/reexports.R
Normal file
195
R/reexports.R
Normal file
@@ -0,0 +1,195 @@
|
||||
####
|
||||
# Generated by `./tools/updateReexports.R`: do not edit by hand
|
||||
# Please call `source('tools/updateReexports.R') from the root folder to update`
|
||||
####
|
||||
|
||||
|
||||
# fastmap key_missing.Rd -------------------------------------------------------
|
||||
|
||||
#' @importFrom fastmap key_missing
|
||||
#' @export
|
||||
fastmap::key_missing
|
||||
|
||||
#' @importFrom fastmap is.key_missing
|
||||
#' @export
|
||||
fastmap::is.key_missing
|
||||
|
||||
|
||||
|
||||
# htmltools builder.Rd ---------------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools tags
|
||||
#' @export
|
||||
htmltools::tags
|
||||
|
||||
#' @importFrom htmltools p
|
||||
#' @export
|
||||
htmltools::p
|
||||
|
||||
#' @importFrom htmltools h1
|
||||
#' @export
|
||||
htmltools::h1
|
||||
|
||||
#' @importFrom htmltools h2
|
||||
#' @export
|
||||
htmltools::h2
|
||||
|
||||
#' @importFrom htmltools h3
|
||||
#' @export
|
||||
htmltools::h3
|
||||
|
||||
#' @importFrom htmltools h4
|
||||
#' @export
|
||||
htmltools::h4
|
||||
|
||||
#' @importFrom htmltools h5
|
||||
#' @export
|
||||
htmltools::h5
|
||||
|
||||
#' @importFrom htmltools h6
|
||||
#' @export
|
||||
htmltools::h6
|
||||
|
||||
#' @importFrom htmltools a
|
||||
#' @export
|
||||
htmltools::a
|
||||
|
||||
#' @importFrom htmltools br
|
||||
#' @export
|
||||
htmltools::br
|
||||
|
||||
#' @importFrom htmltools div
|
||||
#' @export
|
||||
htmltools::div
|
||||
|
||||
#' @importFrom htmltools span
|
||||
#' @export
|
||||
htmltools::span
|
||||
|
||||
#' @importFrom htmltools pre
|
||||
#' @export
|
||||
htmltools::pre
|
||||
|
||||
#' @importFrom htmltools code
|
||||
#' @export
|
||||
htmltools::code
|
||||
|
||||
#' @importFrom htmltools img
|
||||
#' @export
|
||||
htmltools::img
|
||||
|
||||
#' @importFrom htmltools strong
|
||||
#' @export
|
||||
htmltools::strong
|
||||
|
||||
#' @importFrom htmltools em
|
||||
#' @export
|
||||
htmltools::em
|
||||
|
||||
#' @importFrom htmltools hr
|
||||
#' @export
|
||||
htmltools::hr
|
||||
|
||||
|
||||
# htmltools tag.Rd -------------------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools tag
|
||||
#' @export
|
||||
htmltools::tag
|
||||
|
||||
#' @importFrom htmltools tagList
|
||||
#' @export
|
||||
htmltools::tagList
|
||||
|
||||
#' @importFrom htmltools tagAppendAttributes
|
||||
#' @export
|
||||
htmltools::tagAppendAttributes
|
||||
|
||||
#' @importFrom htmltools tagHasAttribute
|
||||
#' @export
|
||||
htmltools::tagHasAttribute
|
||||
|
||||
#' @importFrom htmltools tagGetAttribute
|
||||
#' @export
|
||||
htmltools::tagGetAttribute
|
||||
|
||||
#' @importFrom htmltools tagAppendChild
|
||||
#' @export
|
||||
htmltools::tagAppendChild
|
||||
|
||||
#' @importFrom htmltools tagAppendChildren
|
||||
#' @export
|
||||
htmltools::tagAppendChildren
|
||||
|
||||
#' @importFrom htmltools tagSetChildren
|
||||
#' @export
|
||||
htmltools::tagSetChildren
|
||||
|
||||
|
||||
# htmltools HTML.Rd ------------------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools HTML
|
||||
#' @export
|
||||
htmltools::HTML
|
||||
|
||||
|
||||
# htmltools include.Rd ---------------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools includeHTML
|
||||
#' @export
|
||||
htmltools::includeHTML
|
||||
|
||||
#' @importFrom htmltools includeText
|
||||
#' @export
|
||||
htmltools::includeText
|
||||
|
||||
#' @importFrom htmltools includeMarkdown
|
||||
#' @export
|
||||
htmltools::includeMarkdown
|
||||
|
||||
#' @importFrom htmltools includeCSS
|
||||
#' @export
|
||||
htmltools::includeCSS
|
||||
|
||||
#' @importFrom htmltools includeScript
|
||||
#' @export
|
||||
htmltools::includeScript
|
||||
|
||||
|
||||
# htmltools singleton.Rd -------------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools singleton
|
||||
#' @export
|
||||
htmltools::singleton
|
||||
|
||||
#' @importFrom htmltools is.singleton
|
||||
#' @export
|
||||
htmltools::is.singleton
|
||||
|
||||
|
||||
# htmltools validateCssUnit.Rd -------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools validateCssUnit
|
||||
#' @export
|
||||
htmltools::validateCssUnit
|
||||
|
||||
|
||||
# htmltools htmlTemplate.Rd ----------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools htmlTemplate
|
||||
#' @export
|
||||
htmltools::htmlTemplate
|
||||
|
||||
|
||||
# htmltools suppressDependencies.Rd --------------------------------------------
|
||||
|
||||
#' @importFrom htmltools suppressDependencies
|
||||
#' @export
|
||||
htmltools::suppressDependencies
|
||||
|
||||
|
||||
# htmltools withTags.Rd --------------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools withTags
|
||||
#' @export
|
||||
htmltools::withTags
|
||||
@@ -2,29 +2,29 @@
|
||||
#'
|
||||
#' 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
|
||||
#' `expr` is an expression that generates a plot, similar to that in
|
||||
#' `renderPlot`. Unlike with `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}}
|
||||
#' `cacheKeyExpr` is an expression which, when evaluated, returns an object
|
||||
#' which will be serialized and hashed using the [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
|
||||
#' This `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 plotting expression, `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
|
||||
#' 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
|
||||
#' --- 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
|
||||
@@ -32,12 +32,12 @@
|
||||
#' 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.
|
||||
#' [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
|
||||
#' Internally, the result from `cacheKeyExpr` is combined with the name of
|
||||
#' the output (if you assign it to `output$plot1`, it will be combined
|
||||
#' with `"plot1"`) to form the actual key that is used. As a result, even
|
||||
#' if there are multiple plots that have the same `cacheKeyExpr`, they
|
||||
#' will not have cache key collisions.
|
||||
#'
|
||||
#' @section Cache scoping:
|
||||
@@ -49,28 +49,28 @@
|
||||
#' 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
|
||||
#' To control the scope of the cache, use the `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
|
||||
#' among possibly multiple user sessions), use `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
|
||||
#' \item{2}{To scope the cache to one session, use `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
|
||||
#' If either `"app"` or `"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
|
||||
#' [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
|
||||
@@ -79,7 +79,7 @@
|
||||
#' 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
|
||||
#' [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{
|
||||
@@ -87,9 +87,9 @@
|
||||
#' }
|
||||
#'
|
||||
#' 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:
|
||||
#' [shinyOptions()] at the top of your server function. To use
|
||||
#' the session-scoped cache, you must also call `renderCachedPlot` with
|
||||
#' `cache="session"`. This will create a 20 MB cache for the session:
|
||||
#' \preformatted{
|
||||
#' function(input, output, session) {
|
||||
#' shinyOptions(cache = memoryCache(size = 20e6))
|
||||
@@ -102,7 +102,7 @@
|
||||
#' }
|
||||
#'
|
||||
#' 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
|
||||
#' R processes, you can use a [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{
|
||||
@@ -110,7 +110,7 @@
|
||||
#' }
|
||||
#'
|
||||
#' This will create a subdirectory in your system temp directory named
|
||||
#' \code{myapp-cache} (replace \code{myapp-cache} with a unique name of
|
||||
#' `myapp-cache` (replace `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.
|
||||
@@ -126,28 +126,36 @@
|
||||
#' 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}.
|
||||
#' create a [memoryCache()] or [diskCache()], and pass it
|
||||
#' as the `cache` argument of `renderCachedPlot`.
|
||||
#'
|
||||
#' @section Interactive plots:
|
||||
#'
|
||||
#' `renderCachedPlot` can be used to create interactive plots. See
|
||||
#' [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
|
||||
#' @param sizePolicy A function that takes two arguments, `width` and
|
||||
#' `height`, and returns a list with `width` and `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
|
||||
#' possible pixel dimension. See [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
|
||||
#' `"app"` (the default), `"session"`, or a cache object like
|
||||
#' a [diskCache()]. See the Cache Scoping section for more
|
||||
#' information.
|
||||
#' @param width,height not used. They are specified via the argument
|
||||
#' `sizePolicy`.
|
||||
#'
|
||||
#' @seealso See \code{\link{renderPlot}} for the regular, non-cached version of
|
||||
#' @seealso See [renderPlot()] for the regular, non-cached version of
|
||||
#' this function. For more about configuring caches, see
|
||||
#' \code{\link{memoryCache}} and \code{\link{diskCache}}.
|
||||
#' [memoryCache()] and [diskCache()].
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
@@ -289,7 +297,10 @@ renderCachedPlot <- function(expr,
|
||||
res = 72,
|
||||
cache = "app",
|
||||
...,
|
||||
outputArgs = list()
|
||||
alt = "Plot object",
|
||||
outputArgs = list(),
|
||||
width = NULL,
|
||||
height = NULL
|
||||
) {
|
||||
|
||||
# This ..stacktraceon is matched by a ..stacktraceoff.. when plotFunc
|
||||
@@ -301,6 +312,11 @@ renderCachedPlot <- function(expr,
|
||||
|
||||
args <- list(...)
|
||||
|
||||
if (!is.null(width) || !is.null(height)) {
|
||||
warning("Unused argument(s) 'width' and/or 'height'. ",
|
||||
"'sizePolicy' is used instead.")
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -335,6 +351,14 @@ renderCachedPlot <- function(expr,
|
||||
# values get filled by an observer below.
|
||||
fitDims <- reactiveValues(width = NULL, height = NULL)
|
||||
|
||||
# Make sure alt param to be reactive function
|
||||
if (is.reactive(alt))
|
||||
altWrapper <- alt
|
||||
else if (is.function(alt))
|
||||
altWrapper <- reactive({ alt() })
|
||||
else
|
||||
altWrapper <- function() { alt }
|
||||
|
||||
resizeObserver <- NULL
|
||||
ensureResizeObserver <- function() {
|
||||
if (!is.null(resizeObserver))
|
||||
@@ -373,7 +397,7 @@ renderCachedPlot <- function(expr,
|
||||
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
|
||||
# drawReactive will not necessarily re-execute --- it must be called from
|
||||
# renderFunc, which happens only if there's a cache miss.)
|
||||
userCacheKey(),
|
||||
function(userCacheKeyValue) {
|
||||
@@ -381,6 +405,8 @@ renderCachedPlot <- function(expr,
|
||||
isolate({
|
||||
width <- fitDims$width
|
||||
height <- fitDims$height
|
||||
# Make sure alt text to be reactive function
|
||||
alt <- altWrapper()
|
||||
})
|
||||
|
||||
pixelratio <- session$clientData$pixelratio %OR% 1
|
||||
@@ -392,6 +418,7 @@ renderCachedPlot <- function(expr,
|
||||
func = isolatedFunc,
|
||||
width = width,
|
||||
height = height,
|
||||
alt = alt,
|
||||
pixelratio = pixelratio,
|
||||
res = res
|
||||
),
|
||||
@@ -428,9 +455,10 @@ renderCachedPlot <- function(expr,
|
||||
function(userCacheKeyResult) {
|
||||
width <- fitDims$width
|
||||
height <- fitDims$height
|
||||
alt <- altWrapper()
|
||||
pixelratio <- session$clientData$pixelratio %OR% 1
|
||||
|
||||
key <- digest::digest(list(outputName, userCacheKeyResult, width, height, res, pixelratio), "sha256")
|
||||
key <- digest::digest(list(outputName, userCacheKeyResult, width, height, res, pixelratio), "xxhash64")
|
||||
|
||||
plotObj <- cache$get(key)
|
||||
|
||||
@@ -443,6 +471,7 @@ renderCachedPlot <- function(expr,
|
||||
plotObj = plotObj,
|
||||
width = width,
|
||||
height = height,
|
||||
alt = alt,
|
||||
pixelratio = pixelratio
|
||||
))
|
||||
}
|
||||
@@ -465,67 +494,72 @@ renderCachedPlot <- function(expr,
|
||||
plotObj = drawReactiveResult,
|
||||
width = width,
|
||||
height = height,
|
||||
alt = alt,
|
||||
pixelratio = pixelratio
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
function(result) {
|
||||
width <- result$width
|
||||
height <- result$height
|
||||
pixelratio <- result$pixelratio
|
||||
function(possiblyAsyncResult) {
|
||||
hybrid_chain(possiblyAsyncResult, function(result) {
|
||||
width <- result$width
|
||||
height <- result$height
|
||||
alt <- result$alt
|
||||
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
|
||||
))
|
||||
# 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,
|
||||
alt,
|
||||
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)
|
||||
}
|
||||
# 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 <- 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
|
||||
img
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -554,7 +588,7 @@ renderCachedPlot <- function(expr,
|
||||
#' @param width,height Base width and height.
|
||||
#' @param growthRate Growth rate multiplier.
|
||||
#'
|
||||
#' @seealso This is to be used with \code{\link{renderCachedPlot}}.
|
||||
#' @seealso This is to be used with [renderCachedPlot()].
|
||||
#'
|
||||
#' @examples
|
||||
#' f <- sizeGrowthRatio(500, 500, 1.25)
|
||||
|
||||
431
R/render-plot.R
431
R/render-plot.R
@@ -1,53 +1,66 @@
|
||||
#' Plot Output
|
||||
#'
|
||||
#' Renders a reactive plot that is suitable for assigning to an \code{output}
|
||||
#' Renders a reactive plot that is suitable for assigning to an `output`
|
||||
#' slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-plot-output}.
|
||||
#' The corresponding HTML output tag should be `div` or `img` and have
|
||||
#' the CSS class name `shiny-plot-output`.
|
||||
#'
|
||||
#' @section Interactive plots:
|
||||
#'
|
||||
#' With ggplot2 graphics, the code in \code{renderPlot} should return a ggplot
|
||||
#' With ggplot2 graphics, the code in `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
|
||||
#' `print(p)`, then the coordinates for interactive graphics will not be
|
||||
#' properly scaled to the data space.
|
||||
#'
|
||||
#' See \code{\link{plotOutput}} for more information about interactive plots.
|
||||
#' See [plotOutput()] for more information about interactive plots.
|
||||
#'
|
||||
#' @seealso For the corresponding client-side output function, and example
|
||||
#' usage, see \code{\link{plotOutput}}. For more details on how the plots are
|
||||
#' generated, and how to control the output, see \code{\link{plotPNG}}.
|
||||
#' usage, see [plotOutput()]. For more details on how the plots are
|
||||
#' generated, and how to control the output, see [plotPNG()].
|
||||
#' [renderCachedPlot()] offers a way to cache generated plots to
|
||||
#' expedite the rendering of identical plots.
|
||||
#'
|
||||
#' @param expr An expression that generates a plot.
|
||||
#' @param width,height The width/height of the rendered plot, in pixels; or
|
||||
#' \code{'auto'} to use the \code{offsetWidth}/\code{offsetHeight} of the HTML
|
||||
#' element that is bound to this plot. You can also pass in a function that
|
||||
#' returns the width/height in pixels or \code{'auto'}; in the body of the
|
||||
#' function you may reference reactive values and functions. When rendering an
|
||||
#' inline plot, you must provide numeric values (in pixels) to both
|
||||
#' \code{width} and \code{height}.
|
||||
#' @param width,height Height and width can be specified in three ways:
|
||||
#' * `"auto"`, the default, uses the size specified by [plotOutput()]
|
||||
#' (i.e. the `offsetWidth`/`offsetHeight`` of the HTML element bound to
|
||||
#' this plot.)
|
||||
#' * An integer, defining the width/height in pixels.
|
||||
#' * A function that returns the width/height in pixels (or `"auto"`).
|
||||
#' The function is executed in a reactive context so that you can refer to
|
||||
#' reactive values and expression to make the width/height reactive.
|
||||
#'
|
||||
#' When rendering an inline plot, you must provide numeric values (in pixels)
|
||||
#' to both \code{width} and \code{height}.
|
||||
#' @param res Resolution of resulting plot, in pixels per inch. This value is
|
||||
#' passed to \code{\link[grDevices]{png}}. Note that this affects the resolution of PNG
|
||||
#' passed to [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}}.
|
||||
#' @param alt Alternate text for the HTML `<img>` tag
|
||||
#' if it cannot be displayed or viewed (i.e., the user uses a screen reader).
|
||||
#' In addition to a character string, the value may be a reactive expression
|
||||
#' (or a function referencing reactive values) that returns a character string.
|
||||
#' NULL or "" is not recommended because those should be limited to decorative images
|
||||
#' (the default is "Plot object").
|
||||
#' @param ... Arguments to be passed through to [grDevices::png()].
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' @param env The environment in which to evaluate `expr`.
|
||||
#' @param quoted Is `expr` a quoted expression (with `quote()`)? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param execOnResize If \code{FALSE} (the default), then when a plot is
|
||||
#' resized, Shiny will \emph{replay} the plot drawing commands with
|
||||
#' \code{\link[grDevices]{replayPlot}()} instead of re-executing \code{expr}.
|
||||
#' @param execOnResize If `FALSE` (the default), then when a plot is
|
||||
#' resized, Shiny will *replay* the plot drawing commands with
|
||||
#' [grDevices::replayPlot()] instead of re-executing `expr`.
|
||||
#' This can result in faster plot redrawing, but there may be rare cases where
|
||||
#' it is undesirable. If you encounter problems when resizing a plot, you can
|
||||
#' have Shiny re-execute the code on resize by setting this to \code{TRUE}.
|
||||
#' have Shiny re-execute the code on resize by setting this to `TRUE`.
|
||||
#' @param outputArgs A list of arguments to be passed through to the implicit
|
||||
#' call to \code{\link{plotOutput}} when \code{renderPlot} is used in an
|
||||
#' call to [plotOutput()] when `renderPlot` is used in an
|
||||
#' interactive R Markdown document.
|
||||
#' @export
|
||||
renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
env=parent.frame(), quoted=FALSE,
|
||||
execOnResize=FALSE, outputArgs=list()
|
||||
renderPlot <- function(expr, width = 'auto', height = 'auto', res = 72, ...,
|
||||
alt = "Plot object",
|
||||
env = parent.frame(), quoted = FALSE,
|
||||
execOnResize = FALSE, outputArgs = list()
|
||||
) {
|
||||
# This ..stacktraceon is matched by a ..stacktraceoff.. when plotFunc
|
||||
# is called
|
||||
@@ -69,6 +82,13 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
else
|
||||
heightWrapper <- function() { height }
|
||||
|
||||
if (is.reactive(alt))
|
||||
altWrapper <- alt
|
||||
else if (is.function(alt))
|
||||
altWrapper <- reactive({ alt() })
|
||||
else
|
||||
altWrapper <- function() { alt }
|
||||
|
||||
getDims <- function() {
|
||||
width <- widthWrapper()
|
||||
height <- heightWrapper()
|
||||
@@ -106,6 +126,7 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
func = func,
|
||||
width = dims$width,
|
||||
height = dims$height,
|
||||
alt = altWrapper(),
|
||||
pixelratio = pixelratio,
|
||||
res = res
|
||||
), args))
|
||||
@@ -134,7 +155,7 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
dims <- getDims()
|
||||
pixelratio <- session$clientData$pixelratio %OR% 1
|
||||
result <- do.call("resizeSavedPlot", c(
|
||||
list(name, shinysession, result, dims$width, dims$height, pixelratio, res),
|
||||
list(name, shinysession, result, dims$width, dims$height, altWrapper(), pixelratio, res),
|
||||
args
|
||||
))
|
||||
|
||||
@@ -153,16 +174,21 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
markRenderFunction(outputFunc, renderFunc, outputArgs = outputArgs)
|
||||
}
|
||||
|
||||
resizeSavedPlot <- function(name, session, result, width, height, pixelratio, res, ...) {
|
||||
resizeSavedPlot <- function(name, session, result, width, height, alt, pixelratio, res, ...) {
|
||||
if (result$img$width == width && result$img$height == height &&
|
||||
result$pixelratio == pixelratio && result$res == res) {
|
||||
return(result)
|
||||
}
|
||||
|
||||
if (isNamespaceLoaded("showtext")) {
|
||||
showtextOpts <- showtext::showtext_opts(dpi = res*pixelratio)
|
||||
on.exit({showtext::showtext_opts(showtextOpts)}, add = TRUE)
|
||||
}
|
||||
|
||||
coordmap <- NULL
|
||||
outfile <- plotPNG(function() {
|
||||
grDevices::replayPlot(result$recordedPlot)
|
||||
coordmap <<- getCoordmap(result$plotResult, width, height, pixelratio, res)
|
||||
coordmap <<- getCoordmap(result$plotResult, width*pixelratio, height*pixelratio, res*pixelratio)
|
||||
}, width = width*pixelratio, height = height*pixelratio, res = res*pixelratio, ...)
|
||||
on.exit(unlink(outfile), add = TRUE)
|
||||
|
||||
@@ -170,6 +196,7 @@ resizeSavedPlot <- function(name, session, result, width, height, pixelratio, re
|
||||
src = session$fileUrl(name, outfile, contentType = "image/png"),
|
||||
width = width,
|
||||
height = height,
|
||||
alt = alt,
|
||||
coordmap = coordmap,
|
||||
error = attr(coordmap, "error", exact = TRUE)
|
||||
)
|
||||
@@ -177,7 +204,7 @@ resizeSavedPlot <- function(name, session, result, width, height, pixelratio, re
|
||||
result
|
||||
}
|
||||
|
||||
drawPlot <- function(name, session, func, width, height, pixelratio, res, ...) {
|
||||
drawPlot <- function(name, session, func, width, height, alt, pixelratio, res, ...) {
|
||||
# 1. Start PNG
|
||||
# 2. Enable displaylist recording
|
||||
# 3. Call user-defined func
|
||||
@@ -194,6 +221,17 @@ drawPlot <- function(name, session, func, width, height, pixelratio, res, ...) {
|
||||
domain <- createGraphicsDevicePromiseDomain(device)
|
||||
grDevices::dev.control(displaylist = "enable")
|
||||
|
||||
# In some cases (at least when `png(type='cairo')), showtext's font
|
||||
# rendering needs to know about the device's resolution to work properly.
|
||||
# I don't see any immediate harm in setting the dpi option for any device,
|
||||
# but it's worth noting that the option doesn't currently work with CairoPNG.
|
||||
# https://github.com/yixuan/showtext/issues/33
|
||||
showtextOpts <- if (isNamespaceLoaded("showtext")) {
|
||||
showtext::showtext_opts(dpi = res*pixelratio)
|
||||
} else {
|
||||
NULL
|
||||
}
|
||||
|
||||
hybrid_chain(
|
||||
hybrid_chain(
|
||||
promises::with_promise_domain(domain, {
|
||||
@@ -231,7 +269,7 @@ drawPlot <- function(name, session, func, width, height, pixelratio, res, ...) {
|
||||
list(
|
||||
plotResult = value,
|
||||
recordedPlot = grDevices::recordPlot(),
|
||||
coordmap = getCoordmap(value, width, height, pixelratio, res),
|
||||
coordmap = getCoordmap(value, width*pixelratio, height*pixelratio, res*pixelratio),
|
||||
pixelratio = pixelratio,
|
||||
res = res
|
||||
)
|
||||
@@ -240,6 +278,9 @@ drawPlot <- function(name, session, func, width, height, pixelratio, res, ...) {
|
||||
}),
|
||||
finally = function() {
|
||||
grDevices::dev.off(device)
|
||||
if (length(showtextOpts)) {
|
||||
showtext::showtext_opts(showtextOpts)
|
||||
}
|
||||
}
|
||||
),
|
||||
function(result) {
|
||||
@@ -247,6 +288,7 @@ drawPlot <- function(name, session, func, width, height, pixelratio, res, ...) {
|
||||
src = session$fileUrl(name, outfile, contentType='image/png'),
|
||||
width = width,
|
||||
height = height,
|
||||
alt = alt,
|
||||
coordmap = result$coordmap,
|
||||
# Get coordmap error message if present
|
||||
error = attr(result$coordmap, "error", exact = TRUE)
|
||||
@@ -284,22 +326,26 @@ custom_print.ggplot <- function(x) {
|
||||
# below. For base graphics:
|
||||
# plot(mtcars$wt, mtcars$mpg)
|
||||
# str(getPrevPlotCoordmap(400, 300))
|
||||
# List of 1
|
||||
# $ :List of 4
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.36
|
||||
# .. ..$ right : num 5.58
|
||||
# .. ..$ bottom: num 9.46
|
||||
# .. ..$ top : num 34.8
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 50.4
|
||||
# .. ..$ right : num 373
|
||||
# .. ..$ bottom: num 199
|
||||
# .. ..$ top : num 79.6
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ mapping: Named list()
|
||||
# List of 2
|
||||
# $ panels:List of 1
|
||||
# ..$ :List of 4
|
||||
# .. ..$ domain :List of 4
|
||||
# .. .. ..$ left : num 1.36
|
||||
# .. .. ..$ right : num 5.58
|
||||
# .. .. ..$ bottom: num 9.46
|
||||
# .. .. ..$ top : num 34.8
|
||||
# .. ..$ range :List of 4
|
||||
# .. .. ..$ left : num 65.6
|
||||
# .. .. ..$ right : num 366
|
||||
# .. .. ..$ bottom: num 238
|
||||
# .. .. ..$ top : num 48.2
|
||||
# .. ..$ log :List of 2
|
||||
# .. .. ..$ x: NULL
|
||||
# .. .. ..$ y: NULL
|
||||
# .. ..$ mapping: Named list()
|
||||
# $ dims :List of 2
|
||||
# ..$ width : num 400
|
||||
# ..$ height: num 300
|
||||
#
|
||||
# For ggplot2, first you need to define the print.ggplot function from inside
|
||||
# renderPlot, then use it to print the plot:
|
||||
@@ -318,89 +364,123 @@ custom_print.ggplot <- function(x) {
|
||||
# }
|
||||
#
|
||||
# p <- print(ggplot(mtcars, aes(wt, mpg)) + geom_point())
|
||||
# str(getGgplotCoordmap(p, 1, 72))
|
||||
# List of 1
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 1
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 1
|
||||
# ..$ panel_vars: Named list()
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 2
|
||||
# .. ..$ x: chr "wt"
|
||||
# .. ..$ y: chr "mpg"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 40.8
|
||||
# .. ..$ right : num 446
|
||||
# .. ..$ bottom: num 263
|
||||
# .. ..$ top : num 14.4
|
||||
# str(getGgplotCoordmap(p, 400, 300, 72))
|
||||
# List of 2
|
||||
# $ panels:List of 1
|
||||
# ..$ :List of 8
|
||||
# .. ..$ panel : num 1
|
||||
# .. ..$ row : num 1
|
||||
# .. ..$ col : num 1
|
||||
# .. ..$ panel_vars: Named list()
|
||||
# .. ..$ log :List of 2
|
||||
# .. .. ..$ x: NULL
|
||||
# .. .. ..$ y: NULL
|
||||
# .. ..$ domain :List of 4
|
||||
# .. .. ..$ left : num 1.32
|
||||
# .. .. ..$ right : num 5.62
|
||||
# .. .. ..$ bottom: num 9.22
|
||||
# .. .. ..$ top : num 35.1
|
||||
# .. ..$ mapping :List of 2
|
||||
# .. .. ..$ x: chr "wt"
|
||||
# .. .. ..$ y: chr "mpg"
|
||||
# .. ..$ range :List of 4
|
||||
# .. .. ..$ left : num 33.3
|
||||
# .. .. ..$ right : num 355
|
||||
# .. .. ..$ bottom: num 328
|
||||
# .. .. ..$ top : num 5.48
|
||||
# $ dims :List of 2
|
||||
# ..$ width : num 400
|
||||
# ..$ height: num 300
|
||||
#
|
||||
# With a faceted ggplot2 plot, the outer list contains two objects, each of
|
||||
# which represents one panel. In this example, there is one panelvar, but there
|
||||
# can be up to two of them.
|
||||
# mtc <- mtcars
|
||||
# mtc$am <- factor(mtc$am)
|
||||
# p <- print(ggplot(mtc, aes(wt, mpg)) + geom_point() + facet_wrap(~ am))
|
||||
# str(getGgplotCoordmap(p, 1, 72))
|
||||
# p <- print(ggplot(mpg) + geom_point(aes(fl, cty), alpha = 0.2) + facet_wrap(~drv, scales = "free_x"))
|
||||
# str(getGgplotCoordmap(p, 500, 400, 72))
|
||||
# List of 2
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 1
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 1
|
||||
# ..$ panel_vars:List of 1
|
||||
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 3
|
||||
# .. ..$ x : chr "wt"
|
||||
# .. ..$ y : chr "mpg"
|
||||
# .. ..$ panelvar1: chr "am"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 45.6
|
||||
# .. ..$ right : num 317
|
||||
# .. ..$ bottom: num 251
|
||||
# .. ..$ top : num 35.7
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 2
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 2
|
||||
# ..$ panel_vars:List of 1
|
||||
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 2
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 3
|
||||
# .. ..$ x : chr "wt"
|
||||
# .. ..$ y : chr "mpg"
|
||||
# .. ..$ panelvar1: chr "am"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 322
|
||||
# .. ..$ right : num 594
|
||||
# .. ..$ bottom: num 251
|
||||
# .. ..$ top : num 35.7
|
||||
# $ panels:List of 3
|
||||
# ..$ :List of 8
|
||||
# .. ..$ panel : num 1
|
||||
# .. ..$ row : int 1
|
||||
# .. ..$ col : int 1
|
||||
# .. ..$ panel_vars:List of 1
|
||||
# .. .. ..$ panelvar1: chr "4"
|
||||
# .. ..$ log :List of 2
|
||||
# .. .. ..$ x: NULL
|
||||
# .. .. ..$ y: NULL
|
||||
# .. ..$ domain :List of 5
|
||||
# .. .. ..$ left : num 0.4
|
||||
# .. .. ..$ right : num 4.6
|
||||
# .. .. ..$ bottom : num 7.7
|
||||
# .. .. ..$ top : num 36.3
|
||||
# .. .. ..$ discrete_limits:List of 1
|
||||
# .. .. .. ..$ x: chr [1:4] "d" "e" "p" "r"
|
||||
# .. ..$ mapping :List of 3
|
||||
# .. .. ..$ x : chr "fl"
|
||||
# .. .. ..$ y : chr "cty"
|
||||
# .. .. ..$ panelvar1: chr "drv"
|
||||
# .. ..$ range :List of 4
|
||||
# .. .. ..$ left : num 33.3
|
||||
# .. .. ..$ right : num 177
|
||||
# .. .. ..$ bottom: num 448
|
||||
# .. .. ..$ top : num 23.1
|
||||
# ..$ :List of 8
|
||||
# .. ..$ panel : num 2
|
||||
# .. ..$ row : int 1
|
||||
# .. ..$ col : int 2
|
||||
# .. ..$ panel_vars:List of 1
|
||||
# .. .. ..$ panelvar1: chr "f"
|
||||
# .. ..$ log :List of 2
|
||||
# .. .. ..$ x: NULL
|
||||
# .. .. ..$ y: NULL
|
||||
# .. ..$ domain :List of 5
|
||||
# .. .. ..$ left : num 0.4
|
||||
# .. .. ..$ right : num 5.6
|
||||
# .. .. ..$ bottom : num 7.7
|
||||
# .. .. ..$ top : num 36.3
|
||||
# .. .. ..$ discrete_limits:List of 1
|
||||
# .. .. .. ..$ x: chr [1:5] "c" "d" "e" "p" ...
|
||||
# .. ..$ mapping :List of 3
|
||||
# .. .. ..$ x : chr "fl"
|
||||
# .. .. ..$ y : chr "cty"
|
||||
# .. .. ..$ panelvar1: chr "drv"
|
||||
# .. ..$ range :List of 4
|
||||
# .. .. ..$ left : num 182
|
||||
# .. .. ..$ right : num 326
|
||||
# .. .. ..$ bottom: num 448
|
||||
# .. .. ..$ top : num 23.1
|
||||
# ..$ :List of 8
|
||||
# .. ..$ panel : num 3
|
||||
# .. ..$ row : int 1
|
||||
# .. ..$ col : int 3
|
||||
# .. ..$ panel_vars:List of 1
|
||||
# .. .. ..$ panelvar1: chr "r"
|
||||
# .. ..$ log :List of 2
|
||||
# .. .. ..$ x: NULL
|
||||
# .. .. ..$ y: NULL
|
||||
# .. ..$ domain :List of 5
|
||||
# .. .. ..$ left : num 0.4
|
||||
# .. .. ..$ right : num 3.6
|
||||
# .. .. ..$ bottom : num 7.7
|
||||
# .. .. ..$ top : num 36.3
|
||||
# .. .. ..$ discrete_limits:List of 1
|
||||
# .. .. .. ..$ x: chr [1:3] "e" "p" "r"
|
||||
# .. ..$ mapping :List of 3
|
||||
# .. .. ..$ x : chr "fl"
|
||||
# .. .. ..$ y : chr "cty"
|
||||
# .. .. ..$ panelvar1: chr "drv"
|
||||
# .. ..$ range :List of 4
|
||||
# .. .. ..$ left : num 331
|
||||
# .. .. ..$ right : num 475
|
||||
# .. .. ..$ bottom: num 448
|
||||
# .. .. ..$ top : num 23.1
|
||||
# $ dims :List of 2
|
||||
# ..$ width : num 500
|
||||
# ..$ height: num 400
|
||||
|
||||
|
||||
getCoordmap <- function(x, width, height, pixelratio, res) {
|
||||
getCoordmap <- function(x, width, height, res) {
|
||||
if (inherits(x, "ggplot_build_gtable")) {
|
||||
getGgplotCoordmap(x, pixelratio, res)
|
||||
getGgplotCoordmap(x, width, height, res)
|
||||
} else {
|
||||
getPrevPlotCoordmap(width, height)
|
||||
}
|
||||
@@ -420,7 +500,7 @@ getPrevPlotCoordmap <- function(width, height) {
|
||||
}
|
||||
|
||||
# Wrapped in double list because other types of plots can have multiple panels.
|
||||
list(list(
|
||||
panel_info <- list(list(
|
||||
# Bounds of the plot area, in data space
|
||||
domain = list(
|
||||
left = usrCoords[1],
|
||||
@@ -444,27 +524,43 @@ getPrevPlotCoordmap <- function(width, height) {
|
||||
# (not an array) in JSON.
|
||||
mapping = list(x = NULL)[0]
|
||||
))
|
||||
|
||||
list(
|
||||
panels = panel_info,
|
||||
dims = list(
|
||||
width = width,
|
||||
height =height
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
# Given a ggplot_build_gtable object, return a coordmap for it.
|
||||
getGgplotCoordmap <- function(p, pixelratio, res) {
|
||||
getGgplotCoordmap <- function(p, width, height, res) {
|
||||
if (!inherits(p, "ggplot_build_gtable"))
|
||||
return(NULL)
|
||||
|
||||
tryCatch({
|
||||
# Get info from built ggplot object
|
||||
info <- find_panel_info(p$build)
|
||||
panel_info <- find_panel_info(p$build)
|
||||
|
||||
# Get ranges from gtable - it's possible for this to return more elements than
|
||||
# info, because it calculates positions even for panels that aren't present.
|
||||
# This can happen with facet_wrap.
|
||||
ranges <- find_panel_ranges(p$gtable, pixelratio, res)
|
||||
ranges <- find_panel_ranges(p$gtable, res)
|
||||
|
||||
for (i in seq_along(info)) {
|
||||
info[[i]]$range <- ranges[[i]]
|
||||
for (i in seq_along(panel_info)) {
|
||||
panel_info[[i]]$range <- ranges[[i]]
|
||||
}
|
||||
|
||||
return(info)
|
||||
return(
|
||||
list(
|
||||
panels = panel_info,
|
||||
dims = list(
|
||||
width = width,
|
||||
height = height
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
}, error = function(e) {
|
||||
# If there was an error extracting info from the ggplot object, just return
|
||||
@@ -491,13 +587,11 @@ find_panel_info <- function(b) {
|
||||
# This is for ggplot2>2.2.1, after an API was introduced for extracting
|
||||
# information about the plot object.
|
||||
find_panel_info_api <- function(b) {
|
||||
# Workaround for check NOTE, until ggplot2 >2.2.1 is released
|
||||
colon_colon <- `::`
|
||||
# Given a built ggplot object, return x and y domains (data space coords) for
|
||||
# each panel.
|
||||
layout <- colon_colon("ggplot2", "summarise_layout")(b)
|
||||
coord <- colon_colon("ggplot2", "summarise_coord")(b)
|
||||
layers <- colon_colon("ggplot2", "summarise_layers")(b)
|
||||
layout <- ggplot2::summarise_layout(b)
|
||||
coord <- ggplot2::summarise_coord(b)
|
||||
layers <- ggplot2::summarise_layers(b)
|
||||
|
||||
# Given x and y scale objects and a coord object, return a list that has
|
||||
# the bases of log transformations for x and y, or NULL if it's not a
|
||||
@@ -544,6 +638,9 @@ find_panel_info_api <- function(b) {
|
||||
domain$bottom <- -domain$bottom
|
||||
}
|
||||
|
||||
domain <- add_discrete_limits(domain, xscale, "x")
|
||||
domain <- add_discrete_limits(domain, yscale, "y")
|
||||
|
||||
domain
|
||||
}
|
||||
|
||||
@@ -663,6 +760,9 @@ find_panel_info_non_api <- function(b, ggplot_format) {
|
||||
domain$bottom <- -domain$bottom
|
||||
}
|
||||
|
||||
domain <- add_discrete_limits(domain, xscale, "x")
|
||||
domain <- add_discrete_limits(domain, yscale, "y")
|
||||
|
||||
domain
|
||||
}
|
||||
|
||||
@@ -825,9 +925,17 @@ find_panel_info_non_api <- function(b, ggplot_format) {
|
||||
})
|
||||
}
|
||||
|
||||
# Use public API for getting the unit's type (grid::unitType(), added in R 4.0)
|
||||
# https://github.com/wch/r-source/blob/f9b8a42/src/library/grid/R/unit.R#L179
|
||||
getUnitType <- function(u) {
|
||||
tryCatch(
|
||||
get("unitType", envir = asNamespace("grid"))(u),
|
||||
error = function(e) attr(u, "unit", exact = TRUE)
|
||||
)
|
||||
}
|
||||
|
||||
# Given a gtable object, return the x and y ranges (in pixel dimensions)
|
||||
find_panel_ranges <- function(g, pixelratio, res) {
|
||||
find_panel_ranges <- function(g, res) {
|
||||
# Given a vector of unit objects, return logical vector indicating which ones
|
||||
# are "null" units. These units use the remaining available width/height --
|
||||
# that is, the space not occupied by elements that have an absolute size.
|
||||
@@ -840,11 +948,11 @@ find_panel_ranges <- function(g, pixelratio, res) {
|
||||
if (inherits(x, "unit.list")) {
|
||||
# For ggplot2 <= 1.0.1
|
||||
vapply(x, FUN.VALUE = logical(1), function(u) {
|
||||
isTRUE(attr(u, "unit", exact = TRUE) == "null")
|
||||
isTRUE(getUnitType(u) == "null")
|
||||
})
|
||||
} else {
|
||||
# For later versions of ggplot2
|
||||
attr(x, "unit", exact = TRUE) == "null"
|
||||
getUnitType(x) == "null"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -884,7 +992,11 @@ find_panel_ranges <- function(g, pixelratio, res) {
|
||||
|
||||
# The plotting panels all are 'null' units.
|
||||
null_sizes <- rep(NA_real_, length(rel_sizes))
|
||||
null_sizes[null_idx] <- as.numeric(rel_sizes[null_idx])
|
||||
# Workaround for `[.unit` forbidding zero-length subsets
|
||||
# https://github.com/wch/r-source/blob/f9b8a42/src/library/grid/R/unit.R#L448-L450
|
||||
if (length(null_idx)) {
|
||||
null_sizes[null_idx] <- as.numeric(rel_sizes[null_idx])
|
||||
}
|
||||
|
||||
# Total size allocated for panels is the total image size minus absolute
|
||||
# (non-panel) elements.
|
||||
@@ -957,26 +1069,35 @@ find_panel_ranges <- function(g, pixelratio, res) {
|
||||
layout <- layout[order(layout$t, layout$l), ]
|
||||
layout$panel <- seq_len(nrow(layout))
|
||||
|
||||
# When using a HiDPI client on a Linux server, the pixel
|
||||
# dimensions are doubled, so we have to divide the dimensions by
|
||||
# `pixelratio`. When a HiDPI client is used on a Mac server (with
|
||||
# the quartz device), the pixel dimensions _aren't_ doubled, even though
|
||||
# the image has double size. In the latter case we don't have to scale the
|
||||
# numbers down.
|
||||
pix_ratio <- 1
|
||||
if (!grepl("^quartz", names(grDevices::dev.cur()))) {
|
||||
pix_ratio <- pixelratio
|
||||
}
|
||||
|
||||
# Return list of lists, where each inner list has left, right, top, bottom
|
||||
# values for a panel
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
p <- layout[i, , drop = FALSE]
|
||||
list(
|
||||
left = x_pos[p$l - 1] / pix_ratio,
|
||||
right = x_pos[p$r] / pix_ratio,
|
||||
bottom = y_pos[p$b] / pix_ratio,
|
||||
top = y_pos[p$t - 1] / pix_ratio
|
||||
left = x_pos[p$l - 1],
|
||||
right = x_pos[p$r],
|
||||
bottom = y_pos[p$b],
|
||||
top = y_pos[p$t - 1]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
# Remember the x/y limits of discrete axes. This info is
|
||||
# necessary to properly inverse map the numeric (i.e., trained)
|
||||
# positions back to the data scale, for example:
|
||||
# https://github.com/rstudio/shiny/pull/2410#issuecomment-487783828
|
||||
# https://github.com/rstudio/shiny/pull/2410#issuecomment-488100881
|
||||
#
|
||||
# Eventually, we may want to consider storing the entire ggplot2
|
||||
# object server-side and querying information from that object
|
||||
# as we need it...that's the only way we'll ever be able to
|
||||
# faithfully brush examples like this:
|
||||
# https://github.com/rstudio/shiny/issues/2411
|
||||
add_discrete_limits <- function(domain, scale, var = "x") {
|
||||
var <- match.arg(var, c("x", "y"))
|
||||
if (!is.function(scale$is_discrete) || !is.function(scale$get_limits)) return(domain)
|
||||
if (scale$is_discrete()) {
|
||||
domain$discrete_limits[[var]] <- scale$get_limits()
|
||||
}
|
||||
domain
|
||||
}
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
#' Table Output
|
||||
#'
|
||||
#' Creates a reactive table that is suitable for assigning to an \code{output}
|
||||
#' Creates a reactive table that is suitable for assigning to an `output`
|
||||
#' slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} and have the CSS
|
||||
#' class name \code{shiny-html-output}.
|
||||
#' The corresponding HTML output tag should be `div` and have the CSS
|
||||
#' class name `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
|
||||
#' [xtable::xtable()].
|
||||
#' @param striped,hover,bordered Logicals: if `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 spacing The spacing between the rows of the table (`xs`
|
||||
#' stands for "extra small", `s` for "small", `m` for "medium"
|
||||
#' and `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}
|
||||
#' `'l'`, `'c'` or `'r'`, then all columns will be,
|
||||
#' respectively, left-, center- or right-aligned. Otherwise, `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
|
||||
#' `rownames = TRUE`, this will be equal to `ncol()+1`), with
|
||||
#' the *i*-th character specifying the alignment for the
|
||||
#' *i*-th column (besides `'l'`, `'c'` and
|
||||
#' `'r'`, `'?'` is also permitted - `'?'` is a placeholder
|
||||
#' for that particular column, indicating that it should keep its default
|
||||
#' alignment). If \code{NULL}, then all numeric/integer columns (including
|
||||
#' alignment). If `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
|
||||
#' everything else will be left-aligned (`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
|
||||
#' class). If `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.
|
||||
#' `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()})?
|
||||
#' (i.e. they either evaluate to `NA` or `NaN`).
|
||||
#' @param ... Arguments to be passed through to [xtable::xtable()]
|
||||
#' and [xtable::print.xtable()].
|
||||
#' @param env The environment in which to evaluate `expr`.
|
||||
#' @param quoted Is `expr` a quoted expression (with `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
|
||||
#' implicit call to [tableOutput()] when `renderTable` is
|
||||
#' used in an interactive R Markdown document.
|
||||
#' @export
|
||||
renderTable <- function(expr, striped = FALSE, hover = FALSE,
|
||||
|
||||
30
R/run-url.R
30
R/run-url.R
@@ -1,24 +1,24 @@
|
||||
#' Run a Shiny application from a URL
|
||||
#'
|
||||
#' \code{runUrl()} downloads and launches a Shiny application that is hosted at
|
||||
#' `runUrl()` downloads and launches a Shiny application that is hosted at
|
||||
#' a downloadable URL. The Shiny application must be saved in a .zip, .tar, or
|
||||
#' .tar.gz file. The Shiny application files must be contained in the root
|
||||
#' directory or a subdirectory in the archive. For example, the files might be
|
||||
#' \code{myapp/server.r} and \code{myapp/ui.r}. The functions \code{runGitHub()}
|
||||
#' and \code{runGist()} are based on \code{runUrl()}, using URL's from GitHub
|
||||
#' (\url{https://github.com}) and GitHub gists (\url{https://gist.github.com}),
|
||||
#' `myapp/server.r` and `myapp/ui.r`. The functions `runGitHub()`
|
||||
#' and `runGist()` are based on `runUrl()`, using URL's from GitHub
|
||||
#' (<https://github.com>) and GitHub gists (<https://gist.github.com>),
|
||||
#' respectively.
|
||||
#' @param url URL of the application.
|
||||
#' @param filetype The file type (\code{".zip"}, \code{".tar"}, or
|
||||
#' \code{".tar.gz"}. Defaults to the file extension taken from the url.
|
||||
#' @param filetype The file type (`".zip"`, `".tar"`, or
|
||||
#' `".tar.gz"`. Defaults to the file extension taken from the url.
|
||||
#' @param subdir A subdirectory in the repository that contains the app. By
|
||||
#' default, this function will run an app from the top level of the repo, but
|
||||
#' you can use a path such as `\code{"inst/shinyapp"}.
|
||||
#' @param destdir Directory to store the downloaded application files. If \code{NULL}
|
||||
#' you can use a path such as `"inst/shinyapp"`.
|
||||
#' @param destdir Directory to store the downloaded application files. If `NULL`
|
||||
#' (the default), the application files will be stored in a temporary directory
|
||||
#' and removed when the app exits
|
||||
#' @param ... Other arguments to be passed to \code{\link{runApp}()}, such as
|
||||
#' \code{port} and \code{launch.browser}.
|
||||
#' @param ... Other arguments to be passed to [runApp()], such as
|
||||
#' `port` and `launch.browser`.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
@@ -88,8 +88,8 @@ runUrl <- function(url, filetype = NULL, subdir = NULL, destdir = NULL, ...) {
|
||||
|
||||
#' @rdname runUrl
|
||||
#' @param gist The identifier of the gist. For example, if the gist is
|
||||
#' https://gist.github.com/jcheng5/3239667, then \code{3239667},
|
||||
#' \code{'3239667'}, and \code{'https://gist.github.com/jcheng5/3239667'} are
|
||||
#' https://gist.github.com/jcheng5/3239667, then `3239667`,
|
||||
#' `'3239667'`, and `'https://gist.github.com/jcheng5/3239667'` are
|
||||
#' all valid values.
|
||||
#' @export
|
||||
#' @examples
|
||||
@@ -118,10 +118,10 @@ runGist <- function(gist, destdir = NULL, ...) {
|
||||
|
||||
#' @rdname runUrl
|
||||
#' @param repo Name of the repository.
|
||||
#' @param username GitHub username. If \code{repo} is of the form
|
||||
#' \code{"username/repo"}, \code{username} will be taken from \code{repo}.
|
||||
#' @param username GitHub username. If `repo` is of the form
|
||||
#' `"username/repo"`, `username` will be taken from `repo`.
|
||||
#' @param ref Desired git reference. Could be a commit, tag, or branch name.
|
||||
#' Defaults to \code{"master"}.
|
||||
#' Defaults to `"master"`.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
|
||||
564
R/runapp.R
Normal file
564
R/runapp.R
Normal file
@@ -0,0 +1,564 @@
|
||||
#' Run Shiny Application
|
||||
#'
|
||||
#' Runs a Shiny application. This function normally does not return; interrupt R
|
||||
#' to stop the application (usually by pressing Ctrl+C or Esc).
|
||||
#'
|
||||
#' The host parameter was introduced in Shiny 0.9.0. Its default value of
|
||||
#' `"127.0.0.1"` means that, contrary to previous versions of Shiny, only
|
||||
#' the current machine can access locally hosted Shiny apps. To allow other
|
||||
#' clients to connect, use the value `"0.0.0.0"` instead (which was the
|
||||
#' value that was hard-coded into Shiny in 0.8.0 and earlier).
|
||||
#'
|
||||
#' @param appDir The application to run. Should be one of the following:
|
||||
#' \itemize{
|
||||
#' \item A directory containing `server.R`, plus, either `ui.R` or
|
||||
#' a `www` directory that contains the file `index.html`.
|
||||
#' \item A directory containing `app.R`.
|
||||
#' \item An `.R` file containing a Shiny application, ending with an
|
||||
#' expression that produces a Shiny app object.
|
||||
#' \item A list with `ui` and `server` components.
|
||||
#' \item A Shiny app object created by [shinyApp()].
|
||||
#' }
|
||||
#' @param port The TCP port that the application should listen on. If the
|
||||
#' `port` is not specified, and the `shiny.port` option is set (with
|
||||
#' `options(shiny.port = XX)`), then that port will be used. Otherwise,
|
||||
#' use a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only. This value of this parameter can also be a
|
||||
#' function to call with the application's URL.
|
||||
#' @param host The IPv4 address that the application should listen on. Defaults
|
||||
#' to the `shiny.host` option, if set, or `"127.0.0.1"` if not. See
|
||||
#' Details.
|
||||
#' @param workerId Can generally be ignored. Exists to help some editions of
|
||||
#' Shiny Server Pro route requests to the correct process.
|
||||
#' @param quiet Should Shiny status messages be shown? Defaults to FALSE.
|
||||
#' @param display.mode The mode in which to display the application. If set to
|
||||
#' the value `"showcase"`, shows application code and metadata from a
|
||||
#' `DESCRIPTION` file in the application directory alongside the
|
||||
#' application. If set to `"normal"`, displays the application normally.
|
||||
#' Defaults to `"auto"`, which displays the application in the mode given
|
||||
#' in its `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
|
||||
#' `shiny.testmode` option, or FALSE if the option is not set.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Start app in the current working directory
|
||||
#' runApp()
|
||||
#'
|
||||
#' # Start app in a subdirectory called myapp
|
||||
#' runApp("myapp")
|
||||
#' }
|
||||
#'
|
||||
#' ## 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(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' ))
|
||||
#'
|
||||
#'
|
||||
#' # Running a Shiny app object
|
||||
#' app <- shinyApp(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' )
|
||||
#' runApp(app)
|
||||
#' }
|
||||
#' @export
|
||||
runApp <- function(appDir=getwd(),
|
||||
port=getOption('shiny.port'),
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
workerId="", quiet=FALSE,
|
||||
display.mode=c("auto", "normal", "showcase"),
|
||||
test.mode=getOption('shiny.testmode', FALSE)) {
|
||||
on.exit({
|
||||
handlerManager$clear()
|
||||
}, add = TRUE)
|
||||
|
||||
if (isRunning()) {
|
||||
stop("Can't call `runApp()` from within `runApp()`. If your ",
|
||||
"application code contains `runApp()`, please remove it.")
|
||||
}
|
||||
|
||||
# Make warnings print immediately
|
||||
# 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)
|
||||
|
||||
# ============================================================================
|
||||
# Global onStart/onStop callbacks
|
||||
# ============================================================================
|
||||
# Invoke user-defined onStop callbacks, before the application's internal
|
||||
# onStop callbacks.
|
||||
on.exit({
|
||||
.globals$onStopCallbacks$invoke()
|
||||
.globals$onStopCallbacks <- Callbacks$new()
|
||||
}, add = TRUE)
|
||||
|
||||
require(shiny)
|
||||
|
||||
# ============================================================================
|
||||
# Convert to Shiny app object
|
||||
# ============================================================================
|
||||
appParts <- as.shiny.appobj(appDir)
|
||||
|
||||
# ============================================================================
|
||||
# Initialize app state object
|
||||
# ============================================================================
|
||||
# This is so calls to getCurrentAppState() can be used to find (A) whether an
|
||||
# app is running and (B), get options and data associated with the app.
|
||||
initCurrentAppState(appParts)
|
||||
on.exit(clearCurrentAppState(), add = TRUE)
|
||||
# Any shinyOptions set after this point will apply to the current app only
|
||||
# (and will not persist after the app stops).
|
||||
|
||||
# ============================================================================
|
||||
# shinyOptions
|
||||
# ============================================================================
|
||||
# A unique identifier associated with this run of this application. It is
|
||||
# shared across sessions.
|
||||
shinyOptions(appToken = createUniqueId(8))
|
||||
|
||||
# Set up default cache for app.
|
||||
if (is.null(getShinyOption("cache"))) {
|
||||
shinyOptions(cache = MemoryCache$new())
|
||||
}
|
||||
|
||||
# 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.)
|
||||
applyCapturedAppOptions(appParts$appOptions)
|
||||
|
||||
# ============================================================================
|
||||
# runApp options set via shinyApp(options = list(...))
|
||||
# ============================================================================
|
||||
# The lines below set some of the app's running options, which
|
||||
# can be:
|
||||
# - left unspecified (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'
|
||||
|
||||
# ============================================================================
|
||||
# Hosted environment
|
||||
# ============================================================================
|
||||
workerId(workerId)
|
||||
|
||||
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
|
||||
# any valid version.
|
||||
ver <- Sys.getenv('SHINY_SERVER_VERSION')
|
||||
if (utils::compareVersion(ver, .shinyServerMinVersion) < 0) {
|
||||
warning('Shiny Server v', .shinyServerMinVersion,
|
||||
' or later is required; please upgrade!')
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Shinytest
|
||||
# ============================================================================
|
||||
# Set the testmode shinyoption so that this can be read by both the
|
||||
# ShinySession and the UI code (which executes separately from the
|
||||
# ShinySession code).
|
||||
shinyOptions(testmode = test.mode)
|
||||
if (test.mode) {
|
||||
message("Running application in test mode.")
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Showcase mode
|
||||
# ============================================================================
|
||||
# Showcase mode is disabled by default; it must be explicitly enabled in
|
||||
# either the DESCRIPTION file for directory-based apps, or via
|
||||
# the display.mode parameter. The latter takes precedence.
|
||||
setShowcaseDefault(0)
|
||||
|
||||
# If appDir specifies a path, and display mode is specified in the
|
||||
# DESCRIPTION file at that path, apply it here.
|
||||
if (is.character(appDir)) {
|
||||
# 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"]
|
||||
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") {
|
||||
setShowcaseDefault(0)
|
||||
}
|
||||
else if (display.mode == "showcase") {
|
||||
setShowcaseDefault(1)
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Server port
|
||||
# ============================================================================
|
||||
# determine port if we need to
|
||||
if (is.null(port)) {
|
||||
|
||||
# Try up to 20 random ports. If we don't succeed just plow ahead
|
||||
# with the final value we tried, and let the "real" startServer
|
||||
# somewhere down the line fail and throw the error to the user.
|
||||
#
|
||||
# If we (think we) succeed, save the value as .globals$lastPort,
|
||||
# and try that first next time the user wants a random port.
|
||||
|
||||
for (i in 1:20) {
|
||||
if (!is.null(.globals$lastPort)) {
|
||||
port <- .globals$lastPort
|
||||
.globals$lastPort <- NULL
|
||||
}
|
||||
else {
|
||||
# Try up to 20 random ports
|
||||
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
|
||||
tmp <- try(startServer(host, port, list()), silent=TRUE)
|
||||
if (!inherits(tmp, 'try-error')) {
|
||||
stopServer(tmp)
|
||||
.globals$lastPort <- port
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# onStart/onStop callbacks
|
||||
# ============================================================================
|
||||
# Set up the onStop before we call onStart, so that it gets called even if an
|
||||
# error happens in onStart.
|
||||
if (!is.null(appParts$onStop))
|
||||
on.exit(appParts$onStop(), add = TRUE)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
|
||||
# ============================================================================
|
||||
# Start/stop httpuv app
|
||||
# ============================================================================
|
||||
server <- startApp(appParts, port, host, quiet)
|
||||
|
||||
# Make the httpuv server object accessible. Needed for calling
|
||||
# addResourcePath while app is running.
|
||||
shinyOptions(server = server)
|
||||
|
||||
on.exit({
|
||||
stopServer(server)
|
||||
}, add = TRUE)
|
||||
|
||||
# ============================================================================
|
||||
# Launch web browser
|
||||
# ============================================================================
|
||||
if (!is.character(port)) {
|
||||
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))
|
||||
launch.browser(appUrl)
|
||||
else if (launch.browser)
|
||||
utils::browseURL(appUrl)
|
||||
} else {
|
||||
appUrl <- NULL
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Application hooks
|
||||
# ============================================================================
|
||||
callAppHook("onAppStart", appUrl)
|
||||
on.exit({
|
||||
callAppHook("onAppStop", appUrl)
|
||||
}, add = TRUE)
|
||||
|
||||
# ============================================================================
|
||||
# Run event loop via httpuv
|
||||
# ============================================================================
|
||||
.globals$reterror <- NULL
|
||||
.globals$retval <- NULL
|
||||
.globals$stopped <- FALSE
|
||||
# Top-level ..stacktraceoff..; matches with ..stacktraceon in observe(),
|
||||
# reactive(), Callbacks$invoke(), and others
|
||||
..stacktraceoff..(
|
||||
captureStackTraces({
|
||||
while (!.globals$stopped) {
|
||||
..stacktracefloor..(serviceApp())
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
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
|
||||
#'
|
||||
#' Stops the currently running Shiny app, returning control to the caller of
|
||||
#' [runApp()].
|
||||
#'
|
||||
#' @param returnValue The value that should be returned from
|
||||
#' [runApp()].
|
||||
#' @export
|
||||
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()
|
||||
}
|
||||
|
||||
#' Run Shiny Example Applications
|
||||
#'
|
||||
#' Launch Shiny example applications, and optionally, your system's web browser.
|
||||
#'
|
||||
#' @param example The name of the example to run, or `NA` (the default) to
|
||||
#' list the available examples.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#' @param host The IPv4 address that the application should listen on. Defaults
|
||||
#' to the `shiny.host` option, if set, or `"127.0.0.1"` if not.
|
||||
#' @param display.mode The mode in which to display the example. Defaults to
|
||||
#' `showcase`, but may be set to `normal` to see the example without
|
||||
#' code or commentary.
|
||||
#' @inheritParams runApp
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # List all available examples
|
||||
#' runExample()
|
||||
#'
|
||||
#' # Run one of the examples
|
||||
#' runExample("01_hello")
|
||||
#'
|
||||
#' # Print the directory containing the code for all examples
|
||||
#' system.file("examples", package="shiny")
|
||||
#' }
|
||||
#' @export
|
||||
runExample <- function(example=NA,
|
||||
port=getOption("shiny.port"),
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
display.mode=c("auto", "normal", "showcase")) {
|
||||
examplesDir <- system.file('examples', package='shiny')
|
||||
dir <- resolve(examplesDir, example)
|
||||
if (is.null(dir)) {
|
||||
if (is.na(example)) {
|
||||
errFun <- message
|
||||
errMsg <- ''
|
||||
}
|
||||
else {
|
||||
errFun <- stop
|
||||
errMsg <- paste('Example', example, 'does not exist. ')
|
||||
}
|
||||
|
||||
errFun(errMsg,
|
||||
'Valid examples are "',
|
||||
paste(list.files(examplesDir), collapse='", "'),
|
||||
'"')
|
||||
}
|
||||
else {
|
||||
runApp(dir, port = port, host = host, launch.browser = launch.browser,
|
||||
display.mode = display.mode)
|
||||
}
|
||||
}
|
||||
|
||||
#' Run a gadget
|
||||
#'
|
||||
#' Similar to `runApp`, but handles `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
|
||||
#' [`shinyApp()`][shiny] et al, or, a UI object.
|
||||
#' @param server Ignored if `app` is a Shiny app object; otherwise, passed
|
||||
#' along to `shinyApp` (i.e. `shinyApp(ui = app, server = server)`).
|
||||
#' @param port See [`runApp()`][shiny].
|
||||
#' @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
|
||||
#' [viewer()] functions.
|
||||
#' @param stopOnCancel If `TRUE` (the default), then an `observeEvent`
|
||||
#' is automatically created that handles `input$cancel` by calling
|
||||
#' `stopApp()` with an error. Pass `FALSE` if you want to handle
|
||||
#' `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
|
||||
}
|
||||
@@ -5,38 +5,36 @@ inputHandlers <- Map$new()
|
||||
#'
|
||||
#' Adds an input handler for data of this type. When called, Shiny will use the
|
||||
#' function provided to refine the data passed back from the client (after being
|
||||
#' deserialized by jsonlite) before making it available in the \code{input}
|
||||
#' variable of the \code{server.R} file.
|
||||
#' deserialized by jsonlite) before making it available in the `input` variable
|
||||
#' of the `server.R` file.
|
||||
#'
|
||||
#' This function will register the handler for the duration of the R process
|
||||
#' (unless Shiny is explicitly reloaded). For that reason, the \code{type} used
|
||||
#' (unless Shiny is explicitly reloaded). For that reason, the `type` used
|
||||
#' should be very specific to this package to minimize the risk of colliding
|
||||
#' with another Shiny package which might use this data type name. We recommend
|
||||
#' the format of "packageName.widgetName".
|
||||
#' the format of "packageName.widgetName". It should be called from the
|
||||
#' package's `.onLoad()` function.
|
||||
#'
|
||||
#' Currently Shiny registers the following handlers: \code{shiny.matrix},
|
||||
#' \code{shiny.number}, and \code{shiny.date}.
|
||||
#' Currently Shiny registers the following handlers: `shiny.matrix`,
|
||||
#' `shiny.number`, and `shiny.date`.
|
||||
#'
|
||||
#' The \code{type} of a custom Shiny Input widget will be deduced using the
|
||||
#' \code{getType()} JavaScript function on the registered Shiny inputBinding.
|
||||
#' @param type The type for which the handler should be added -- should be a
|
||||
#' single-element character vector.
|
||||
#' The `type` of a custom Shiny Input widget will be deduced using the
|
||||
#' `getType()` JavaScript function on the registered Shiny inputBinding.
|
||||
#' @param type The type for which the handler should be added --- should be a
|
||||
#' single-element character vector.
|
||||
#' @param fun The handler function. This is the function that will be used to
|
||||
#' parse the data delivered from the client before it is available in the
|
||||
#' \code{input} variable. The function will be called with the following three
|
||||
#' parameters:
|
||||
#' \enumerate{
|
||||
#' \item{The value of this input as provided by the client, deserialized
|
||||
#' using jsonlite.}
|
||||
#' \item{The \code{shinysession} in which the input exists.}
|
||||
#' \item{The name of the input.}
|
||||
#' }
|
||||
#' @param force If \code{TRUE}, will overwrite any existing handler without
|
||||
#' warning. If \code{FALSE}, will throw an error if this class already has
|
||||
#' a handler defined.
|
||||
#' `input` variable. The function will be called with the following three
|
||||
#' parameters: \enumerate{ \item{The value of this input as provided by the
|
||||
#' client, deserialized using jsonlite.} \item{The `shinysession` in which the
|
||||
#' input exists.} \item{The name of the input.} }
|
||||
#' @param force If `TRUE`, will overwrite any existing handler without warning.
|
||||
#' If `FALSE`, will throw an error if this class already has a handler
|
||||
#' defined.
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Register an input handler which rounds a input number to the nearest integer
|
||||
#' # In a package, this should be called from the .onLoad function.
|
||||
#' registerInputHandler("mypackage.validint", function(x, shinysession, name) {
|
||||
#' if (is.null(x)) return(NA)
|
||||
#' round(x)
|
||||
@@ -48,7 +46,7 @@ inputHandlers <- Map$new()
|
||||
#' }
|
||||
#'
|
||||
#' }
|
||||
#' @seealso \code{\link{removeInputHandler}}
|
||||
#' @seealso [removeInputHandler()]
|
||||
#' @export
|
||||
registerInputHandler <- function(type, fun, force=FALSE){
|
||||
if (inputHandlers$containsKey(type) && !force){
|
||||
@@ -63,9 +61,9 @@ registerInputHandler <- function(type, fun, force=FALSE){
|
||||
#' for data of this type, the default jsonlite serialization will be used.
|
||||
#'
|
||||
#' @param type The type for which handlers should be removed.
|
||||
#' @return The handler previously associated with this \code{type}, if one
|
||||
#' existed. Otherwise, \code{NULL}.
|
||||
#' @seealso \code{\link{registerInputHandler}}
|
||||
#' @return The handler previously associated with this `type`, if one
|
||||
#' existed. Otherwise, `NULL`.
|
||||
#' @seealso [registerInputHandler()]
|
||||
#' @export
|
||||
removeInputHandler <- function(type){
|
||||
inputHandlers$remove(type)
|
||||
@@ -103,8 +101,8 @@ applyInputHandler <- function(name, val, shinysession) {
|
||||
#' 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
|
||||
#' like `"x:shiny.date"`. This function would apply the `"shiny.date"`
|
||||
#' input handler to the value, and then rename the result to `"x"`, in the
|
||||
#' output.
|
||||
#'
|
||||
#' @param inputs A named list of input values.
|
||||
|
||||
169
R/server-resource-paths.R
Normal file
169
R/server-resource-paths.R
Normal file
@@ -0,0 +1,169 @@
|
||||
.globals$resourcePaths <- list()
|
||||
.globals$resources <- list()
|
||||
|
||||
#' Resource Publishing
|
||||
#'
|
||||
#' Add, remove, or list directory of static resources to Shiny's web server,
|
||||
#' with the given path prefix. Primarily intended for package authors to make
|
||||
#' supporting JavaScript/CSS files available to their components.
|
||||
#'
|
||||
#' Shiny provides two ways of serving static files (i.e., resources):
|
||||
#'
|
||||
#' 1. Static files under the `www/` directory are automatically made available
|
||||
#' under a request path that begins with `/`.
|
||||
#' 2. `addResourcePath()` makes static files in a `directoryPath` available
|
||||
#' under a request path that begins with `prefix`.
|
||||
#'
|
||||
#' The second approach is primarily intended for package authors to make
|
||||
#' supporting JavaScript/CSS files available to their components.
|
||||
#'
|
||||
#' Tools for managing static resources published by Shiny's web server:
|
||||
#' * `addResourcePath()` adds a directory of static resources.
|
||||
#' * `resourcePaths()` lists the currently active resource mappings.
|
||||
#' * `removeResourcePath()` removes a directory of static resources.
|
||||
#'
|
||||
#' @param prefix The URL prefix (without slashes). Valid characters are 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
|
||||
#' served.
|
||||
#'
|
||||
#' @rdname resourcePaths
|
||||
#' @seealso [singleton()]
|
||||
#'
|
||||
#' @examples
|
||||
#' addResourcePath('datasets', system.file('data', package='datasets'))
|
||||
#' resourcePaths()
|
||||
#' removeResourcePath('datasets')
|
||||
#' resourcePaths()
|
||||
#'
|
||||
#' # make sure all resources are removed
|
||||
#' lapply(names(resourcePaths()), removeResourcePath)
|
||||
#' @export
|
||||
addResourcePath <- function(prefix, directoryPath) {
|
||||
if (length(prefix) != 1) stop("prefix must be of length 1")
|
||||
if (grepl("^\\.+$", prefix)) stop("prefix can't be composed of dots only")
|
||||
if (!grepl('[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")
|
||||
}
|
||||
normalizedPath <- tryCatch(normalizePath(directoryPath, mustWork = TRUE),
|
||||
error = function(e) {
|
||||
stop("Couldn't normalize path in `addResourcePath`, with arguments: ",
|
||||
"`prefix` = '", prefix, "'; `directoryPath` = '" , directoryPath, "'")
|
||||
}
|
||||
)
|
||||
|
||||
# # Often times overwriting a resource path is "what you want",
|
||||
# # but sometimes it can lead to difficult to diagnose issues
|
||||
# # (e.g. an implict dependency might set a resource path that
|
||||
# # conflicts with what you, the app author, are trying to register)
|
||||
# # Note that previous versions of shiny used to warn about this case,
|
||||
# # but it was eventually removed since it caused confusion (#567).
|
||||
# # It seems a good compromise is to throw a more information message.
|
||||
# if (getOption("shiny.resourcePathChanges", FALSE) &&
|
||||
# prefix %in% names(.globals$resourcePaths)) {
|
||||
# existingPath <- .globals$resourcePaths[[prefix]]$path
|
||||
# if (normalizedPath != existingPath) {
|
||||
# message(
|
||||
# "The resource path '", prefix, "' used to point to ",
|
||||
# existingPath, ", but it now points to ", normalizedPath, ". ",
|
||||
# "If your app doesn't work as expected, you may want to ",
|
||||
# "choose a different prefix name."
|
||||
# )
|
||||
# }
|
||||
# }
|
||||
|
||||
# If a shiny app is currently running, dynamically register this path with
|
||||
# the corresponding httpuv server object.
|
||||
if (!is.null(getShinyOption("server")))
|
||||
{
|
||||
getShinyOption("server")$setStaticPath(.list = stats::setNames(normalizedPath, prefix))
|
||||
}
|
||||
|
||||
# .globals$resourcePaths and .globals$resources persist across runs of applications.
|
||||
.globals$resourcePaths[[prefix]] <- staticPath(normalizedPath)
|
||||
# This is necessary because resourcePaths is only for serving assets out of C++;
|
||||
# to support subapps, we also need assets to be served out of R, because those
|
||||
# URLs are rewritten by R code (i.e. routeHandler) before they can be matched to
|
||||
# a resource path.
|
||||
.globals$resources[[prefix]] <- list(
|
||||
directoryPath = normalizedPath,
|
||||
func = staticHandler(normalizedPath)
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname resourcePaths
|
||||
#' @export
|
||||
resourcePaths <- function() {
|
||||
urls <- names(.globals$resourcePaths)
|
||||
paths <- vapply(.globals$resourcePaths, function(x) x$path, character(1))
|
||||
stats::setNames(paths, urls)
|
||||
}
|
||||
|
||||
hasResourcePath <- function(prefix) {
|
||||
prefix %in% names(resourcePaths())
|
||||
}
|
||||
|
||||
#' @rdname resourcePaths
|
||||
#' @export
|
||||
removeResourcePath <- function(prefix) {
|
||||
if (length(prefix) > 1) stop("`prefix` must be of length 1.")
|
||||
if (!hasResourcePath(prefix)) {
|
||||
warning("Resource ", prefix, " not found.")
|
||||
return(invisible(FALSE))
|
||||
}
|
||||
.globals$resourcePaths[[prefix]] <- NULL
|
||||
.globals$resources[[prefix]] <- NULL
|
||||
invisible(TRUE)
|
||||
}
|
||||
|
||||
# This function handles any GET request with two or more path elements where the
|
||||
# first path element matches a prefix that was previously added using
|
||||
# addResourcePath().
|
||||
#
|
||||
# For example, if `addResourcePath("foo", "~/bar")` was called, then a GET
|
||||
# request for /foo/one/two.html would rewrite the PATH_INFO as /one/two.html and
|
||||
# send it to the resource path function for "foo". As of this writing, that
|
||||
# function will always be a staticHandler, which serves up a file if it exists
|
||||
# and NULL if it does not.
|
||||
#
|
||||
# Since Shiny 1.3.x, assets registered via addResourcePath should mostly be
|
||||
# served out of httpuv's native static file serving features. However, in the
|
||||
# specific case of subapps, the R code path must be used, because subapps insert
|
||||
# a giant random ID into the beginning of the URL that must be stripped off by
|
||||
# an R route handler (see addSubApp()).
|
||||
resourcePathHandler <- function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
# e.g. "/foo/one/two.html"
|
||||
path <- req$PATH_INFO
|
||||
|
||||
match <- regexpr('^/([^/]+)/', path, perl=TRUE)
|
||||
if (match == -1)
|
||||
return(NULL)
|
||||
len <- attr(match, 'capture.length')
|
||||
# e.g. "foo"
|
||||
prefix <- substr(path, 2, 2 + len - 1)
|
||||
|
||||
resInfo <- .globals$resources[[prefix]]
|
||||
if (is.null(resInfo))
|
||||
return(NULL)
|
||||
|
||||
# e.g. "/one/two.html"
|
||||
suffix <- substr(path, 2 + len, nchar(path))
|
||||
|
||||
# Create a new request that's a clone of the current request, but adjust
|
||||
# PATH_INFO and SCRIPT_NAME to reflect that we have already matched the first
|
||||
# path element (e.g. "/foo"). See routeHandler() for more info.
|
||||
subreq <- as.environment(as.list(req, all.names=TRUE))
|
||||
subreq$PATH_INFO <- suffix
|
||||
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, substr(path, 1, 2 + len), sep='')
|
||||
|
||||
return(resInfo$func(subreq))
|
||||
}
|
||||
771
R/server.R
771
R/server.R
@@ -22,102 +22,32 @@ registerClient <- function(client) {
|
||||
}
|
||||
|
||||
|
||||
.globals$resources <- list()
|
||||
|
||||
.globals$showcaseDefault <- 0
|
||||
|
||||
.globals$showcaseOverride <- FALSE
|
||||
|
||||
#' Resource Publishing
|
||||
#'
|
||||
#' Adds a directory of static resources to Shiny's web server, with the given
|
||||
#' path prefix. Primarily intended for package authors to make supporting
|
||||
#' 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.
|
||||
#' 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
|
||||
#' served.
|
||||
#'
|
||||
#' @details You can call \code{addResourcePath} multiple times for a given
|
||||
#' \code{prefix}; only the most recent value will be retained. If the
|
||||
#' normalized \code{directoryPath} is different than the directory that's
|
||||
#' currently mapped to the \code{prefix}, a warning will be issued.
|
||||
#'
|
||||
#' @seealso \code{\link{singleton}}
|
||||
#'
|
||||
#' @examples
|
||||
#' addResourcePath('datasets', system.file('data', package='datasets'))
|
||||
#' @export
|
||||
addResourcePath <- function(prefix, directoryPath) {
|
||||
prefix <- prefix[1]
|
||||
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")
|
||||
}
|
||||
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) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
path <- req$PATH_INFO
|
||||
|
||||
match <- regexpr('^/([^/]+)/', path, perl=TRUE)
|
||||
if (match == -1)
|
||||
return(NULL)
|
||||
len <- attr(match, 'capture.length')
|
||||
prefix <- substr(path, 2, 2 + len - 1)
|
||||
|
||||
resInfo <- .globals$resources[[prefix]]
|
||||
if (is.null(resInfo))
|
||||
return(NULL)
|
||||
|
||||
suffix <- substr(path, 2 + len, nchar(path))
|
||||
|
||||
subreq <- as.environment(as.list(req, all.names=TRUE))
|
||||
subreq$PATH_INFO <- suffix
|
||||
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, substr(path, 1, 2 + len), sep='')
|
||||
|
||||
return(resInfo$func(subreq))
|
||||
}
|
||||
|
||||
#' Define Server Functionality
|
||||
#'
|
||||
#' Defines the server-side logic of the Shiny application. This generally
|
||||
#' involves creating functions that map user inputs to various kinds of output.
|
||||
#' In older versions of Shiny, it was necessary to call \code{shinyServer()} in
|
||||
#' the \code{server.R} file, but this is no longer required as of Shiny 0.10.
|
||||
#' Now the \code{server.R} file may simply return the appropriate server
|
||||
#' In older versions of Shiny, it was necessary to call `shinyServer()` in
|
||||
#' the `server.R` file, but this is no longer required as of Shiny 0.10.
|
||||
#' Now the `server.R` file may simply return the appropriate server
|
||||
#' function (as the last expression in the code), without calling
|
||||
#' \code{shinyServer()}.
|
||||
#' `shinyServer()`.
|
||||
#'
|
||||
#' Call \code{shinyServer} from your application's \code{server.R}
|
||||
#' Call `shinyServer` from your application's `server.R`
|
||||
#' file, passing in a "server function" that provides the server-side logic of
|
||||
#' your application.
|
||||
#'
|
||||
#' The server function will be called when each client (web browser) first loads
|
||||
#' the Shiny application's page. It must take an \code{input} and an
|
||||
#' \code{output} parameter. Any return value will be ignored. It also takes an
|
||||
#' optional \code{session} parameter, which is used when greater control is
|
||||
#' the Shiny application's page. It must take an `input` and an
|
||||
#' `output` parameter. Any return value will be ignored. It also takes an
|
||||
#' optional `session` parameter, which is used when greater control is
|
||||
#' needed.
|
||||
#'
|
||||
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{tutorial} for more
|
||||
#' See the [tutorial](http://rstudio.github.com/shiny/tutorial/) for more
|
||||
#' on how to write a server function.
|
||||
#'
|
||||
#' @param func The server function for this application. See the details section
|
||||
@@ -144,6 +74,7 @@ resourcePathHandler <- function(req) {
|
||||
#' }
|
||||
#' }
|
||||
#' @export
|
||||
#' @keywords internal
|
||||
shinyServer <- function(func) {
|
||||
.globals$server <- list(func)
|
||||
invisible(func)
|
||||
@@ -178,6 +109,8 @@ decodeMessage <- function(data) {
|
||||
return(mainMessage)
|
||||
}
|
||||
|
||||
autoReloadCallbacks <- Callbacks$new()
|
||||
|
||||
createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
appvars <- new.env()
|
||||
appvars$server <- NULL
|
||||
@@ -187,7 +120,7 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
# This value, if non-NULL, must be present on all HTTP and WebSocket
|
||||
# requests as the Shiny-Shared-Secret header or else access will be
|
||||
# denied (403 response for HTTP, and instant close for websocket).
|
||||
sharedSecret <- getOption('shiny.sharedSecret')
|
||||
checkSharedSecret <- loadSharedSecret()
|
||||
|
||||
appHandlers <- list(
|
||||
http = joinHandlers(c(
|
||||
@@ -195,14 +128,30 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
httpHandlers,
|
||||
sys.www.root,
|
||||
resourcePathHandler,
|
||||
reactLogHandler)),
|
||||
reactLogHandler
|
||||
)),
|
||||
ws = function(ws) {
|
||||
if (!is.null(sharedSecret)
|
||||
&& !identical(sharedSecret, ws$request$HTTP_SHINY_SHARED_SECRET)) {
|
||||
if (!checkSharedSecret(ws$request$HTTP_SHINY_SHARED_SECRET)) {
|
||||
ws$close()
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
if (identical(ws$request$PATH_INFO, "/autoreload/")) {
|
||||
if (!getOption("shiny.autoreload", FALSE)) {
|
||||
ws$close()
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
callbackHandle <- autoReloadCallbacks$register(function() {
|
||||
ws$send("autoreload")
|
||||
ws$close()
|
||||
})
|
||||
ws$onClose(function() {
|
||||
callbackHandle()
|
||||
})
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
if (!is.null(getOption("shiny.observer.error", NULL))) {
|
||||
warning(
|
||||
call. = FALSE,
|
||||
@@ -417,6 +366,70 @@ startApp <- function(appObj, port, host, quiet) {
|
||||
handlerManager$addHandler(appHandlers$http, "/", tail = TRUE)
|
||||
handlerManager$addWSHandler(appHandlers$ws, "/", tail = TRUE)
|
||||
|
||||
httpuvApp <- handlerManager$createHttpuvApp()
|
||||
httpuvApp$staticPaths <- c(
|
||||
appObj$staticPaths,
|
||||
list(
|
||||
# Always handle /session URLs dynamically, even if / is a static path.
|
||||
"session" = excludeStaticPath(),
|
||||
"shared" = system.file(package = "shiny", "www", "shared")
|
||||
),
|
||||
.globals$resourcePaths
|
||||
)
|
||||
|
||||
# throw an informative warning if a subdirectory of the
|
||||
# app's www dir conflicts with another resource prefix
|
||||
wwwDir <- httpuvApp$staticPaths[["/"]]$path
|
||||
if (length(wwwDir)) {
|
||||
# although httpuv allows for resource prefixes like 'foo/bar',
|
||||
# we won't worry about conflicts in sub-sub directories since
|
||||
# addResourcePath() currently doesn't allow it
|
||||
wwwSubDirs <- list.dirs(wwwDir, recursive = FALSE, full.names = FALSE)
|
||||
resourceConflicts <- intersect(wwwSubDirs, names(httpuvApp$staticPaths))
|
||||
if (length(resourceConflicts)) {
|
||||
warning(
|
||||
"Found subdirectories of your app's www/ directory that ",
|
||||
"conflict with other resource URL prefixes. ",
|
||||
"Consider renaming these directories: '",
|
||||
paste0("www/", resourceConflicts, collapse = "', '"), "'",
|
||||
call. = FALSE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
# check for conflicts in each pairwise combinations of resource mappings
|
||||
checkResourceConflict <- function(paths) {
|
||||
if (length(paths) < 2) return(NULL)
|
||||
# ensure paths is a named character vector: c(resource_path = local_path)
|
||||
paths <- vapply(paths, function(x) if (inherits(x, "staticPath")) x$path else x, character(1))
|
||||
# get all possible pairwise combinations of paths
|
||||
pair_indices <- utils::combn(length(paths), 2, simplify = FALSE)
|
||||
lapply(pair_indices, function(x) {
|
||||
p1 <- paths[x[1]]
|
||||
p2 <- paths[x[2]]
|
||||
if (identical(names(p1), names(p2)) && (p1 != p2)) {
|
||||
warning(
|
||||
"Found multiple local file paths pointing the same resource prefix: ", names(p1), ". ",
|
||||
"If you run into resource-related issues (e.g. 404 requests), consider ",
|
||||
"using `addResourcePath()` and/or `removeResourcePath()` to manage resource mappings.",
|
||||
call. = FALSE
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
checkResourceConflict(httpuvApp$staticPaths)
|
||||
|
||||
httpuvApp$staticPathOptions <- httpuv::staticPathOptions(
|
||||
html_charset = "utf-8",
|
||||
headers = list("X-UA-Compatible" = "IE=edge,chrome=1"),
|
||||
validation =
|
||||
if (!is.null(getOption("shiny.sharedSecret"))) {
|
||||
sprintf('"Shiny-Shared-Secret" == "%s"', getOption("shiny.sharedSecret"))
|
||||
} else {
|
||||
character(0)
|
||||
}
|
||||
)
|
||||
|
||||
if (is.numeric(port) || is.integer(port)) {
|
||||
if (!quiet) {
|
||||
hostString <- host
|
||||
@@ -424,7 +437,7 @@ startApp <- function(appObj, port, host, quiet) {
|
||||
hostString <- paste0("[", hostString, "]")
|
||||
message('\n', 'Listening on http://', hostString, ':', port)
|
||||
}
|
||||
return(startServer(host, port, handlerManager$createHttpuvApp()))
|
||||
return(startServer(host, port, httpuvApp))
|
||||
} else if (is.character(port)) {
|
||||
if (!quiet) {
|
||||
message('\n', 'Listening on domain socket ', port)
|
||||
@@ -436,7 +449,7 @@ startApp <- function(appObj, port, host, quiet) {
|
||||
"configuration (and not domain sockets), then `port` must ",
|
||||
"be numeric, not a string.")
|
||||
}
|
||||
return(startPipeServer(port, mask, handlerManager$createHttpuvApp()))
|
||||
return(startPipeServer(port, mask, httpuvApp))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,597 +474,17 @@ serviceApp <- function() {
|
||||
|
||||
.shinyServerMinVersion <- '0.3.4'
|
||||
|
||||
# Global flag that's TRUE whenever we're inside of the scope of a call to runApp
|
||||
.globals$running <- FALSE
|
||||
|
||||
#' 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}.
|
||||
#' @return `TRUE` if a Shiny application is currently running. Otherwise,
|
||||
#' `FALSE`.
|
||||
#' @export
|
||||
isRunning <- function() {
|
||||
.globals$running
|
||||
!is.null(getCurrentAppState())
|
||||
}
|
||||
|
||||
#' Run Shiny Application
|
||||
#'
|
||||
#' Runs a Shiny application. This function normally does not return; interrupt R
|
||||
#' to stop the application (usually by pressing Ctrl+C or Esc).
|
||||
#'
|
||||
#' The host parameter was introduced in Shiny 0.9.0. Its default value of
|
||||
#' \code{"127.0.0.1"} means that, contrary to previous versions of Shiny, only
|
||||
#' the current machine can access locally hosted Shiny apps. To allow other
|
||||
#' 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 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,
|
||||
#' use a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only. This value of this parameter can also be a
|
||||
#' function to call with the application's URL.
|
||||
#' @param host The IPv4 address that the application should listen on. Defaults
|
||||
#' to the \code{shiny.host} option, if set, or \code{"127.0.0.1"} if not. See
|
||||
#' Details.
|
||||
#' @param workerId Can generally be ignored. Exists to help some editions of
|
||||
#' Shiny Server Pro route requests to the correct process.
|
||||
#' @param quiet Should Shiny status messages be shown? Defaults to FALSE.
|
||||
#' @param display.mode The mode in which to display the application. If set to
|
||||
#' the value \code{"showcase"}, shows application code and metadata from a
|
||||
#' \code{DESCRIPTION} file in the application directory alongside the
|
||||
#' 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{
|
||||
#' # Start app in the current working directory
|
||||
#' runApp()
|
||||
#'
|
||||
#' # Start app in a subdirectory called myapp
|
||||
#' runApp("myapp")
|
||||
#' }
|
||||
#'
|
||||
#' ## 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(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' ))
|
||||
#'
|
||||
#'
|
||||
#' # Running a Shiny app object
|
||||
#' app <- shinyApp(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' )
|
||||
#' runApp(app)
|
||||
#' }
|
||||
#' @export
|
||||
runApp <- function(appDir=getwd(),
|
||||
port=getOption('shiny.port'),
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
workerId="", quiet=FALSE,
|
||||
display.mode=c("auto", "normal", "showcase"),
|
||||
test.mode=getOption('shiny.testmode', FALSE)) {
|
||||
on.exit({
|
||||
handlerManager$clear()
|
||||
}, add = TRUE)
|
||||
|
||||
if (.globals$running) {
|
||||
stop("Can't call `runApp()` from within `runApp()`. If your ",
|
||||
"application code contains `runApp()`, please remove it.")
|
||||
}
|
||||
.globals$running <- TRUE
|
||||
on.exit({
|
||||
.globals$running <- FALSE
|
||||
}, add = TRUE)
|
||||
|
||||
# Enable per-app Shiny options, 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
|
||||
# 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 (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
|
||||
# any valid version.
|
||||
ver <- Sys.getenv('SHINY_SERVER_VERSION')
|
||||
if (utils::compareVersion(ver, .shinyServerMinVersion) < 0) {
|
||||
warning('Shiny Server v', .shinyServerMinVersion,
|
||||
' or later is required; please upgrade!')
|
||||
}
|
||||
}
|
||||
|
||||
# Showcase mode is disabled by default; it must be explicitly enabled in
|
||||
# either the DESCRIPTION file for directory-based apps, or via
|
||||
# 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)) {
|
||||
# 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"]
|
||||
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") {
|
||||
setShowcaseDefault(0)
|
||||
}
|
||||
else if (display.mode == "showcase") {
|
||||
setShowcaseDefault(1)
|
||||
}
|
||||
|
||||
require(shiny)
|
||||
|
||||
# determine port if we need to
|
||||
if (is.null(port)) {
|
||||
|
||||
# Try up to 20 random ports. If we don't succeed just plow ahead
|
||||
# with the final value we tried, and let the "real" startServer
|
||||
# somewhere down the line fail and throw the error to the user.
|
||||
#
|
||||
# If we (think we) succeed, save the value as .globals$lastPort,
|
||||
# and try that first next time the user wants a random port.
|
||||
|
||||
for (i in 1:20) {
|
||||
if (!is.null(.globals$lastPort)) {
|
||||
port <- .globals$lastPort
|
||||
.globals$lastPort <- NULL
|
||||
}
|
||||
else {
|
||||
# Try up to 20 random ports
|
||||
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
|
||||
tmp <- try(startServer(host, port, list()), silent=TRUE)
|
||||
if (!inherits(tmp, 'try-error')) {
|
||||
stopServer(tmp)
|
||||
.globals$lastPort <- port
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 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$onStop))
|
||||
on.exit(appParts$onStop(), add = TRUE)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
|
||||
server <- startApp(appParts, port, host, quiet)
|
||||
|
||||
on.exit({
|
||||
stopServer(server)
|
||||
}, add = TRUE)
|
||||
|
||||
if (!is.character(port)) {
|
||||
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))
|
||||
launch.browser(appUrl)
|
||||
else if (launch.browser)
|
||||
utils::browseURL(appUrl)
|
||||
} else {
|
||||
appUrl <- NULL
|
||||
}
|
||||
|
||||
# call application hooks
|
||||
callAppHook("onAppStart", appUrl)
|
||||
on.exit({
|
||||
callAppHook("onAppStop", appUrl)
|
||||
}, add = TRUE)
|
||||
|
||||
.globals$reterror <- NULL
|
||||
.globals$retval <- NULL
|
||||
.globals$stopped <- FALSE
|
||||
# Top-level ..stacktraceoff..; matches with ..stacktraceon in observe(),
|
||||
# reactive(), Callbacks$invoke(), and others
|
||||
..stacktraceoff..(
|
||||
captureStackTraces({
|
||||
while (!.globals$stopped) {
|
||||
..stacktracefloor..(serviceApp())
|
||||
Sys.sleep(0.001)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
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
|
||||
#'
|
||||
#' Stops the currently running Shiny app, returning control to the caller of
|
||||
#' \code{\link{runApp}}.
|
||||
#'
|
||||
#' @param returnValue The value that should be returned from
|
||||
#' \code{\link{runApp}}.
|
||||
#' @export
|
||||
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()
|
||||
}
|
||||
|
||||
#' Run Shiny Example Applications
|
||||
#'
|
||||
#' Launch Shiny example applications, and optionally, your system's web browser.
|
||||
#'
|
||||
#' @param example The name of the example to run, or \code{NA} (the default) to
|
||||
#' list the available examples.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#' @param host The IPv4 address that the application should listen on. Defaults
|
||||
#' to the \code{shiny.host} option, if set, or \code{"127.0.0.1"} if not.
|
||||
#' @param display.mode The mode in which to display the example. Defaults to
|
||||
#' \code{showcase}, but may be set to \code{normal} to see the example without
|
||||
#' code or commentary.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # List all available examples
|
||||
#' runExample()
|
||||
#'
|
||||
#' # Run one of the examples
|
||||
#' runExample("01_hello")
|
||||
#'
|
||||
#' # Print the directory containing the code for all examples
|
||||
#' system.file("examples", package="shiny")
|
||||
#' }
|
||||
#' @export
|
||||
runExample <- function(example=NA,
|
||||
port=NULL,
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
display.mode=c("auto", "normal", "showcase")) {
|
||||
examplesDir <- system.file('examples', package='shiny')
|
||||
dir <- resolve(examplesDir, example)
|
||||
if (is.null(dir)) {
|
||||
if (is.na(example)) {
|
||||
errFun <- message
|
||||
errMsg <- ''
|
||||
}
|
||||
else {
|
||||
errFun <- stop
|
||||
errMsg <- paste('Example', example, 'does not exist. ')
|
||||
}
|
||||
|
||||
errFun(errMsg,
|
||||
'Valid examples are "',
|
||||
paste(list.files(examplesDir), collapse='", "'),
|
||||
'"')
|
||||
}
|
||||
else {
|
||||
runApp(dir, port = port, host = host, launch.browser = launch.browser,
|
||||
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.
|
||||
@@ -1062,5 +495,5 @@ inShinyServer <- function() {
|
||||
# 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)
|
||||
identical(as.raw(c(0x03, 0xe9)), msg)
|
||||
}
|
||||
|
||||
@@ -8,62 +8,242 @@ 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
|
||||
# Check if there's a current session
|
||||
session <- getDefaultReactiveDomain()
|
||||
if (!is.null(session)) {
|
||||
if (name %in% names(session$options)) {
|
||||
return(session$options[[name]])
|
||||
} else {
|
||||
return(default)
|
||||
}
|
||||
}
|
||||
|
||||
# Check if there's a current app
|
||||
app_state <- getCurrentAppState()
|
||||
if (!is.null(app_state)) {
|
||||
if (name %in% names(app_state$options)) {
|
||||
return(app_state$options[[name]])
|
||||
} else {
|
||||
return(default)
|
||||
}
|
||||
}
|
||||
|
||||
# If we got here, look in global options
|
||||
if (name %in% names(.globals$options)) {
|
||||
return(.globals$options[[name]])
|
||||
} else {
|
||||
return(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.
|
||||
#' @description
|
||||
#'
|
||||
#' 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.
|
||||
#' There are two mechanisms for working with options for Shiny. One is the
|
||||
#' [options()] function, which is part of base R, and the other is the
|
||||
#' `shinyOptions()` function, which is in the Shiny package. The reason for
|
||||
#' these two mechanisms is has to do with legacy code and scoping.
|
||||
#'
|
||||
#' @param ... Options to set, with the form \code{name = value}.
|
||||
#' The [options()] function sets options globally, for the duration of the R
|
||||
#' process. The [getOption()] function retrieves the value of an option. All
|
||||
#' shiny related options of this type are prefixed with `"shiny."`.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyOptions(myOption = 10)
|
||||
#' getShinyOption("myOption")
|
||||
#' The `shinyOptions()` function sets the value of a shiny option, but unlike
|
||||
#' `options()`, it is not always global in scope; the options may be scoped
|
||||
#' globally, to an application, or to a user session in an application,
|
||||
#' depending on the context. The `getShinyOption()` function retrieves a value
|
||||
#' of a shiny option. Currently, the options set via `shinyOptions` are for
|
||||
#' internal use only.
|
||||
#'
|
||||
#' @section Options with `options()`:
|
||||
#'
|
||||
#' \describe{
|
||||
#' \item{shiny.autoreload (defaults to `FALSE`)}{If `TRUE` when a Shiny app is launched, the
|
||||
#' app directory will be continually monitored for changes to files that
|
||||
#' have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif. If any
|
||||
#' changes are detected, all connected Shiny sessions are reloaded. This
|
||||
#' allows for fast feedback loops when tweaking Shiny UI.
|
||||
#'
|
||||
#' Since monitoring for changes is expensive (we simply poll for last
|
||||
#' modified times), 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 default polling interval is 500 milliseconds. You can change this
|
||||
#' by setting e.g. `options(shiny.autoreload.interval = 2000)` (every
|
||||
#' two seconds).}
|
||||
#' \item{shiny.deprecation.messages (defaults to `TRUE`)}{This controls whether messages for
|
||||
#' deprecated functions in Shiny will be printed. See
|
||||
#' [shinyDeprecated()] for more information.}
|
||||
#' \item{shiny.error (defaults to `NULL`)}{This can be a function which is called when an error
|
||||
#' occurs. For example, `options(shiny.error=recover)` will result a
|
||||
#' the debugger prompt when an error occurs.}
|
||||
#' \item{shiny.fullstacktrace (defaults to `FALSE`)}{Controls whether "pretty" (`FALSE`) or full
|
||||
#' stack traces (`TRUE`) are dumped to the console when errors occur during Shiny app execution.
|
||||
#' Pretty stack traces attempt to only show user-supplied code, but this pruning can't always
|
||||
#' be done 100% correctly.}
|
||||
#' \item{shiny.host (defaults to `"127.0.0.1"`)}{The IP address that Shiny should listen on. See
|
||||
#' [runApp()] for more information.}
|
||||
#' \item{shiny.jquery.version (defaults to `3`)}{The major version of jQuery to use.
|
||||
#' Currently only values of `3` or `1` are supported. If `1`, then jQuery 1.12.4 is used. If `3`,
|
||||
#' then jQuery 3.5.1 is used.}
|
||||
#' \item{shiny.json.digits (defaults to `16`)}{The number of digits to use when converting
|
||||
#' numbers to JSON format to send to the client web browser.}
|
||||
#' \item{shiny.launch.browser (defaults to `interactive()`)}{A boolean which controls the default behavior
|
||||
#' when an app is run. See [runApp()] for more information.}
|
||||
#' \item{shiny.maxRequestSize (defaults to 5MB)}{This is a number which specifies the maximum
|
||||
#' web request size, which serves as a size limit for file uploads.}
|
||||
#' \item{shiny.minified (defaults to `TRUE`)}{By default
|
||||
#' Whether or not to include Shiny's JavaScript as a minified (`shiny.min.js`)
|
||||
#' or un-minified (`shiny.js`) file. The un-minified version is larger,
|
||||
#' but can be helpful for development and debugging.}
|
||||
#' \item{shiny.port (defaults to a random open port)}{A port number that Shiny will listen on. See
|
||||
#' [runApp()] for more information.}
|
||||
#' \item{shiny.reactlog (defaults to `FALSE`)}{If `TRUE`, enable logging of reactive events,
|
||||
#' which can be viewed later with the [reactlogShow()] function.
|
||||
#' This incurs a substantial performance penalty and should not be used in
|
||||
#' production.}
|
||||
#' \item{shiny.sanitize.errors (defaults to `FALSE`)}{If `TRUE`, then normal errors (i.e.
|
||||
#' errors not wrapped in `safeError`) won't show up in the app; a simple
|
||||
#' generic error message is printed instead (the error and strack trace printed
|
||||
#' to the console remain unchanged). If you want to sanitize errors in general, but you DO want a
|
||||
#' particular error `e` to get displayed to the user, then set this option
|
||||
#' to `TRUE` and use `stop(safeError(e))` for errors you want the
|
||||
#' user to see.}
|
||||
#' \item{shiny.stacktraceoffset (defaults to `TRUE`)}{If `TRUE`, then Shiny's printed stack
|
||||
#' traces will display srcrefs one line above their usual location. This is
|
||||
#' an arguably more intuitive arrangement for casual R users, as the name
|
||||
#' of a function appears next to the srcref where it is defined, rather than
|
||||
#' where it is currently being called from.}
|
||||
#' \item{shiny.suppressMissingContextError (defaults to `FALSE`)}{Normally, invoking a reactive
|
||||
#' outside of a reactive context (or [isolate()]) results in
|
||||
#' an error. If this is `TRUE`, don't error in these cases. This
|
||||
#' should only be used for debugging or demonstrations of reactivity at the
|
||||
#' console.}
|
||||
#' \item{shiny.testmode (defaults to `FALSE`)}{If `TRUE`, then various features for testing Shiny
|
||||
#' applications are enabled.}
|
||||
#' \item{shiny.trace (defaults to `FALSE`)}{Print messages sent between the R server and the web
|
||||
#' browser client to the R console. This is useful for debugging. Possible
|
||||
#' values are `"send"` (only print messages sent to the client),
|
||||
#' `"recv"` (only print messages received by the server), `TRUE`
|
||||
#' (print all messages), or `FALSE` (default; don't print any of these
|
||||
#' messages).}
|
||||
#' \item{shiny.autoload.r (defaults to `TRUE`)}{If `TRUE`, then the R/
|
||||
#' of a shiny app will automatically be sourced.}
|
||||
#' \item{shiny.usecairo (defaults to `TRUE`)}{This is used to disable graphical rendering by the
|
||||
#' Cairo package, if it is installed. See [plotPNG()] for more
|
||||
#' information.}
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
#' @section Scoping for `shinyOptions()`:
|
||||
#'
|
||||
#' There are three levels of scoping for `shinyOptions()`: global,
|
||||
#' application, and session.
|
||||
#'
|
||||
#' The global option set is available by default. Any calls to
|
||||
#' `shinyOptions()` and `getShinyOption()` outside of an app will access the
|
||||
#' global option set.
|
||||
#'
|
||||
#' When a Shiny application is run with [runApp()], the global option set is
|
||||
#' duplicated and the new option set is available at the application level. If
|
||||
#' options are set from `global.R`, `app.R`, `ui.R`, or `server.R` (but
|
||||
#' outside of the server function), then the application-level options will be
|
||||
#' modified.
|
||||
#'
|
||||
#' Each time a user session is started, the application-level option set is
|
||||
#' duplicated, for that session. If the options are set from inside the server
|
||||
#' function, then they will be scoped to the session.
|
||||
#'
|
||||
#' @section Options with `shinyOptions()`:
|
||||
#'
|
||||
#' There are a number of global options that affect Shiny's behavior. These
|
||||
#' can be set globally with `options()` or locally (for a single app) with
|
||||
#' `shinyOptions()`.
|
||||
#'
|
||||
#' \describe{ \item{cache}{A caching object that will be used by
|
||||
#' [renderCachedPlot()]. If not specified, a [memoryCache()] will be used.} }
|
||||
#'
|
||||
#' @param ... Options to set, with the form `name = value`.
|
||||
#' @aliases shiny-options
|
||||
#' @export
|
||||
shinyOptions <- function(...) {
|
||||
newOpts <- list(...)
|
||||
|
||||
if (length(newOpts) > 0) {
|
||||
# If we're within a session, modify at the session level.
|
||||
session <- getDefaultReactiveDomain()
|
||||
if (!is.null(session)) {
|
||||
# Modify session-level-options
|
||||
session$options <- dropNulls(mergeVectors(session$options, newOpts))
|
||||
return(invisible(session$options))
|
||||
}
|
||||
|
||||
# If not in a session, but we have a currently running app, modify options
|
||||
# at the app level.
|
||||
app_state <- getCurrentAppState()
|
||||
if (!is.null(app_state)) {
|
||||
# Modify app-level options
|
||||
app_state$options <- dropNulls(mergeVectors(app_state$options, newOpts))
|
||||
return(invisible(app_state$options))
|
||||
}
|
||||
|
||||
# If no currently running app, modify global options and return them.
|
||||
.globals$options <- dropNulls(mergeVectors(.globals$options, newOpts))
|
||||
invisible(.globals$options)
|
||||
} else {
|
||||
.globals$options
|
||||
return(invisible(.globals$options))
|
||||
}
|
||||
}
|
||||
|
||||
# If not setting any options, just return current option set, visibly.
|
||||
|
||||
# Eval an expression with a new option set
|
||||
withLocalOptions <- function(expr) {
|
||||
oldOptionSet <- .globals$options
|
||||
on.exit(.globals$options <- oldOptionSet)
|
||||
session <- getDefaultReactiveDomain()
|
||||
if (!is.null(session)) {
|
||||
return(session$options)
|
||||
}
|
||||
|
||||
expr
|
||||
app_state <- getCurrentAppState()
|
||||
if (!is.null(app_state)) {
|
||||
return(app_state$options)
|
||||
}
|
||||
|
||||
return(.globals$options)
|
||||
}
|
||||
|
||||
|
||||
# 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() {
|
||||
# shiny app object. This function "consumes" the options when the shinyApp
|
||||
# object is created, so the options won't affect another app that is created
|
||||
# later.
|
||||
#
|
||||
# ==== Example ====
|
||||
# shinyOptions(bookmarkStore = 1234)
|
||||
# # This now returns 1234.
|
||||
# getShinyOption("bookmarkStore")
|
||||
#
|
||||
# # Creating the app captures the bookmarkStore option and clears it.
|
||||
# s <- shinyApp(
|
||||
# fluidPage(verbatimTextOutput("txt")),
|
||||
# function(input, output) {
|
||||
# output$txt <- renderText(getShinyOption("bookmarkStore"))
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# # This now returns NULL.
|
||||
# getShinyOption("bookmarkStore")
|
||||
#
|
||||
# When running the app, the app will display "1234"
|
||||
# runApp(s)
|
||||
#
|
||||
# # After quitting the app, this still returns NULL.
|
||||
# getShinyOption("bookmarkStore")
|
||||
# ==================
|
||||
#
|
||||
# If another app had been created after s was created, but before s was run,
|
||||
# then it would capture the value of "bookmarkStore" at the time of creation.
|
||||
captureAppOptions <- function() {
|
||||
options <- list(
|
||||
appDir = getwd(),
|
||||
bookmarkStore = getShinyOption("bookmarkStore")
|
||||
@@ -74,9 +254,9 @@ consumeAppOptions <- function() {
|
||||
options
|
||||
}
|
||||
|
||||
# Do the inverse of consumeAppOptions. This should be called once the app is
|
||||
# Do the inverse of captureAppOptions. This should be called once the app is
|
||||
# started.
|
||||
unconsumeAppOptions <- function(options) {
|
||||
applyCapturedAppOptions <- function(options) {
|
||||
if (!is.null(options)) {
|
||||
do.call(shinyOptions, options)
|
||||
}
|
||||
|
||||
@@ -3,40 +3,41 @@
|
||||
#' Create a Shiny app object
|
||||
#'
|
||||
#' These functions create Shiny app objects from either an explicit UI/server
|
||||
#' pair (\code{shinyApp}), or by passing the path of a directory that contains a
|
||||
#' Shiny app (\code{shinyAppDir}). You generally shouldn't need to use these
|
||||
#' functions to create/run applications; they are intended for interoperability
|
||||
#' purposes, such as embedding Shiny apps inside a \pkg{knitr} document.
|
||||
#' pair (`shinyApp`), or by passing the path of a directory that contains a
|
||||
#' Shiny app (`shinyAppDir`).
|
||||
#'
|
||||
#' Normally when this function is used at the R console, the Shiny app object is
|
||||
#' automatically passed to the \code{print()} function, which runs the app. If
|
||||
#' automatically passed to the `print()` function, which runs the app. If
|
||||
#' this is called in the middle of a function, the value will not be passed to
|
||||
#' \code{print()} and the app will not be run. To make the app run, pass the app
|
||||
#' object to \code{print()} or \code{\link{runApp}()}.
|
||||
#' `print()` and the app will not be run. To make the app run, pass the app
|
||||
#' object to `print()` or [runApp()].
|
||||
#'
|
||||
#' @param ui The UI definition of the app (for example, a call to
|
||||
#' \code{fluidPage()} with nested controls)
|
||||
#' @param server A server function
|
||||
#' `fluidPage()` with nested controls).
|
||||
#'
|
||||
#' If bookmarking is enabled (see `enableBookmarking`), this must be
|
||||
#' a single argument function that returns the UI definition.
|
||||
#' @param server A function with three parameters: `input`, `output`, and
|
||||
#' `session`. The function is called once for each session ensuring that each
|
||||
#' app is independent.
|
||||
#' @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 \code{runApp} call
|
||||
#' This is only needed for `shinyAppObj`, since in the `shinyAppDir`
|
||||
#' case, a `global.R` file can be used for this purpose.
|
||||
#' @param options Named options that should be passed to the `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
|
||||
#' "display.mode" and "test.mode"). You can also specify `width` and
|
||||
#' `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
|
||||
#' @param uiPattern A regular expression that will be applied to each `GET`
|
||||
#' request to determine whether the `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.
|
||||
#' @param enableBookmarking Can be one of `"url"`, `"server"`, or
|
||||
#' `"disable"`. The default value, `NULL`, will respect the setting from
|
||||
#' any previous calls to [enableBookmarking()]. See [enableBookmarking()]
|
||||
#' for more information on bookmarking your app.
|
||||
#' @return An object that represents the app. Printing the object or passing it
|
||||
#' to \code{\link{runApp}} will run the app.
|
||||
#' to [runApp()] will run the app.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
@@ -70,10 +71,10 @@
|
||||
#' runApp(app)
|
||||
#' }
|
||||
#' @export
|
||||
shinyApp <- function(ui=NULL, server=NULL, onStart=NULL, options=list(),
|
||||
shinyApp <- function(ui, server, onStart=NULL, options=list(),
|
||||
uiPattern="/", enableBookmarking=NULL) {
|
||||
if (is.null(server)) {
|
||||
stop("`server` missing from shinyApp")
|
||||
if (!is.function(server)) {
|
||||
stop("`server` must be a function", call. = FALSE)
|
||||
}
|
||||
|
||||
# Ensure that the entire path is a match
|
||||
@@ -92,8 +93,7 @@ shinyApp <- function(ui=NULL, server=NULL, onStart=NULL, options=list(),
|
||||
|
||||
# Store the appDir and bookmarking-related options, so that we can read them
|
||||
# from within the app.
|
||||
shinyOptions(appDir = getwd())
|
||||
appOptions <- consumeAppOptions()
|
||||
appOptions <- captureAppOptions()
|
||||
|
||||
structure(
|
||||
list(
|
||||
@@ -141,10 +141,21 @@ shinyAppFile <- function(appFile, options=list()) {
|
||||
|
||||
# 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.
|
||||
# appDir must be a normalized (absolute) path, not a relative one
|
||||
shinyAppDir_serverR <- function(appDir, options=list()) {
|
||||
# Most of the complexity here comes from needing to hot-reload if the .R files
|
||||
# change on disk, or are created, or are removed.
|
||||
|
||||
# In an upcoming version of shiny, this option will go away.
|
||||
if (getOption("shiny.autoload.r", TRUE)) {
|
||||
# Create a child env which contains all the helpers and will be the shared parent
|
||||
# of the ui.R and server.R load.
|
||||
sharedEnv <- new.env(parent = globalenv())
|
||||
} else {
|
||||
# old behavior
|
||||
sharedEnv <- globalenv()
|
||||
}
|
||||
|
||||
# uiHandlerSource is a function that returns an HTTP handler for serving up
|
||||
# ui.R as a webpage. The "cachedFuncWithFile" call makes sure that the closure
|
||||
# we're creating here only gets executed when ui.R's contents change.
|
||||
@@ -155,7 +166,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, envir = new.env(parent = globalenv()))
|
||||
ui <- sourceUTF8(uiR, envir = new.env(parent = sharedEnv))
|
||||
if (!is.null(.globals$ui)) {
|
||||
ui <- .globals$ui[[1]]
|
||||
}
|
||||
@@ -170,7 +181,14 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
|
||||
}
|
||||
|
||||
wwwDir <- file.path.ci(appDir, "www")
|
||||
if (dirExists(wwwDir)) {
|
||||
staticPaths <- list("/" = staticPath(wwwDir, indexhtml = FALSE, fallthrough = TRUE))
|
||||
} else {
|
||||
staticPaths <- list()
|
||||
}
|
||||
|
||||
fallbackWWWDir <- system.file("www-dir", package = "shiny")
|
||||
|
||||
serverSource <- cachedFuncWithFile(appDir, "server.R", case.sensitive = FALSE,
|
||||
function(serverR) {
|
||||
# If server.R contains a call to shinyServer (which sets .globals$server),
|
||||
@@ -178,7 +196,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
|
||||
# server.R.
|
||||
.globals$server <- NULL
|
||||
on.exit(.globals$server <- NULL, add = TRUE)
|
||||
result <- sourceUTF8(serverR, envir = new.env(parent = globalenv()))
|
||||
result <- sourceUTF8(serverR, envir = new.env(parent = sharedEnv))
|
||||
if (!is.null(.globals$server)) {
|
||||
result <- .globals$server[[1]]
|
||||
}
|
||||
@@ -208,18 +226,35 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
|
||||
onStart <- function() {
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
# TODO: we should support hot reloading on global.R and R/*.R changes.
|
||||
if (getOption("shiny.autoload.r", TRUE)) {
|
||||
loadSupport(appDir, renv=sharedEnv, globalrenv=globalenv())
|
||||
} else {
|
||||
if (file.exists(file.path.ci(appDir, "global.R")))
|
||||
sourceUTF8(file.path.ci(appDir, "global.R"))
|
||||
}
|
||||
monitorHandle <<- initAutoReloadMonitor(appDir)
|
||||
if (file.exists(file.path.ci(appDir, "global.R")))
|
||||
sourceUTF8(file.path.ci(appDir, "global.R"))
|
||||
}
|
||||
onStop <- function() {
|
||||
setwd(oldwd)
|
||||
monitorHandle()
|
||||
monitorHandle <<- NULL
|
||||
# It is possible that while calling appObj()$onStart() or loadingSupport, an error occured
|
||||
# This will cause `onStop` to be called.
|
||||
# The `oldwd` will exist, but `monitorHandle` is not a function yet.
|
||||
if (is.function(monitorHandle)) {
|
||||
monitorHandle()
|
||||
monitorHandle <<- NULL
|
||||
}
|
||||
}
|
||||
|
||||
structure(
|
||||
list(
|
||||
staticPaths = staticPaths,
|
||||
# Even though the wwwDir is handled as a static path, we need to include
|
||||
# it here to be handled by R as well. This is because the special case
|
||||
# of index.html: it is specifically not handled as a staticPath for
|
||||
# reasons explained above, but if someone does want to serve up an
|
||||
# index.html, we need to handle it, and we do it by using the
|
||||
# staticHandler in the R code path. (#2380)
|
||||
httpHandler = joinHandlers(c(uiHandler, wwwDir, fallbackWWWDir)),
|
||||
serverFuncSource = serverFuncSource,
|
||||
onStart = onStart,
|
||||
@@ -253,9 +288,11 @@ initAutoReloadMonitor <- function(dir) {
|
||||
".*\\.(r|html?|js|css|png|jpe?g|gif)$")
|
||||
|
||||
lastValue <- NULL
|
||||
obs <- observe({
|
||||
files <- sort(list.files(dir, pattern = filePattern, recursive = TRUE,
|
||||
ignore.case = TRUE))
|
||||
observeLabel <- paste0("File Auto-Reload - '", basename(dir), "'")
|
||||
obs <- observe(label = observeLabel, {
|
||||
files <- sort_c(
|
||||
list.files(dir, pattern = filePattern, recursive = TRUE, ignore.case = TRUE)
|
||||
)
|
||||
times <- file.info(files)$mtime
|
||||
names(times) <- files
|
||||
|
||||
@@ -265,19 +302,84 @@ initAutoReloadMonitor <- function(dir) {
|
||||
} else if (!identical(lastValue, times)) {
|
||||
# We've changed!
|
||||
lastValue <<- times
|
||||
for (session in appsByToken$values()) {
|
||||
session$reload()
|
||||
}
|
||||
autoReloadCallbacks$invoke()
|
||||
}
|
||||
|
||||
invalidateLater(getOption("shiny.autoreload.interval", 500))
|
||||
})
|
||||
|
||||
onStop(obs$destroy)
|
||||
|
||||
obs$destroy
|
||||
}
|
||||
|
||||
#' Load an app's supporting R files
|
||||
#'
|
||||
#' Loads all of the supporting R files of a Shiny application. Specifically,
|
||||
#' this function loads any top-level supporting `.R` files in the `R/` directory
|
||||
#' adjacent to the `app.R`/`server.R`/`ui.R` files.
|
||||
#'
|
||||
#' Since Shiny 1.5.0, this function is called by default when running an
|
||||
#' application. If it causes problems, there are two ways to opt out. You can
|
||||
#' either place a file named `_disable_autoload.R` in your R/ directory, or
|
||||
#' set `options(shiny.autoload.r=FALSE)`. If you set this option, it will
|
||||
#' affect any application that runs later in the same R session, potentially
|
||||
#' breaking it, so after running your application, you should unset option with
|
||||
#' `options(shiny.autoload.r=NULL)`
|
||||
#'
|
||||
#' @details The files are sourced in alphabetical order (as determined by
|
||||
#' [list.files]). `global.R` is evaluated before the supporting R files in the
|
||||
#' `R/` directory.
|
||||
#' @param appDir The application directory. If `appDir` is `NULL` or
|
||||
#' not supplied, the nearest enclosing directory that is a Shiny app, starting
|
||||
#' with the current directory, is used.
|
||||
#' @param renv The environmeny in which the files in the `R/` directory should
|
||||
#' be evaluated.
|
||||
#' @param globalrenv The environment in which `global.R` should be evaluated. If
|
||||
#' `NULL`, `global.R` will not be evaluated at all.
|
||||
#' @export
|
||||
loadSupport <- function(appDir=NULL, renv=new.env(parent=globalenv()), globalrenv=globalenv()){
|
||||
require(shiny)
|
||||
|
||||
if (is.null(appDir)) {
|
||||
appDir <- findEnclosingApp(".")
|
||||
}
|
||||
|
||||
if (!is.null(globalrenv)){
|
||||
# Evaluate global.R, if it exists.
|
||||
globalPath <- file.path.ci(appDir, "global.R")
|
||||
if (file.exists(globalPath)){
|
||||
withr::with_dir(appDir, {
|
||||
sourceUTF8(basename(globalPath), envir=globalrenv)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
helpersDir <- file.path(appDir, "R")
|
||||
|
||||
disabled <- list.files(helpersDir, pattern="^_disable_autoload\\.r$", recursive=FALSE, ignore.case=TRUE)
|
||||
if (length(disabled) > 0){
|
||||
return(invisible(renv))
|
||||
}
|
||||
|
||||
helpers <- list.files(helpersDir, pattern="\\.[rR]$", recursive=FALSE, full.names=TRUE)
|
||||
# Ensure files in R/ are sorted according to the 'C' locale before sourcing.
|
||||
# This convention is based on the default for packages. For details, see:
|
||||
# https://cran.r-project.org/doc/manuals/r-release/R-exts.html#The-DESCRIPTION-file
|
||||
helpers <- sort_c(helpers)
|
||||
helpers <- normalizePath(helpers)
|
||||
|
||||
withr::with_dir(appDir, {
|
||||
lapply(helpers, sourceUTF8, envir=renv)
|
||||
})
|
||||
|
||||
invisible(renv)
|
||||
}
|
||||
|
||||
# This reads in an app dir for a single-file application (e.g. app.R), and
|
||||
# returns a shiny.appobj.
|
||||
# appDir must be a normalized (absolute) path, not a relative one
|
||||
shinyAppDir_appR <- function(fileName, appDir, options=list())
|
||||
{
|
||||
fullpath <- file.path.ci(appDir, fileName)
|
||||
@@ -287,12 +389,25 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
|
||||
# app.R has changed, it'll re-source the file and return the result.
|
||||
appObj <- cachedFuncWithFile(appDir, fileName, case.sensitive = FALSE,
|
||||
function(appR) {
|
||||
result <- sourceUTF8(fullpath, envir = new.env(parent = globalenv()))
|
||||
wasDir <- setwd(appDir)
|
||||
on.exit(setwd(wasDir))
|
||||
|
||||
# TODO: we should support hot reloading on R/*.R changes.
|
||||
# In an upcoming version of shiny, this option will go away.
|
||||
if (getOption("shiny.autoload.r", TRUE)) {
|
||||
# Create a child env which contains all the helpers and will be the shared parent
|
||||
# of the ui.R and server.R load.
|
||||
sharedEnv <- new.env(parent = globalenv())
|
||||
loadSupport(appDir, renv=sharedEnv, globalrenv=NULL)
|
||||
} else {
|
||||
sharedEnv <- globalenv()
|
||||
}
|
||||
result <- sourceUTF8(fullpath, envir = new.env(parent = sharedEnv))
|
||||
|
||||
if (!is.shiny.appobj(result))
|
||||
stop("app.R did not return a shiny.appobj object.")
|
||||
|
||||
unconsumeAppOptions(result$appOptions)
|
||||
applyCapturedAppOptions(result$appOptions)
|
||||
|
||||
return(result)
|
||||
}
|
||||
@@ -309,6 +424,20 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
|
||||
}
|
||||
|
||||
wwwDir <- file.path.ci(appDir, "www")
|
||||
if (dirExists(wwwDir)) {
|
||||
# wwwDir is a static path served by httpuv. It does _not_ serve up
|
||||
# index.html, for two reasons. (1) It's possible that the user's
|
||||
# www/index.html file is not actually used as the index, but as a template
|
||||
# that gets processed before being sent; and (2) the index content may be
|
||||
# modified by the hosting environment (as in SockJSAdapter.R).
|
||||
#
|
||||
# The call to staticPath normalizes the path, so that if the working dir
|
||||
# later changes, it will continue to point to the right place.
|
||||
staticPaths <- list("/" = staticPath(wwwDir, indexhtml = FALSE, fallthrough = TRUE))
|
||||
} else {
|
||||
staticPaths <- list()
|
||||
}
|
||||
|
||||
fallbackWWWDir <- system.file("www-dir", package = "shiny")
|
||||
|
||||
oldwd <- NULL
|
||||
@@ -316,48 +445,76 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
|
||||
onStart <- function() {
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
monitorHandle <<- initAutoReloadMonitor(appDir)
|
||||
if (!is.null(appObj()$onStart)) appObj()$onStart()
|
||||
monitorHandle <<- initAutoReloadMonitor(appDir)
|
||||
invisible()
|
||||
}
|
||||
onStop <- function() {
|
||||
setwd(oldwd)
|
||||
monitorHandle()
|
||||
monitorHandle <<- NULL
|
||||
# It is possible that while calling appObj()$onStart() or loadingSupport, an error occured
|
||||
# This will cause `onStop` to be called.
|
||||
# The `oldwd` will exist, but `monitorHandle` is not a function yet.
|
||||
if (is.function(monitorHandle)) {
|
||||
monitorHandle()
|
||||
monitorHandle <<- NULL
|
||||
}
|
||||
}
|
||||
|
||||
appObjOptions <- appObj()$options
|
||||
|
||||
structure(
|
||||
list(
|
||||
# fallbackWWWDir is _not_ listed in staticPaths, because it needs to
|
||||
# come after the uiHandler. It also does not need to be fast, since it
|
||||
# should rarely be hit. The order is wwwDir (in staticPaths), then
|
||||
# uiHandler, then falbackWWWDir (which is served up by the R
|
||||
# staticHandler function).
|
||||
staticPaths = staticPaths,
|
||||
# Even though the wwwDir is handled as a static path, we need to include
|
||||
# it here to be handled by R as well. This is because the special case
|
||||
# of index.html: it is specifically not handled as a staticPath for
|
||||
# reasons explained above, but if someone does want to serve up an
|
||||
# index.html, we need to handle it, and we do it by using the
|
||||
# staticHandler in the R code path. (#2380)
|
||||
httpHandler = joinHandlers(c(dynHttpHandler, wwwDir, fallbackWWWDir)),
|
||||
serverFuncSource = dynServerFuncSource,
|
||||
onStart = onStart,
|
||||
onStop = onStop,
|
||||
options = options
|
||||
options = joinOptions(appObjOptions, options)
|
||||
),
|
||||
class = "shiny.appobj"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' Shiny App object
|
||||
#'
|
||||
#' Internal methods for the `shiny.appobj` S3 class.
|
||||
#'
|
||||
#' @keywords internal
|
||||
#' @name shiny.appobj
|
||||
NULL
|
||||
|
||||
#' @rdname shiny.appobj
|
||||
#' @param x Object to convert to a Shiny app.
|
||||
#' @export
|
||||
as.shiny.appobj <- function(x) {
|
||||
UseMethod("as.shiny.appobj", x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @rdname shiny.appobj
|
||||
#' @export
|
||||
as.shiny.appobj.shiny.appobj <- function(x) {
|
||||
x
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @rdname shiny.appobj
|
||||
#' @export
|
||||
as.shiny.appobj.list <- function(x) {
|
||||
shinyApp(ui = x$ui, server = x$server)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @rdname shiny.appobj
|
||||
#' @export
|
||||
as.shiny.appobj.character <- function(x) {
|
||||
if (identical(tolower(tools::file_ext(x)), "r"))
|
||||
@@ -366,28 +523,35 @@ as.shiny.appobj.character <- function(x) {
|
||||
shinyAppDir(x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @rdname shiny.appobj
|
||||
#' @export
|
||||
is.shiny.appobj <- function(x) {
|
||||
inherits(x, "shiny.appobj")
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param ... Additional parameters to be passed to print.
|
||||
#' @rdname shiny.appobj
|
||||
#' @param ... Ignored.
|
||||
#' @export
|
||||
print.shiny.appobj <- function(x, ...) {
|
||||
opts <- x$options %OR% list()
|
||||
opts <- opts[names(opts) %in%
|
||||
c("port", "launch.browser", "host", "quiet",
|
||||
"display.mode", "test.mode")]
|
||||
|
||||
# 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)
|
||||
runApp(x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
# Joins two options objects (i.e. the `options` argument to shinyApp(),
|
||||
# shinyAppDir(), etc.). The values in `b` should take precedence over the values
|
||||
# in `a`. Given the current options available, it is safe to throw away any
|
||||
# values in `a` that are provided in `b`. But in the future, if new options are
|
||||
# introduced that need to be combined in some way instead of simply overwritten,
|
||||
# then this will be the place to do it. See the implementations of
|
||||
# print.shiny.appobj() and runApp() (for the latter, look specifically for
|
||||
# "findVal()") to determine the set of possible options.
|
||||
joinOptions <- function(a, b) {
|
||||
stopifnot(is.null(a) || is.list(a))
|
||||
stopifnot(is.null(b) || is.list(b))
|
||||
|
||||
mergeVectors(a, b)
|
||||
}
|
||||
|
||||
#' @rdname shiny.appobj
|
||||
#' @method as.tags shiny.appobj
|
||||
#' @export
|
||||
as.tags.shiny.appobj <- function(x, ...) {
|
||||
@@ -414,87 +578,3 @@ deferredIFrame <- function(path, width, height) {
|
||||
class = "shiny-frame shiny-frame-deferred"
|
||||
)
|
||||
}
|
||||
|
||||
#' Knitr S3 methods
|
||||
#'
|
||||
#' These S3 methods are necessary to help Shiny applications and UI chunks embed
|
||||
#' themselves in knitr/rmarkdown documents.
|
||||
#'
|
||||
#' @name knitr_methods
|
||||
#' @param x Object to knit_print
|
||||
#' @param ... Additional knit_print arguments
|
||||
NULL
|
||||
|
||||
# If there's an R Markdown runtime option set but it isn't set to Shiny, then
|
||||
# return a warning indicating the runtime is inappropriate for this object.
|
||||
# Returns NULL in all other cases.
|
||||
shiny_rmd_warning <- function() {
|
||||
runtime <- knitr::opts_knit$get("rmarkdown.runtime")
|
||||
if (!is.null(runtime) && runtime != "shiny")
|
||||
# note that the RStudio IDE checks for this specific string to detect Shiny
|
||||
# applications in static document
|
||||
list(structure(
|
||||
"Shiny application in a static R Markdown document",
|
||||
class = "rmd_warning"))
|
||||
else
|
||||
NULL
|
||||
}
|
||||
|
||||
#' @rdname knitr_methods
|
||||
#' @export
|
||||
knit_print.shiny.appobj <- function(x, ...) {
|
||||
opts <- x$options %OR% list()
|
||||
width <- if (is.null(opts$width)) "100%" else opts$width
|
||||
height <- if (is.null(opts$height)) "400" else opts$height
|
||||
|
||||
runtime <- knitr::opts_knit$get("rmarkdown.runtime")
|
||||
if (!is.null(runtime) && runtime != "shiny") {
|
||||
# If not rendering to a Shiny document, create a box exactly the same
|
||||
# dimensions as the Shiny app would have had (so the document continues to
|
||||
# flow as it would have with the app), and display a diagnostic message
|
||||
width <- validateCssUnit(width)
|
||||
height <- validateCssUnit(height)
|
||||
output <- tags$div(
|
||||
style=paste("width:", width, "; height:", height, "; text-align: center;",
|
||||
"box-sizing: border-box;", "-moz-box-sizing: border-box;",
|
||||
"-webkit-box-sizing: border-box;"),
|
||||
class="muted well",
|
||||
"Shiny applications not supported in static R Markdown documents")
|
||||
}
|
||||
else {
|
||||
path <- addSubApp(x)
|
||||
output <- deferredIFrame(path, width, height)
|
||||
}
|
||||
|
||||
# If embedded Shiny apps ever have JS/CSS dependencies (like pym.js) we'll
|
||||
# need to grab those and put them in meta, like in knit_print.shiny.tag. But
|
||||
# for now it's not an issue, so just return the HTML and warning.
|
||||
|
||||
knitr::asis_output(htmlPreserve(format(output, indent=FALSE)),
|
||||
meta = shiny_rmd_warning(), cacheable = FALSE)
|
||||
}
|
||||
|
||||
# Let us use a nicer syntax in knitr chunks than literally
|
||||
# calling output$value <- renderFoo(...) and fooOutput().
|
||||
#' @rdname knitr_methods
|
||||
#' @param inline Whether the object is printed inline.
|
||||
#' @export
|
||||
knit_print.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
x <- htmltools::as.tags(x, inline = inline)
|
||||
output <- knitr::knit_print(tagList(x))
|
||||
attr(output, "knit_cacheable") <- FALSE
|
||||
attr(output, "knit_meta") <- append(attr(output, "knit_meta"),
|
||||
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)
|
||||
}
|
||||
107
R/shinyui.R
107
R/shinyui.R
@@ -4,9 +4,9 @@ NULL
|
||||
#' Load the MathJax library and typeset math expressions
|
||||
#'
|
||||
#' This function adds MathJax to the page and typeset the math expressions (if
|
||||
#' found) in the content \code{...}. It only needs to be called once in an app
|
||||
#' unless the content is rendered \emph{after} the page is loaded, e.g. via
|
||||
#' \code{\link{renderUI}}, in which case we have to call it explicitly every
|
||||
#' found) in the content `...`. It only needs to be called once in an app
|
||||
#' unless the content is rendered *after* the page is loaded, e.g. via
|
||||
#' [renderUI()], in which case we have to call it explicitly every
|
||||
#' time we write math expressions to the output.
|
||||
#' @param ... any HTML elements to apply MathJax to
|
||||
#' @export
|
||||
@@ -24,7 +24,9 @@ withMathJax <- function(...) {
|
||||
)
|
||||
}
|
||||
|
||||
renderPage <- function(ui, connection, showcase=0, testMode=FALSE) {
|
||||
renderPage <- function(ui, showcase=0, testMode=FALSE) {
|
||||
lang <- getLang(ui)
|
||||
|
||||
# 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")) {
|
||||
@@ -38,27 +40,84 @@ renderPage <- function(ui, connection, showcase=0, testMode=FALSE) {
|
||||
# Put the body into the default template
|
||||
ui <- htmlTemplate(
|
||||
system.file("template", "default.html", package = "shiny"),
|
||||
body = ui
|
||||
lang = lang,
|
||||
body = ui,
|
||||
# this template is a complete HTML document
|
||||
document_ = TRUE
|
||||
)
|
||||
}
|
||||
|
||||
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")
|
||||
jquery <- function() {
|
||||
version <- getOption("shiny.jquery.version", 3)
|
||||
if (version == 3) {
|
||||
return(htmlDependency(
|
||||
"jquery", "3.5.1",
|
||||
c(href = "shared"),
|
||||
script = "jquery.min.js"
|
||||
))
|
||||
}
|
||||
if (version == 1) {
|
||||
return(htmlDependency(
|
||||
"jquery", "1.12.4",
|
||||
c(href = "shared/legacy"),
|
||||
script = "jquery.min.js"
|
||||
))
|
||||
}
|
||||
stop("Unsupported version of jQuery: ", version)
|
||||
}
|
||||
|
||||
shiny_deps <- c(
|
||||
list(jquery()),
|
||||
shinyDependencies()
|
||||
)
|
||||
|
||||
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")
|
||||
c(href="shared"), script = "shiny-testmode.js")
|
||||
}
|
||||
|
||||
html <- renderDocument(ui, shiny_deps, processDep = createWebDependency)
|
||||
writeUTF8(html, con = connection)
|
||||
enc2utf8(paste(collapse = "\n", html))
|
||||
}
|
||||
|
||||
shinyDependencies <- function() {
|
||||
version <- utils::packageVersion("shiny")
|
||||
list(
|
||||
bootstraplib::bs_dependency_defer(shinyDependencyCSS),
|
||||
htmlDependency(
|
||||
name = "shiny-javascript",
|
||||
version = version,
|
||||
src = c(href = "shared"),
|
||||
script = if (getOption("shiny.minified", TRUE)) "shiny.min.js" else "shiny.js"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
shinyDependencyCSS <- function(theme) {
|
||||
version <- utils::packageVersion("shiny")
|
||||
|
||||
if (!is_bs_theme(theme)) {
|
||||
return(htmlDependency(
|
||||
name = "shiny-css",
|
||||
version = version,
|
||||
src = c(href = "shared"),
|
||||
stylesheet = "shiny.min.css"
|
||||
))
|
||||
}
|
||||
|
||||
scss_home <- system.file("www/shared/shiny_scss", package = "shiny")
|
||||
scss_files <- file.path(scss_home, c("bootstrap.scss", "shiny.scss"))
|
||||
scss_files <- lapply(scss_files, sass::sass_file)
|
||||
|
||||
bootstraplib::bs_dependency(
|
||||
input = scss_files,
|
||||
theme = theme,
|
||||
name = "shiny-sass",
|
||||
version = version,
|
||||
cache_key_extra = version
|
||||
)
|
||||
}
|
||||
|
||||
#' Create a Shiny UI handler
|
||||
@@ -71,6 +130,7 @@ renderPage <- function(ui, connection, showcase=0, testMode=FALSE) {
|
||||
#'
|
||||
#' @param ui A user interace definition
|
||||
#' @return The user interface definition, without modifications or side effects.
|
||||
#' @keywords internal
|
||||
#' @export
|
||||
shinyUI <- function(ui) {
|
||||
.globals$ui <- list(ui)
|
||||
@@ -81,16 +141,18 @@ uiHttpHandler <- function(ui, uiPattern = "^/$") {
|
||||
|
||||
force(ui)
|
||||
|
||||
allowed_methods <- "GET"
|
||||
if (is.function(ui)) {
|
||||
allowed_methods <- attr(ui, "http_methods_supported", exact = TRUE) %OR% allowed_methods
|
||||
}
|
||||
|
||||
function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
if (!isTRUE(req$REQUEST_METHOD %in% allowed_methods))
|
||||
return(NULL)
|
||||
|
||||
if (!isTRUE(grepl(uiPattern, req$PATH_INFO)))
|
||||
return(NULL)
|
||||
|
||||
textConn <- file(open = "w+")
|
||||
on.exit(close(textConn))
|
||||
|
||||
showcaseMode <- .globals$showcaseDefault
|
||||
if (.globals$showcaseOverride) {
|
||||
mode <- showcaseModeOfReq(req)
|
||||
@@ -98,7 +160,7 @@ uiHttpHandler <- function(ui, uiPattern = "^/$") {
|
||||
showcaseMode <- mode
|
||||
}
|
||||
|
||||
testMode <- .globals$testMode %OR% FALSE
|
||||
testMode <- getShinyOption("testmode", default = FALSE)
|
||||
|
||||
# Create a restore context using query string
|
||||
bookmarkStore <- getShinyOption("bookmarkStore", default = "disable")
|
||||
@@ -130,8 +192,11 @@ uiHttpHandler <- function(ui, uiPattern = "^/$") {
|
||||
if (is.null(uiValue))
|
||||
return(NULL)
|
||||
|
||||
renderPage(uiValue, textConn, showcaseMode, testMode)
|
||||
html <- paste(readLines(textConn, encoding = 'UTF-8'), collapse='\n')
|
||||
return(httpResponse(200, content=enc2utf8(html)))
|
||||
if (inherits(uiValue, "httpResponse")) {
|
||||
return(uiValue)
|
||||
} else {
|
||||
html <- renderPage(uiValue, showcaseMode, testMode)
|
||||
return(httpResponse(200, content=html))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ utils::globalVariables('func')
|
||||
|
||||
#' Mark a function as a render function
|
||||
#'
|
||||
#' Should be called by implementers of \code{renderXXX} functions in order to
|
||||
#' Should be called by implementers of `renderXXX` functions in order to
|
||||
#' mark their return values as Shiny render functions, and to provide a hint to
|
||||
#' Shiny regarding what UI function is most commonly used with this type of
|
||||
#' render function. This can be used in R Markdown documents to create complete
|
||||
@@ -12,13 +12,13 @@ utils::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
|
||||
#' @param outputArgs A list of arguments to pass to the `uiFunc`. Render
|
||||
#' functions should include `outputArgs = list()` in their own parameter
|
||||
#' list, and pass through the value to `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.
|
||||
#' @return The `renderFunc` function, with annotations.
|
||||
#' @export
|
||||
markRenderFunction <- function(uiFunc, renderFunc, outputArgs = list()) {
|
||||
# a mutable object that keeps track of whether `useRenderFunction` has been
|
||||
@@ -52,27 +52,32 @@ markRenderFunction <- function(uiFunc, renderFunc, outputArgs = list()) {
|
||||
hasExecuted = hasExecuted)
|
||||
}
|
||||
|
||||
#' @export
|
||||
print.shiny.render.function <- function(x, ...) {
|
||||
cat_line("<shiny.render.function>")
|
||||
}
|
||||
|
||||
#' 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},
|
||||
#' @param transform A function that takes four arguments: `value`,
|
||||
#' `session`, `name`, and `...` (for future-proofing). This
|
||||
#' function will be invoked each time a value is returned from `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
|
||||
#' @param outputArgs A list of arguments to pass to the `outputFunc`.
|
||||
#' Render functions should include `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.
|
||||
#' `output` slot.
|
||||
#'
|
||||
#' @export
|
||||
createRenderFunction <- function(
|
||||
@@ -165,37 +170,40 @@ markOutputAttrs <- function(renderFunc, snapshotExclude = NULL,
|
||||
|
||||
#' Image file output
|
||||
#'
|
||||
#' Renders a reactive image that is suitable for assigning to an \code{output}
|
||||
#' Renders a reactive image that is suitable for assigning to an `output`
|
||||
#' slot.
|
||||
#'
|
||||
#' The expression \code{expr} must return a list containing the attributes for
|
||||
#' the \code{img} object on the client web page. For the image to display,
|
||||
#' properly, the list must have at least one entry, \code{src}, which is the
|
||||
#' path to the image file. It may also useful to have a \code{contentType}
|
||||
#' The expression `expr` must return a list containing the attributes for
|
||||
#' the `img` object on the client web page. For the image to display,
|
||||
#' properly, the list must have at least one entry, `src`, which is the
|
||||
#' path to the image file. It may also useful to have a `contentType`
|
||||
#' entry specifying the MIME type of the image. If one is not provided,
|
||||
#' \code{renderImage} will try to autodetect the type, based on the file
|
||||
#' `renderImage` will try to autodetect the type, based on the file
|
||||
#' extension.
|
||||
#'
|
||||
#' Other elements such as \code{width}, \code{height}, \code{class}, and
|
||||
#' \code{alt}, can also be added to the list, and they will be used as
|
||||
#' attributes in the \code{img} object.
|
||||
#' Other elements such as `width`, `height`, `class`, and
|
||||
#' `alt`, can also be added to the list, and they will be used as
|
||||
#' attributes in the `img` object.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-image-output}.
|
||||
#' The corresponding HTML output tag should be `div` or `img` and have
|
||||
#' the CSS class name `shiny-image-output`.
|
||||
#'
|
||||
#' @seealso For more details on how the images are generated, and how to control
|
||||
#' the output, see \code{\link{plotPNG}}.
|
||||
#' the output, see [plotPNG()].
|
||||
#'
|
||||
#' @param expr An expression that returns a list.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' @param env The environment in which to evaluate `expr`.
|
||||
#' @param quoted Is `expr` a quoted expression (with `quote()`)? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param deleteFile Should the file in \code{func()$src} be deleted after
|
||||
#' @param deleteFile Should the file in `func()$src` be deleted after
|
||||
#' 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}.
|
||||
#' temp file generated within `func`, then this should be `TRUE`;
|
||||
#' if the image is not a temp file, this should be `FALSE`. (For backward
|
||||
#' compatibility reasons, if this argument is missing, a warning will be
|
||||
#' emitted, and if the file is in the temp directory it will be deleted. In
|
||||
#' the future, this warning will become an error.)
|
||||
#' @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
|
||||
#' call to [imageOutput()] when `renderImage` is used in an
|
||||
#' interactive R Markdown document.
|
||||
#' @export
|
||||
#'
|
||||
@@ -266,15 +274,53 @@ markOutputAttrs <- function(renderFunc, snapshotExclude = NULL,
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
deleteFile=TRUE, outputArgs=list()) {
|
||||
deleteFile, outputArgs=list()) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
# missing() must be used directly within the function with the given arg
|
||||
if (missing(deleteFile)) {
|
||||
deleteFile <- NULL
|
||||
}
|
||||
|
||||
# Tracks whether we've reported the `deleteFile` warning yet; we don't want to
|
||||
# do it on every invalidation (though we will end up doing it at least once
|
||||
# per output per session).
|
||||
warned <- FALSE
|
||||
|
||||
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))
|
||||
shouldDelete <- deleteFile
|
||||
|
||||
# jcheng 2020-05-08
|
||||
#
|
||||
# Until Shiny 1.5.0, the default for deleteFile was, incredibly, TRUE.
|
||||
# Changing it to default to FALSE might cause existing Shiny apps to pile
|
||||
# up images in their temp directory (for long lived R processes). Not
|
||||
# having a default (requiring explicit value) is the right long-term move,
|
||||
# but would break today's apps.
|
||||
#
|
||||
# Compromise we decided on was to eventually require TRUE/FALSE, but for
|
||||
# now, change the default behavior to only delete temp files; and emit a
|
||||
# warning encouraging people to not rely on the default.
|
||||
if (is.null(shouldDelete)) {
|
||||
shouldDelete <- isTRUE(try(silent = TRUE,
|
||||
file.exists(imageinfo$src) && isTemp(imageinfo$src, mustExist = TRUE)
|
||||
))
|
||||
|
||||
if (!warned) {
|
||||
warned <<- TRUE
|
||||
warning("The renderImage output named '",
|
||||
getCurrentOutputInfo()$name,
|
||||
"' is missing the deleteFile argument; as of Shiny 1.5.0, you must ",
|
||||
"use deleteFile=TRUE or deleteFile=FALSE. (This warning will ",
|
||||
"become an error in a future version of Shiny.)",
|
||||
call. = FALSE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDelete) {
|
||||
on.exit(unlink(imageinfo$src), add = TRUE)
|
||||
}
|
||||
|
||||
# If contentType not specified, autodetect based on extension
|
||||
@@ -290,36 +336,71 @@ renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
imageOutput, outputArgs)
|
||||
}
|
||||
|
||||
# TODO: If we ever take a dependency on fs, it'd be great to replace this with
|
||||
# fs::path_has_parent().
|
||||
isTemp <- function(path, tempDir = tempdir(), mustExist) {
|
||||
if (!isTRUE(mustExist)) {
|
||||
# jcheng 2020-05-11: I added mustExist just to make it totally obvious that
|
||||
# the path must exist. We don't support the case where the file doesn't
|
||||
# exist because it makes normalizePath unusable, and it's a bit scary
|
||||
# security-wise to compare paths without normalization. Using fs would fix
|
||||
# this as it knows how to normalize paths that don't exist.
|
||||
stop("isTemp(mustExist=FALSE) is not implemented")
|
||||
}
|
||||
|
||||
#' Printable Output
|
||||
if (mustExist && !file.exists(path)) {
|
||||
stop("path does not exist")
|
||||
}
|
||||
|
||||
if (nchar(tempDir) == 0 || !dir.exists(tempDir)) {
|
||||
# This should never happen, but just to be super paranoid...
|
||||
stop("invalid temp dir")
|
||||
}
|
||||
|
||||
path <- normalizePath(path, winslash = "/", mustWork = mustExist)
|
||||
|
||||
tempDir <- normalizePath(tempDir, winslash = "/", mustWork = TRUE)
|
||||
if (path == tempDir) {
|
||||
return(FALSE)
|
||||
}
|
||||
|
||||
tempDir <- ensure_trailing_slash(tempDir)
|
||||
if (path == tempDir) {
|
||||
return(FALSE)
|
||||
}
|
||||
|
||||
return(substr(path, 1, nchar(tempDir)) == tempDir)
|
||||
}
|
||||
|
||||
#' Text Output
|
||||
#'
|
||||
#' Makes a reactive version of the given function that captures any printed
|
||||
#' output, and also captures its printable result (unless
|
||||
#' \code{\link[base]{invisible}}), into a string. The resulting function is suitable
|
||||
#' for assigning to an \code{output} slot.
|
||||
#' @description
|
||||
#' `renderPrint()` prints the result of `expr`, while `renderText()` pastes it
|
||||
#' together into a single string. `renderPrint()` is equivalent to [print()];
|
||||
#' `renderText()` is equivalent to [cat()]. Both functions capture all other
|
||||
#' printed output generated while evaluating `expr`.
|
||||
#'
|
||||
#' The corresponding HTML output tag can be anything (though \code{pre} is
|
||||
#' `renderPrint()` is usually paired with [verbatimTextOutput()];
|
||||
#' `renderText()` is usually paired with [textOutput()].
|
||||
#'
|
||||
#' @details
|
||||
#' The corresponding HTML output tag can be anything (though `pre` is
|
||||
#' recommended if you need a monospace font and whitespace preserved) and should
|
||||
#' have the CSS class name \code{shiny-text-output}.
|
||||
#' have the CSS class name `shiny-text-output`.
|
||||
#'
|
||||
#' The result of executing \code{func} will be printed inside a
|
||||
#' \code{\link[utils]{capture.output}} call.
|
||||
#' @return
|
||||
#' For `renderPrint()`, note the given expression returns `NULL` then `NULL`
|
||||
#' will actually be visible in the output. To display nothing, make your
|
||||
#' function return [invisible()].
|
||||
#'
|
||||
#' 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[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 expr An expression to evaluate.
|
||||
#' @param env The environment in which to evaluate `expr`. For expert use only.
|
||||
#' @param quoted Is `expr` a quoted expression (with `quote()`)? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param width The value for \code{\link[base]{options}('width')}.
|
||||
#' @param width Width of printed output.
|
||||
#' @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.
|
||||
#' call to [verbatimTextOutput()] or [textOutput()] when the functions are
|
||||
#' used in an interactive RMarkdown document.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#' @export
|
||||
@@ -395,41 +476,18 @@ createRenderPrintPromiseDomain <- function(width) {
|
||||
)
|
||||
}
|
||||
|
||||
#' Text Output
|
||||
#'
|
||||
#' Makes a reactive version of the given function that also uses
|
||||
#' \code{\link[base]{cat}} to turn its result into a single-element character
|
||||
#' vector.
|
||||
#'
|
||||
#' The corresponding HTML output tag can be anything (though \code{pre} is
|
||||
#' recommended if you need a monospace font and whitespace preserved) and should
|
||||
#' have the CSS class name \code{shiny-text-output}.
|
||||
#'
|
||||
#' The result of executing \code{func} will passed to \code{cat}, inside a
|
||||
#' \code{\link[utils]{capture.output}} call.
|
||||
#'
|
||||
#' @param expr An expression that returns an R object that can be used as an
|
||||
#' argument to \code{cat}.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param 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
|
||||
#' @param sep A separator passed to `cat` to be appended after each
|
||||
#' element.
|
||||
#' @export
|
||||
#' @rdname renderPrint
|
||||
renderText <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
outputArgs=list()) {
|
||||
outputArgs=list(), sep=" ") {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
createRenderFunction(
|
||||
func,
|
||||
function(value, session, name, ...) {
|
||||
paste(utils::capture.output(cat(value)), collapse="\n")
|
||||
paste(utils::capture.output(cat(value, sep=sep)), collapse="\n")
|
||||
},
|
||||
textOutput, outputArgs
|
||||
)
|
||||
@@ -439,19 +497,19 @@ renderText <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
#'
|
||||
#' 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}}).
|
||||
#' The corresponding HTML output tag should be `div` and have the CSS class
|
||||
#' name `shiny-html-output` (or use [uiOutput()]).
|
||||
#'
|
||||
#' @param expr An expression that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' @param expr An expression that returns a Shiny tag object, [HTML()],
|
||||
#' or a list of such objects.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' @param env The environment in which to evaluate `expr`.
|
||||
#' @param quoted Is `expr` a quoted expression (with `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{uiOutput}} when \code{renderUI} is used in an
|
||||
#' call to [uiOutput()] when `renderUI` is used in an
|
||||
#' interactive R Markdown document.
|
||||
#'
|
||||
#' @seealso \code{\link{uiOutput}}
|
||||
#' @seealso [uiOutput()]
|
||||
#' @export
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -494,25 +552,25 @@ renderUI <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
#' file downloads (for example, downloading the currently visible data as a CSV
|
||||
#' file). Both filename and contents can be calculated dynamically at the time
|
||||
#' the user initiates the download. Assign the return value to a slot on
|
||||
#' \code{output} in your server function, and in the UI use
|
||||
#' \code{\link{downloadButton}} or \code{\link{downloadLink}} to make the
|
||||
#' `output` in your server function, and in the UI use
|
||||
#' [downloadButton()] or [downloadLink()] to make the
|
||||
#' download available.
|
||||
#'
|
||||
#' @param filename A string of the filename, including extension, that the
|
||||
#' user's web browser should default to when downloading the file; or a
|
||||
#' function that returns such a string. (Reactive values and functions may be
|
||||
#' used from this function.)
|
||||
#' @param content A function that takes a single argument \code{file} that is a
|
||||
#' @param content A function that takes a single argument `file` that is a
|
||||
#' file path (string) of a nonexistent temp file, and writes the content to
|
||||
#' that file path. (Reactive values and functions may be used from this
|
||||
#' function.)
|
||||
#' @param contentType A string of the download's
|
||||
#' \href{http://en.wikipedia.org/wiki/Internet_media_type}{content type}, for
|
||||
#' 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.
|
||||
#' [content type](http://en.wikipedia.org/wiki/Internet_media_type), for
|
||||
#' example `"text/csv"` or `"image/png"`. If `NULL` or
|
||||
#' `NA`, the content type will be guessed based on the filename
|
||||
#' extension, or `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
|
||||
#' call to [downloadButton()] when `downloadHandler` is used
|
||||
#' in an interactive R Markdown document.
|
||||
#'
|
||||
#' @examples
|
||||
@@ -520,7 +578,7 @@ renderUI <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' downloadLink("downloadData", "Download")
|
||||
#' downloadButton("downloadData", "Download")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
@@ -556,12 +614,12 @@ downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()
|
||||
#' searching, filtering, and sorting can be done on the R side using Shiny as
|
||||
#' the server infrastructure.
|
||||
#'
|
||||
#' For the \code{options} argument, the character elements that have the class
|
||||
#' \code{"AsIs"} (usually returned from \code{\link[base]{I}()}) will be evaluated in
|
||||
#' For the `options` argument, the character elements that have the class
|
||||
#' `"AsIs"` (usually returned from [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
|
||||
#' options list, and the \code{I()} notation does not work for lower-level
|
||||
#' options list, and the `I()` notation does not work for lower-level
|
||||
#' elements in the list.
|
||||
#' @param expr An expression that returns a data frame or a matrix.
|
||||
#' @param options A list of initialization options to be passed to DataTables,
|
||||
@@ -570,25 +628,25 @@ downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()
|
||||
#' frequent search requests).
|
||||
#' @param callback A JavaScript function to be applied to the DataTable object.
|
||||
#' This is useful for DataTables plug-ins, which often require the DataTable
|
||||
#' instance to be available (\url{http://datatables.net/extensions/}).
|
||||
#' @param escape Whether to escape HTML entities in the table: \code{TRUE} means
|
||||
#' to escape the whole table, and \code{FALSE} means not to escape it.
|
||||
#' instance to be available (<http://datatables.net/extensions/>).
|
||||
#' @param escape Whether to escape HTML entities in the table: `TRUE` means
|
||||
#' to escape the whole table, and `FALSE` means not to escape it.
|
||||
#' Alternatively, you can specify numeric column indices or column names to
|
||||
#' 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')}.
|
||||
#' indicate which columns to escape, e.g. `1:5` (the first 5 columns),
|
||||
#' `c(1, 3, 4)`, or `c(-1, -3)` (all columns except the first and
|
||||
#' third), or `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
|
||||
#' call to [dataTableOutput()] when `renderDataTable` is used
|
||||
#' in an interactive R Markdown document.
|
||||
#'
|
||||
#' @references \url{http://datatables.net}
|
||||
#' @references <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
|
||||
#' separate package \pkg{DT} (\url{https://github.com/rstudio/DT}) that allows
|
||||
#' separate package \pkg{DT} (<https://github.com/rstudio/DT>) that allows
|
||||
#' you to create both server-side and client-side DataTables, and supports
|
||||
#' additional DataTables features. Consider using \code{DT::renderDataTable()}
|
||||
#' and \code{DT::dataTableOutput()} (see
|
||||
#' \url{http://rstudio.github.io/DT/shiny.html} for more information).
|
||||
#' additional DataTables features. Consider using `DT::renderDataTable()`
|
||||
#' and `DT::dataTableOutput()` (see
|
||||
#' <http://rstudio.github.io/DT/shiny.html> for more information).
|
||||
#' @export
|
||||
#' @inheritParams renderPlot
|
||||
#' @examples
|
||||
@@ -707,13 +765,19 @@ checkDT9 <- function(options) {
|
||||
|
||||
# Deprecated functions ------------------------------------------------------
|
||||
|
||||
#' Deprecated reactive functions
|
||||
#' @name deprecatedReactives
|
||||
#' @keywords internal
|
||||
NULL
|
||||
|
||||
#' Plot output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderPlot}}.
|
||||
#' `reactivePlot` has been replaced by [renderPlot()].
|
||||
#' @param func A function.
|
||||
#' @param width Width.
|
||||
#' @param height Height.
|
||||
#' @param ... Other arguments to pass on.
|
||||
#' @rdname deprecatedReactives
|
||||
#' @export
|
||||
reactivePlot <- function(func, width='auto', height='auto', ...) {
|
||||
shinyDeprecated(new="renderPlot")
|
||||
@@ -722,9 +786,8 @@ reactivePlot <- function(func, width='auto', height='auto', ...) {
|
||||
|
||||
#' Table output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderTable}}.
|
||||
#' @param func A function.
|
||||
#' @param ... Other arguments to pass on.
|
||||
#' `reactiveTable` has been replaced by [renderTable()].
|
||||
#' @rdname deprecatedReactives
|
||||
#' @export
|
||||
reactiveTable <- function(func, ...) {
|
||||
shinyDeprecated(new="renderTable")
|
||||
@@ -733,8 +796,8 @@ reactiveTable <- function(func, ...) {
|
||||
|
||||
#' Print output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderPrint}}.
|
||||
#' @param func A function.
|
||||
#' `reactivePrint` has been replaced by [renderPrint()].
|
||||
#' @rdname deprecatedReactives
|
||||
#' @export
|
||||
reactivePrint <- function(func) {
|
||||
shinyDeprecated(new="renderPrint")
|
||||
@@ -743,8 +806,8 @@ reactivePrint <- function(func) {
|
||||
|
||||
#' UI output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderUI}}.
|
||||
#' @param func A function.
|
||||
#' `reactiveUI` has been replaced by [renderUI()].
|
||||
#' @rdname deprecatedReactives
|
||||
#' @export
|
||||
reactiveUI <- function(func) {
|
||||
shinyDeprecated(new="renderUI")
|
||||
@@ -753,8 +816,8 @@ reactiveUI <- function(func) {
|
||||
|
||||
#' Text output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderText}}.
|
||||
#' @param func A function.
|
||||
#' `reactiveText` has been replaced by [renderText()].
|
||||
#' @rdname deprecatedReactives
|
||||
#' @export
|
||||
reactiveText <- function(func) {
|
||||
shinyDeprecated(new="renderText")
|
||||
|
||||
@@ -104,20 +104,18 @@ navTabsDropdown <- function(files) {
|
||||
|
||||
tabContentHelper <- function(files, path, language) {
|
||||
lapply(files, function(file) {
|
||||
with(tags,
|
||||
div(class=paste("tab-pane",
|
||||
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",
|
||||
tags$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))))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -222,4 +220,3 @@ showcaseUI <- function(ui) {
|
||||
showcaseBody(ui)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
#' 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}.
|
||||
#' done by calling `runApp()` with `test.mode=TRUE`, or by setting the
|
||||
#' global option `shiny.testmode` to `TRUE`.
|
||||
#'
|
||||
#' @param quoted_ Are the expression quoted? Default is \code{FALSE}.
|
||||
#' @param quoted_ Are the expression quoted? Default is `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
|
||||
|
||||
156
R/test-server.R
Normal file
156
R/test-server.R
Normal file
@@ -0,0 +1,156 @@
|
||||
#' Reactive testing for Shiny server functions and modules
|
||||
#'
|
||||
#' A way to test the reactive interactions in Shiny applications. Reactive
|
||||
#' interactions are defined in the server function of applications and in
|
||||
#' modules.
|
||||
#' @param app A server function (i.e. a function with `input`, `output`,
|
||||
#' and `session`), or a module function (i.e. a function with first
|
||||
#' argument `id` that calls [moduleServer()].
|
||||
#'
|
||||
#' You can also provide an app, a path an app, or anything that
|
||||
#' [`as.shiny.appobj()`] can handle.
|
||||
#' @param expr Test code containing expectations. The objects from inside the
|
||||
#' server function environment will be made available in the environment of
|
||||
#' the test expression (this is done using a data mask with
|
||||
#' [rlang::eval_tidy()]). This includes the parameters of the server function
|
||||
#' (e.g. `input`, `output`, and `session`), along with any other values
|
||||
#' created inside of the server function.
|
||||
#' @param args Additional arguments to pass to the module function. If `app` is
|
||||
#' a module, and no `id` argument is provided, one will be generated and
|
||||
#' supplied automatically.
|
||||
#' @param session The [`MockShinySession`] object to use as the [reactive
|
||||
#' domain][shiny::domains]. The same session object is used as the domain both
|
||||
#' during invocation of the server or module under test and during evaluation
|
||||
#' of `expr`.
|
||||
#' @include mock-session.R
|
||||
#' @rdname testServer
|
||||
#' @examples
|
||||
#' # Testing a server function ----------------------------------------------
|
||||
#' server <- function(input, output, session) {
|
||||
#' x <- reactive(input$a * input$b)
|
||||
#' }
|
||||
#'
|
||||
#' testServer(server, {
|
||||
#' session$setInputs(a = 2, b = 3)
|
||||
#' stopifnot(x() == 6)
|
||||
#' })
|
||||
#'
|
||||
#'
|
||||
#' # Testing a module --------------------------------------------------------
|
||||
#' myModuleServer <- function(id, multiplier = 2, prefix = "I am ") {
|
||||
#' moduleServer(id, function(input, output, session) {
|
||||
#' myreactive <- reactive({
|
||||
#' input$x * multiplier
|
||||
#' })
|
||||
#' output$txt <- renderText({
|
||||
#' paste0(prefix, myreactive())
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' testServer(myModuleServer, args = list(multiplier = 2), {
|
||||
#' session$setInputs(x = 1)
|
||||
#' # You're also free to use third-party
|
||||
#' # testing packages like testthat:
|
||||
#' # expect_equal(myreactive(), 2)
|
||||
#' stopifnot(myreactive() == 2)
|
||||
#' stopifnot(output$txt == "I am 2")
|
||||
#'
|
||||
#' session$setInputs(x = 2)
|
||||
#' stopifnot(myreactive() == 4)
|
||||
#' stopifnot(output$txt == "I am 4")
|
||||
#' # Any additional arguments, below, are passed along to the module.
|
||||
#' })
|
||||
#' @export
|
||||
testServer <- function(app = NULL, expr, args = list(), session = MockShinySession$new()) {
|
||||
require(shiny)
|
||||
|
||||
if (!is.null(getDefaultReactiveDomain()))
|
||||
stop("testServer() is for use only within tests and may not indirectly call itself.")
|
||||
|
||||
on.exit(if (!session$isClosed()) session$close(), add = TRUE)
|
||||
quosure <- rlang::enquo(expr)
|
||||
|
||||
if (isModuleServer(app)) {
|
||||
if (!("id" %in% names(args)))
|
||||
args[["id"]] <- session$genId()
|
||||
# app is presumed to be a module, and modules may take additional arguments,
|
||||
# so splice in any args.
|
||||
withMockContext(session, rlang::exec(app, !!!args))
|
||||
|
||||
# If app is a module, then we must use both the module function's immediate
|
||||
# environment and also its enclosing environment to construct the mask.
|
||||
parent_clone <- rlang::env_clone(parent.env(session$env))
|
||||
clone <- rlang::env_clone(session$env, parent_clone)
|
||||
mask <- rlang::new_data_mask(clone, parent_clone)
|
||||
withMockContext(session, rlang::eval_tidy(quosure, mask, rlang::caller_env()))
|
||||
return(invisible())
|
||||
}
|
||||
|
||||
if (is.null(app)) {
|
||||
path <- findEnclosingApp(".")
|
||||
app <- shinyAppDir(path)
|
||||
} else if (isServer(app)) {
|
||||
app <- shinyApp(fluidPage(), app)
|
||||
} else {
|
||||
app <- as.shiny.appobj(app)
|
||||
}
|
||||
|
||||
if (!is.null(app$onStart))
|
||||
app$onStart()
|
||||
if (!is.null(app$onStop))
|
||||
on.exit(app$onStop(), add = TRUE)
|
||||
|
||||
server <- app$serverFuncSource()
|
||||
if (!"session" %in% names(formals(server)))
|
||||
stop("Tested application server functions must declare input, output, and session arguments.")
|
||||
if (length(args))
|
||||
stop("Arguments were provided to a server function.")
|
||||
|
||||
body(server) <- rlang::expr({
|
||||
session$setEnv(base::environment())
|
||||
!!body(server)
|
||||
})
|
||||
withMockContext(session,
|
||||
server(input = session$input, output = session$output, session = session)
|
||||
)
|
||||
|
||||
# # If app is a server, we use only the server function's immediate
|
||||
# # environment to construct the mask.
|
||||
mask <- rlang::new_data_mask(rlang::env_clone(session$env))
|
||||
withMockContext(session, {
|
||||
rlang::eval_tidy(quosure, mask, rlang::caller_env())
|
||||
})
|
||||
invisible()
|
||||
}
|
||||
|
||||
withMockContext <- function(session, expr) {
|
||||
isolate(
|
||||
withReactiveDomain(session, {
|
||||
withr::with_options(list(`shiny.allowoutputreads` = TRUE), {
|
||||
# Sets a cache for renderCachedPlot() with cache = "app" to use.
|
||||
shinyOptions("cache" = session$appcache)
|
||||
expr
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
# Helpers -----------------------------------------------------------------
|
||||
|
||||
isModuleServer <- function(x) {
|
||||
is.function(x) && names(formals(x))[[1]] == "id"
|
||||
}
|
||||
|
||||
isServer <- function(x) {
|
||||
if (!is.function(x)) {
|
||||
return(FALSE)
|
||||
}
|
||||
|
||||
if (length(formals(x)) < 3) {
|
||||
return(FALSE)
|
||||
}
|
||||
|
||||
identical(names(formals(x))[1:3], c("input", "output", "session"))
|
||||
}
|
||||
189
R/test.R
Normal file
189
R/test.R
Normal file
@@ -0,0 +1,189 @@
|
||||
#' Creates and returns run result data frame.
|
||||
#'
|
||||
#' @param file Name of the test runner file, a character vector of length 1.
|
||||
#' @param pass Whether or not the test passed, a logical vector of length 1.
|
||||
#' @param result Value (wrapped in a list) obtained by evaluating `file`.
|
||||
#' This can also by any errors signaled when evaluating the `file`.
|
||||
#'
|
||||
#' @return A 1-row data frame representing a single test run. `result` and
|
||||
#' is a "list column", or a column that contains list elements.
|
||||
#' @noRd
|
||||
result_row <- function(file, pass, result) {
|
||||
stopifnot(is.list(result))
|
||||
df <- data.frame(
|
||||
file = file,
|
||||
pass = pass,
|
||||
result = I(result),
|
||||
stringsAsFactors = FALSE
|
||||
)
|
||||
class(df) <- c("shiny_runtests", class(df))
|
||||
df
|
||||
}
|
||||
|
||||
#' Check to see if the given directory contains at least one script, and that
|
||||
#' all scripts in the directory are shinytest scripts.
|
||||
#' Scans for the magic string of `app <- ShinyDriver$new(` as an indicator that
|
||||
#' this is a shinytest.
|
||||
#' @noRd
|
||||
is_legacy_shinytest_dir <- function(path){
|
||||
is_shinytest_script <- function(file) {
|
||||
if (!file.exists(file)) {
|
||||
return(FALSE)
|
||||
}
|
||||
|
||||
text <- readLines(file, warn = FALSE)
|
||||
any(
|
||||
grepl("app\\s*<-\\s*ShinyDriver\\$new\\(", text, perl=TRUE)
|
||||
)
|
||||
}
|
||||
|
||||
files <- dir(path, full.names = TRUE)
|
||||
files <- files[!file.info(files)$isdir]
|
||||
if (length(files) == 0) {
|
||||
return(FALSE)
|
||||
}
|
||||
all(vapply(files, is_shinytest_script, logical(1)))
|
||||
}
|
||||
|
||||
#' Runs the tests associated with this Shiny app
|
||||
#'
|
||||
#' Sources the `.R` files in the top-level of `tests/` much like `R CMD check`.
|
||||
#' These files are typically simple runners for tests nested in other
|
||||
#' directories under `tests/`.
|
||||
#'
|
||||
#' @param appDir The base directory for the application.
|
||||
#' @param filter If not `NULL`, only tests with file names matching this regular
|
||||
#' expression will be executed. Matching is performed on the file name
|
||||
#' including the extension.
|
||||
#' @param assert Logical value which determines if an error should be thrown if any error is captured.
|
||||
#' @param envir Parent testing environment in which to base the individual testing environments.
|
||||
#'
|
||||
#' @return A data frame classed with the supplemental class `"shiny_runtests"`.
|
||||
#' The data frame has the following columns:
|
||||
#'
|
||||
#' | **Name** | **Type** | **Meaning** |
|
||||
#' | :-- | :-- | :-- |
|
||||
#' | `file` | `character(1)` | File name of the runner script in `tests/` that was sourced. |
|
||||
#' | `pass` | `logical(1)` | Whether or not the runner script signaled an error when sourced. |
|
||||
#' | `result` | any or `NA` | The return value of the runner |
|
||||
#'
|
||||
#' @details Historically, [shinytest](https://rstudio.github.io/shinytest/)
|
||||
#' recommended placing tests at the top-level of the `tests/` directory.
|
||||
#' This older folder structure is not supported by runTests.
|
||||
#' Please see [shinyAppTemplate()] for more details.
|
||||
#' @export
|
||||
runTests <- function(
|
||||
appDir = ".",
|
||||
filter = NULL,
|
||||
assert = TRUE,
|
||||
envir = globalenv()
|
||||
) {
|
||||
# similar to runApp()
|
||||
# Allows shiny's functions to be available in the UI, server, and test code
|
||||
require(shiny)
|
||||
|
||||
testsDir <- file.path(appDir, "tests")
|
||||
if (!dirExists(testsDir)) {
|
||||
stop("No tests directory found: ", testsDir)
|
||||
}
|
||||
runners <- list.files(testsDir, pattern="\\.r$", ignore.case = TRUE)
|
||||
|
||||
if (length(runners) == 0) {
|
||||
message("No test runners found in ", testsDir)
|
||||
return(result_row(character(0), logical(0), list()))
|
||||
}
|
||||
|
||||
if (!is.null(filter)) {
|
||||
runners <- runners[grepl(filter, runners)]
|
||||
}
|
||||
if (length(runners) == 0) {
|
||||
stop("No test runners matched the given filter: '", filter, "'")
|
||||
}
|
||||
|
||||
# See the @details section of the runTests() docs above for why this branch exists.
|
||||
if (is_legacy_shinytest_dir(testsDir)) {
|
||||
stop(
|
||||
"It appears that the .R files in ", testsDir, " are all shinytests.",
|
||||
" This is not supported by `shiny::runTests()`.",
|
||||
"\nPlease see `?shinytest::migrateShinytestDir` to migrate your shinytest file structure to the new format (requires shinytest 1.4.0 or above).",
|
||||
"\nSee `?shiny::shinyAppTemplate` for an example of the new testing file structure."
|
||||
)
|
||||
}
|
||||
|
||||
renv <- new.env(parent = envir)
|
||||
|
||||
# Otherwise source all the runners -- each in their own environment.
|
||||
ret <- do.call(rbind, lapply(runners, function(r) {
|
||||
pass <- FALSE
|
||||
result <-
|
||||
tryCatch({
|
||||
env <- new.env(parent = renv)
|
||||
withr::with_dir(testsDir, {
|
||||
ret <- sourceUTF8(r, envir = env)
|
||||
})
|
||||
pass <- TRUE
|
||||
ret
|
||||
}, error = function(err) {
|
||||
message("Error in ", r, "\n", err)
|
||||
err
|
||||
})
|
||||
result_row(file.path(testsDir, r), pass, list(result))
|
||||
}))
|
||||
|
||||
if (isTRUE(assert)) {
|
||||
if (!all(ret$pass)) {
|
||||
stop("Shiny App Test Failures detected in\n", paste0("* ", runtest_pretty_file(ret$file[!ret$pass]), collapse = "\n"), call. = FALSE)
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
|
||||
runtest_pretty_file <- function(f) {
|
||||
test_folder <- dirname(f)
|
||||
app_folder <- dirname(test_folder)
|
||||
file.path(
|
||||
basename(app_folder),
|
||||
basename(test_folder),
|
||||
basename(f)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#' @export
|
||||
print.shiny_runtests <- function(x, ..., reporter = "summary") {
|
||||
|
||||
cat("Shiny App Test Results\n")
|
||||
|
||||
|
||||
if (any(x$pass)) {
|
||||
# TODO in future... use clisymbols::symbol$tick and crayon green
|
||||
cat("* Success\n")
|
||||
mapply(
|
||||
x$file,
|
||||
x$pass,
|
||||
x$result,
|
||||
FUN = function(file, pass, result) {
|
||||
if (!pass) return()
|
||||
# print(result)
|
||||
cat(" - ", runtest_pretty_file(file), "\n", sep = "")
|
||||
}
|
||||
)
|
||||
}
|
||||
if (any(!x$pass)) {
|
||||
# TODO in future... use clisymbols::symbol$cross and crayon red
|
||||
cat("* Failure\n")
|
||||
mapply(
|
||||
x$file,
|
||||
x$pass,
|
||||
x$result,
|
||||
FUN = function(file, pass, result) {
|
||||
if (pass) return()
|
||||
cat(" - ", runtest_pretty_file(file), "\n", sep = "")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
invisible(x)
|
||||
}
|
||||
67
R/timer.R
67
R/timer.R
@@ -1,6 +1,5 @@
|
||||
# Return the current time, in milliseconds from epoch, with
|
||||
# unspecified time zone.
|
||||
now <- function() {
|
||||
# Return the current time, in milliseconds from epoch.
|
||||
getTimeMs <- function() {
|
||||
as.numeric(Sys.time()) * 1000
|
||||
}
|
||||
|
||||
@@ -12,9 +11,11 @@ TimerCallbacks <- R6Class(
|
||||
.nextId = 0L,
|
||||
.funcs = 'Map',
|
||||
.times = data.frame(),
|
||||
.now = 'Function',
|
||||
|
||||
initialize = function() {
|
||||
initialize = function(nowFn = getTimeMs) {
|
||||
.funcs <<- Map$new()
|
||||
.now <<- nowFn
|
||||
},
|
||||
clear = function() {
|
||||
.nextId <<- 0L
|
||||
@@ -30,7 +31,7 @@ TimerCallbacks <- R6Class(
|
||||
id <- .nextId
|
||||
.nextId <<- .nextId + 1L
|
||||
|
||||
t <- now()
|
||||
t <- .now()
|
||||
|
||||
# TODO: Horribly inefficient, use a heap instead
|
||||
.times <<- rbind(.times, data.frame(time=t+millis,
|
||||
@@ -56,17 +57,17 @@ TimerCallbacks <- R6Class(
|
||||
timeToNextEvent = function() {
|
||||
if (dim(.times)[1] == 0)
|
||||
return(Inf)
|
||||
return(.times[1, 'time'] - now())
|
||||
return(.times[1, 'time'] - .now())
|
||||
},
|
||||
takeElapsed = function() {
|
||||
t <- now()
|
||||
elapsed <- .times$time < now()
|
||||
t <- .now()
|
||||
elapsed <- .times$time <= .now()
|
||||
result <- .times[elapsed,]
|
||||
.times <<- .times[!elapsed,]
|
||||
|
||||
# TODO: Examine scheduled column to check if any funny business
|
||||
# has occurred with the system clock (e.g. if scheduled
|
||||
# is later than now())
|
||||
# is later than .now())
|
||||
|
||||
return(result)
|
||||
},
|
||||
@@ -86,6 +87,30 @@ TimerCallbacks <- R6Class(
|
||||
)
|
||||
)
|
||||
|
||||
MockableTimerCallbacks <- R6Class(
|
||||
'MockableTimerCallbacks',
|
||||
inherit = TimerCallbacks,
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
# Empty constructor defaults to the getNow implementation
|
||||
initialize = function() {
|
||||
super$initialize(self$mockNow)
|
||||
},
|
||||
mockNow = function() {
|
||||
return(private$time)
|
||||
},
|
||||
elapse = function(millis) {
|
||||
private$time <- private$time + millis
|
||||
},
|
||||
getElapsed = function() {
|
||||
private$time
|
||||
}
|
||||
), private = list(
|
||||
time = 0L
|
||||
)
|
||||
)
|
||||
|
||||
timerCallbacks <- TimerCallbacks$new()
|
||||
|
||||
scheduleTask <- function(millis, callback) {
|
||||
@@ -96,3 +121,27 @@ scheduleTask <- function(millis, callback) {
|
||||
invisible(timerCallbacks$unschedule(id))
|
||||
}
|
||||
}
|
||||
|
||||
#' Get a scheduler function for scheduling tasks. Give priority to the
|
||||
#' session scheduler, but if it doesn't exist, use the global one.
|
||||
#' @noRd
|
||||
defineScheduler <- function(session){
|
||||
if (!is.null(session) && !is.null(session$.scheduleTask)){
|
||||
return(session$.scheduleTask)
|
||||
}
|
||||
scheduleTask
|
||||
}
|
||||
|
||||
|
||||
#' Get the current time using the current reactive domain. This will try to use
|
||||
#' the session's .now() method, but if that's not available, it will just return
|
||||
#' the real time (from getTimeMs()). The purpose of this function is to allow
|
||||
#' MockableTimerCallbacks to work.
|
||||
#' @noRd
|
||||
getDomainTimeMs <- function(session){
|
||||
if (!is.null(session) && !is.null(session$.now)){
|
||||
return(session$.now())
|
||||
} else {
|
||||
getTimeMs()
|
||||
}
|
||||
}
|
||||
|
||||
140
R/update-input.R
140
R/update-input.R
@@ -1,10 +1,9 @@
|
||||
#' Change the value of a text input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#' @param placeholder The placeholder to set for the input object.
|
||||
#' @inheritParams textInput
|
||||
#'
|
||||
#' @seealso \code{\link{textInput}}
|
||||
#' @seealso [textInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -45,7 +44,7 @@ updateTextInput <- function(session, inputId, label = NULL, value = NULL, placeh
|
||||
#' @template update-input
|
||||
#' @inheritParams updateTextInput
|
||||
#'
|
||||
#' @seealso \code{\link{textAreaInput}}
|
||||
#' @seealso [textAreaInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -82,9 +81,9 @@ updateTextAreaInput <- updateTextInput
|
||||
#' Change the value of a checkbox input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#' @inheritParams checkboxInput
|
||||
#'
|
||||
#' @seealso \code{\link{checkboxInput}}
|
||||
#' @seealso [checkboxInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -116,23 +115,24 @@ updateCheckboxInput <- function(session, inputId, label = NULL, value = NULL) {
|
||||
#' 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)}.
|
||||
#' @inheritParams actionButton
|
||||
#'
|
||||
#' @seealso \code{\link{actionButton}}
|
||||
#' @seealso [actionButton()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' actionButton("update", "Update other buttons"),
|
||||
#' actionButton("update", "Update other buttons and link"),
|
||||
#' br(),
|
||||
#' actionButton("goButton", "Go"),
|
||||
#' br(),
|
||||
#' actionButton("goButton2", "Go 2", icon = icon("area-chart")),
|
||||
#' br(),
|
||||
#' actionButton("goButton3", "Go 3")
|
||||
#' actionButton("goButton3", "Go 3"),
|
||||
#' br(),
|
||||
#' actionLink("goLink", "Go Link")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
@@ -153,30 +153,34 @@ updateCheckboxInput <- function(session, inputId, label = NULL, value = NULL) {
|
||||
#' # unchaged and changes its label
|
||||
#' updateActionButton(session, "goButton3",
|
||||
#' label = "New label 3")
|
||||
#'
|
||||
#' # Updates goLink's label and icon
|
||||
#' updateActionButton(session, "goLink",
|
||||
#' label = "New link label",
|
||||
#' icon = icon("link"))
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @rdname updateActionButton
|
||||
#' @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)
|
||||
}
|
||||
#' @rdname updateActionButton
|
||||
#' @export
|
||||
updateActionLink <- updateActionButton
|
||||
|
||||
|
||||
#' 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. 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
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @inheritParams dateInput
|
||||
#'
|
||||
#' @seealso \code{\link{dateInput}}
|
||||
#' @seealso [dateInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -205,18 +209,9 @@ updateActionButton <- function(session, inputId, label = NULL, icon = NULL) {
|
||||
updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL) {
|
||||
|
||||
# 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)
|
||||
value <- dateYMD(value, "value")
|
||||
min <- dateYMD(min, "min")
|
||||
max <- dateYMD(max, "max")
|
||||
|
||||
message <- dropNulls(list(label=label, value=value, min=min, max=max))
|
||||
session$sendInputMessage(inputId, message)
|
||||
@@ -226,16 +221,9 @@ updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
#' Change the start and end values of a date range input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param start The start date. Either a Date object, or a string in
|
||||
#' \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. 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
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @inheritParams dateRangeInput
|
||||
#'
|
||||
#' @seealso \code{\link{dateRangeInput}}
|
||||
#' @seealso [dateRangeInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -266,12 +254,11 @@ updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
updateDateRangeInput <- function(session, inputId, label = NULL,
|
||||
start = NULL, end = NULL, min = NULL,
|
||||
max = NULL) {
|
||||
# Make sure start and end are strings, not date objects. This is for
|
||||
# consistency across different locales.
|
||||
if (inherits(start, "Date")) start <- format(start, '%Y-%m-%d')
|
||||
if (inherits(end, "Date")) end <- format(end, '%Y-%m-%d')
|
||||
if (inherits(min, "Date")) min <- format(min, '%Y-%m-%d')
|
||||
if (inherits(max, "Date")) max <- format(max, '%Y-%m-%d')
|
||||
|
||||
start <- dateYMD(start, "start")
|
||||
end <- dateYMD(end, "end")
|
||||
min <- dateYMD(min, "min")
|
||||
max <- dateYMD(max, "max")
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = label,
|
||||
@@ -285,14 +272,14 @@ updateDateRangeInput <- function(session, inputId, label = NULL,
|
||||
|
||||
#' Change the selected tab on the client
|
||||
#'
|
||||
#' @param session The \code{session} object passed to function given to
|
||||
#' \code{shinyServer}.
|
||||
#' @param inputId The id of the \code{tabsetPanel}, \code{navlistPanel},
|
||||
#' or \code{navbarPage} object.
|
||||
#' @param selected The name of the tab to make active.
|
||||
#' @param session The `session` object passed to function given to
|
||||
#' `shinyServer`.
|
||||
#' @param inputId The id of the `tabsetPanel`, `navlistPanel`,
|
||||
#' or `navbarPage` object.
|
||||
#' @inheritParams tabsetPanel
|
||||
#'
|
||||
#' @seealso \code{\link{tabsetPanel}}, \code{\link{navlistPanel}},
|
||||
#' \code{\link{navbarPage}}
|
||||
#' @seealso [tabsetPanel()], [navlistPanel()],
|
||||
#' [navbarPage()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -338,12 +325,9 @@ updateNavlistPanel <- updateTabsetPanel
|
||||
#' Change the value of a number 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.
|
||||
#' @inheritParams numericInput
|
||||
#'
|
||||
#' @seealso \code{\link{numericInput}}
|
||||
#' @seealso [numericInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -388,14 +372,9 @@ updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
#' 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.
|
||||
#' @inheritParams sliderInput
|
||||
#'
|
||||
#' @seealso \code{\link{sliderInput}}
|
||||
#' @seealso [sliderInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
@@ -428,13 +407,15 @@ updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
updateSliderInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL, step = NULL, timeFormat = NULL, timezone = NULL)
|
||||
{
|
||||
# If no min/max/value is provided, we won't know the
|
||||
# type, and this will return an empty string
|
||||
dataType <- getSliderType(min, max, value)
|
||||
|
||||
if (is.null(timeFormat)) {
|
||||
timeFormat <- switch(dataType, date = "%F", datetime = "%F %T", number = NULL)
|
||||
}
|
||||
|
||||
if (dataType == "date" || dataType == "datetime") {
|
||||
if (isTRUE(dataType %in% c("date", "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)
|
||||
@@ -481,7 +462,7 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
|
||||
#' @template update-input
|
||||
#' @inheritParams checkboxGroupInput
|
||||
#'
|
||||
#' @seealso \code{\link{checkboxGroupInput}}
|
||||
#' @seealso [checkboxGroupInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -528,7 +509,7 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
#' @template update-input
|
||||
#' @inheritParams radioButtons
|
||||
#'
|
||||
#' @seealso \code{\link{radioButtons}}
|
||||
#' @seealso [radioButtons()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -576,7 +557,7 @@ updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
|
||||
#' @template update-input
|
||||
#' @inheritParams selectInput
|
||||
#'
|
||||
#' @seealso \code{\link{selectInput}} \code{\link{varSelectInput}}
|
||||
#' @seealso [selectInput()] [varSelectInput()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -614,16 +595,16 @@ updateSelectInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL) {
|
||||
choices <- if (!is.null(choices)) choicesWithNames(choices)
|
||||
if (!is.null(selected)) selected <- as.character(selected)
|
||||
options <- if (!is.null(choices)) selectOptions(choices, selected)
|
||||
options <- if (!is.null(choices)) selectOptions(choices, selected, inputId, FALSE)
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' @rdname updateSelectInput
|
||||
#' @inheritParams selectizeInput
|
||||
#' @param server whether to store \code{choices} on the server side, and load
|
||||
#' @param server whether to store `choices` on the server side, and load
|
||||
#' the select options dynamically on searching, instead of writing all
|
||||
#' \code{choices} into the page at once (i.e., only use the client-side
|
||||
#' `choices` into the page at once (i.e., only use the client-side
|
||||
#' version of \pkg{selectize.js})
|
||||
#' @export
|
||||
updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
@@ -653,8 +634,15 @@ updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
}
|
||||
}
|
||||
}
|
||||
# convert choices to a data frame so it returns [{label: , value: , group: },...]
|
||||
choices <- if (is.atomic(choices) || noOptGroup) {
|
||||
# convert choices to a data frame so it returns [{label: , value: , optgroup: },...]
|
||||
choices <- if (is.data.frame(choices)) {
|
||||
# jcheng 2018/09/25: I don't think we ever said data frames were OK to pass
|
||||
# to updateSelectInput, but one of the example apps does this and at least
|
||||
# one user noticed when we broke it.
|
||||
# https://github.com/rstudio/shiny/issues/2172
|
||||
# https://github.com/rstudio/shiny/issues/2192
|
||||
as.data.frame(choices, stringsAsFactors = FALSE)
|
||||
} else if (is.atomic(choices) || noOptGroup) {
|
||||
# fast path for vectors and flat lists
|
||||
if (is.list(choices)) {
|
||||
choices <- unlist(choices)
|
||||
@@ -701,7 +689,9 @@ updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
list(
|
||||
label = lab,
|
||||
value = as.character(choice),
|
||||
group = group
|
||||
# The name "optgroup" is because this is the default field where
|
||||
# selectize will look for group IDs
|
||||
optgroup = group
|
||||
)
|
||||
}, SIMPLIFY = FALSE)
|
||||
|
||||
@@ -714,7 +704,7 @@ updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
data.frame(
|
||||
label = extract_vector(choice_list, "label"),
|
||||
value = extract_vector(choice_list, "value"),
|
||||
group = extract_vector(choice_list, "group"),
|
||||
optgroup = extract_vector(choice_list, "optgroup"),
|
||||
stringsAsFactors = FALSE, row.names = NULL
|
||||
)
|
||||
}
|
||||
|
||||
324
R/utils.R
324
R/utils.R
@@ -14,7 +14,7 @@ NULL
|
||||
#'
|
||||
#' @note When called, the returned function attempts to preserve the R session's
|
||||
#' current seed by snapshotting and restoring
|
||||
#' \code{\link[base]{.Random.seed}}.
|
||||
#' [base::.Random.seed()].
|
||||
#'
|
||||
#' @examples
|
||||
#' rnormA <- repeatable(rnorm)
|
||||
@@ -121,8 +121,8 @@ isWholeNum <- function(x, tol = .Machine$double.eps^0.5) {
|
||||
}
|
||||
|
||||
`%AND%` <- function(x, y) {
|
||||
if (!is.null(x) && !is.na(x))
|
||||
if (!is.null(y) && !is.na(y))
|
||||
if (!is.null(x) && !isTRUE(is.na(x)))
|
||||
if (!is.null(y) && !isTRUE(is.na(y)))
|
||||
return(y)
|
||||
return(NULL)
|
||||
}
|
||||
@@ -173,12 +173,12 @@ anyUnnamed <- function(x) {
|
||||
}
|
||||
|
||||
|
||||
# Given a vector/list, returns a named vector (the labels will be blank).
|
||||
asNamedVector <- function(x) {
|
||||
if (!is.null(names(x)))
|
||||
return(x)
|
||||
# Given a vector/list, returns a named vector/list (the labels will be blank).
|
||||
asNamed <- function(x) {
|
||||
if (is.null(names(x))) {
|
||||
names(x) <- character(length(x))
|
||||
}
|
||||
|
||||
names(x) <- rep.int("", length(x))
|
||||
x
|
||||
}
|
||||
|
||||
@@ -210,6 +210,16 @@ sortByName <- function(x) {
|
||||
x[order(names(x))]
|
||||
}
|
||||
|
||||
# Sort a vector. If a character vector, sort using C locale, which is consistent
|
||||
# across platforms. Note that radix sort uses C locale according to ?sort.
|
||||
sort_c <- function(x, ...) {
|
||||
# Use UTF-8 encoding, because if encoding is "unknown" for non-ASCII
|
||||
# characters, the sort() will throw an error.
|
||||
if (is.character(x))
|
||||
x <- enc2utf8(x)
|
||||
sort(x, method = "radix", ...)
|
||||
}
|
||||
|
||||
# Wrapper around list2env with a NULL check. In R <3.2.0, if an empty unnamed
|
||||
# list is passed to list2env(), it errors. But an empty named list is OK. For
|
||||
# R >=3.2.0, this wrapper is not necessary.
|
||||
@@ -316,6 +326,15 @@ resolve <- function(dir, relpath) {
|
||||
return(abs.path)
|
||||
}
|
||||
|
||||
# Given a string, make sure it has a trailing slash.
|
||||
ensure_trailing_slash <- function(path) {
|
||||
if (!grepl("/$", path)) {
|
||||
path <- paste0(path, "/")
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
|
||||
isWindows <- function() .Platform$OS.type == 'windows'
|
||||
|
||||
# This is a wrapper for download.file and has the same interface.
|
||||
@@ -468,12 +487,12 @@ exprToFunction <- function(expr, env=parent.frame(), quoted=FALSE) {
|
||||
#' Installs an expression in the given environment as a function, and registers
|
||||
#' debug hooks so that breakpoints may be set in the function.
|
||||
#'
|
||||
#' This function can replace \code{exprToFunction} as follows: we may use
|
||||
#' \code{func <- exprToFunction(expr)} if we do not want the debug hooks, or
|
||||
#' \code{installExprFunction(expr, "func")} if we do. Both approaches create a
|
||||
#' function named \code{func} in the current environment.
|
||||
#' This function can replace `exprToFunction` as follows: we may use
|
||||
#' `func <- exprToFunction(expr)` if we do not want the debug hooks, or
|
||||
#' `installExprFunction(expr, "func")` if we do. Both approaches create a
|
||||
#' function named `func` in the current environment.
|
||||
#'
|
||||
#' @seealso Wraps \code{\link{exprToFunction}}; see that method's documentation
|
||||
#' @seealso Wraps [exprToFunction()]; see that method's documentation
|
||||
#' for more documentation and examples.
|
||||
#'
|
||||
#' @param expr A quoted or unquoted expression
|
||||
@@ -485,7 +504,7 @@ exprToFunction <- function(expr, env=parent.frame(), quoted=FALSE) {
|
||||
#' @param label A label for the object to be shown in the debugger. Defaults to
|
||||
#' the name of the calling function.
|
||||
#' @param wrappedWithLabel,..stacktraceon Advanced use only. For stack manipulation purposes; see
|
||||
#' \code{\link{stacktrace}}.
|
||||
#' [stacktrace()].
|
||||
#' @export
|
||||
installExprFunction <- function(expr, name, eval.env = parent.frame(2),
|
||||
quoted = FALSE,
|
||||
@@ -516,6 +535,7 @@ installExprFunction <- function(expr, name, eval.env = parent.frame(2),
|
||||
#'
|
||||
#' Returns a named list of key-value pairs.
|
||||
#'
|
||||
#' @noMd
|
||||
#' @param str The query string. It can have a leading \code{"?"} or not.
|
||||
#' @param nested Whether to parse the query string of as a nested list when it
|
||||
#' contains pairs of square brackets \code{[]}. For example, the query
|
||||
@@ -613,7 +633,7 @@ shinyCallingHandlers <- function(expr) {
|
||||
|
||||
#' Print message for deprecated functions in Shiny
|
||||
#'
|
||||
#' To disable these messages, use \code{options(shiny.deprecation.messages=FALSE)}.
|
||||
#' To disable these messages, use `options(shiny.deprecation.messages=FALSE)`.
|
||||
#'
|
||||
#' @param new Name of replacement function.
|
||||
#' @param msg Message to print. If used, this will override the default message.
|
||||
@@ -799,7 +819,14 @@ dataTablesJSON <- function(data, req) {
|
||||
|
||||
fdata <- unname(as.matrix(fdata))
|
||||
if (is.character(fdata) && q$escape != 'false') {
|
||||
if (q$escape == 'true') fdata <- htmlEscape(fdata) else {
|
||||
if (q$escape == 'true') {
|
||||
# fdata must be a matrix at this point, and we need to preserve
|
||||
# dimensions. Note that it could be a 1xn matrix.
|
||||
dims <- dim(fdata)
|
||||
fdata <- htmlEscape(fdata)
|
||||
dim(fdata) <- dims
|
||||
|
||||
} else {
|
||||
k <- as.integer(strsplit(q$escape, ',')[[1]])
|
||||
# use seq_len() in case escape = negative indices, e.g. c(-1, -5)
|
||||
for (j in seq_len(ncol(fdata))[k]) fdata[, j] <- htmlEscape(fdata[, j])
|
||||
@@ -949,28 +976,28 @@ columnToRowData <- function(data) {
|
||||
#'
|
||||
#' This should be used when you want to let the user see an error
|
||||
#' message even if the default is to sanitize all errors. If you have an
|
||||
#' error \code{e} and call \code{stop(safeError(e))}, then Shiny will
|
||||
#' ignore the value of \code{getOption("shiny.sanitize.errors")} and always
|
||||
#' error `e` and call `stop(safeError(e))`, then Shiny will
|
||||
#' ignore the value of `getOption("shiny.sanitize.errors")` and always
|
||||
#' display the error in the app itself.
|
||||
#'
|
||||
#' @param error Either an "error" object or a "character" object (string).
|
||||
#' In the latter case, the string will become the message of the error
|
||||
#' returned by \code{safeError}.
|
||||
#' returned by `safeError`.
|
||||
#'
|
||||
#' @return An "error" object
|
||||
#'
|
||||
#' @details An error generated by \code{safeError} has priority over all
|
||||
#' @details An error generated by `safeError` has priority over all
|
||||
#' other Shiny errors. This can be dangerous. For example, if you have set
|
||||
#' \code{options(shiny.sanitize.errors = TRUE)}, then by default all error
|
||||
#' `options(shiny.sanitize.errors = TRUE)`, then by default all error
|
||||
#' messages are omitted in the app, and replaced by a generic error message.
|
||||
#' However, this does not apply to \code{safeError}: whatever you pass
|
||||
#' through \code{error} will be displayed to the user. So, this should only
|
||||
#' However, this does not apply to `safeError`: whatever you pass
|
||||
#' through `error` will be displayed to the user. So, this should only
|
||||
#' be used when you are sure that your error message does not contain any
|
||||
#' sensitive information. In those situations, \code{safeError} can make
|
||||
#' sensitive information. In those situations, `safeError` can make
|
||||
#' your users' lives much easier by giving them a hint as to where the
|
||||
#' error occurred.
|
||||
#'
|
||||
#' @seealso \code{\link{shiny-options}}
|
||||
#' @seealso [shiny-options()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -1050,7 +1077,7 @@ safeError <- function(error) {
|
||||
# #' @examples
|
||||
# #' ## Note: the breaking of the reactive chain that happens in the app
|
||||
# #' ## below (when input$txt = 'bad' and input$allowBad = 'FALSE') is
|
||||
# #' ## easily visualized with `showReactLog()`
|
||||
# #' ## easily visualized with `reactlogShow()`
|
||||
# #'
|
||||
# #' ## Only run examples in interactive R sessions
|
||||
# #' if (interactive()) {
|
||||
@@ -1084,58 +1111,58 @@ reactiveStop <- function(message = "", class = NULL) {
|
||||
|
||||
#' Validate input values and other conditions
|
||||
#'
|
||||
#' For an output rendering function (e.g. \code{\link{renderPlot}()}), you may
|
||||
#' For an output rendering function (e.g. [renderPlot()]), you may
|
||||
#' need to check that certain input values are available and valid before you
|
||||
#' can render the output. \code{validate} gives you a convenient mechanism for
|
||||
#' can render the output. `validate` gives you a convenient mechanism for
|
||||
#' doing so.
|
||||
#'
|
||||
#' The \code{validate} function takes any number of (unnamed) arguments, each of
|
||||
#' The `validate` function takes any number of (unnamed) arguments, each of
|
||||
#' which represents a condition to test. If any of the conditions represent
|
||||
#' failure, then a special type of error is signaled which stops execution. If
|
||||
#' this error is not handled by application-specific code, it is displayed to
|
||||
#' the user by Shiny.
|
||||
#'
|
||||
#' An easy way to provide arguments to \code{validate} is to use the \code{need}
|
||||
#' An easy way to provide arguments to `validate` is to use the `need`
|
||||
#' function, which takes an expression and a string; if the expression is
|
||||
#' considered a failure, then the string will be used as the error message. The
|
||||
#' \code{need} function considers its expression to be a failure if it is any of
|
||||
#' `need` function considers its expression to be a failure if it is any of
|
||||
#' the following:
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \item{\code{FALSE}}
|
||||
#' \item{\code{NULL}}
|
||||
#' \item{\code{""}}
|
||||
#' \item{`FALSE`}
|
||||
#' \item{`NULL`}
|
||||
#' \item{`""`}
|
||||
#' \item{An empty atomic vector}
|
||||
#' \item{An atomic vector that contains only missing values}
|
||||
#' \item{A logical vector that contains all \code{FALSE} or missing values}
|
||||
#' \item{An object of class \code{"try-error"}}
|
||||
#' \item{A value that represents an unclicked \code{\link{actionButton}}}
|
||||
#' \item{A logical vector that contains all `FALSE` or missing values}
|
||||
#' \item{An object of class `"try-error"`}
|
||||
#' \item{A value that represents an unclicked [actionButton()]}
|
||||
#' }
|
||||
#'
|
||||
#' If any of these values happen to be valid, you can explicitly turn them to
|
||||
#' logical values. For example, if you allow \code{NA} but not \code{NULL}, you
|
||||
#' can use the condition \code{!is.null(input$foo)}, because \code{!is.null(NA)
|
||||
#' == TRUE}.
|
||||
#' logical values. For example, if you allow `NA` but not `NULL`, you
|
||||
#' can use the condition `!is.null(input$foo)`, because `!is.null(NA)
|
||||
#' == TRUE`.
|
||||
#'
|
||||
#' If you need validation logic that differs significantly from \code{need}, you
|
||||
#' If you need validation logic that differs significantly from `need`, you
|
||||
#' can create other validation test functions. A passing test should return
|
||||
#' \code{NULL}. A failing test should return an error message as a
|
||||
#' `NULL`. A failing test should return an error message as a
|
||||
#' single-element character vector, or if the failure should happen silently,
|
||||
#' \code{FALSE}.
|
||||
#' `FALSE`.
|
||||
#'
|
||||
#' Because validation failure is signaled as an error, you can use
|
||||
#' \code{validate} in reactive expressions, and validation failures will
|
||||
#' `validate` in reactive expressions, and validation failures will
|
||||
#' automatically propagate to outputs that use the reactive expression. In
|
||||
#' other words, if reactive expression \code{a} needs \code{input$x}, and two
|
||||
#' outputs use \code{a} (and thus depend indirectly on \code{input$x}), it's
|
||||
#' not necessary for the outputs to validate \code{input$x} explicitly, as long
|
||||
#' as \code{a} does validate it.
|
||||
#' other words, if reactive expression `a` needs `input$x`, and two
|
||||
#' outputs use `a` (and thus depend indirectly on `input$x`), it's
|
||||
#' not necessary for the outputs to validate `input$x` explicitly, as long
|
||||
#' as `a` does validate it.
|
||||
#'
|
||||
#' @param ... A list of tests. Each test should equal \code{NULL} for success,
|
||||
#' \code{FALSE} for silent failure, or a string for failure with an error
|
||||
#' @param ... A list of tests. Each test should equal `NULL` for success,
|
||||
#' `FALSE` for silent failure, or a string for failure with an error
|
||||
#' message.
|
||||
#' @param errorClass A CSS class to apply. The actual CSS string will have
|
||||
#' \code{shiny-output-error-} prepended to this value.
|
||||
#' `shiny-output-error-` prepended to this value.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -1187,10 +1214,10 @@ validate <- function(..., errorClass = character(0)) {
|
||||
#' @param expr An expression to test. The condition will pass if the expression
|
||||
#' meets the conditions spelled out in Details.
|
||||
#' @param message A message to convey to the user if the validation condition is
|
||||
#' not met. If no message is provided, one will be created using \code{label}.
|
||||
#' To fail with no message, use \code{FALSE} for the message.
|
||||
#' not met. If no message is provided, one will be created using `label`.
|
||||
#' To fail with no message, use `FALSE` for the message.
|
||||
#' @param label A human-readable name for the field that may be missing. This
|
||||
#' parameter is not needed if \code{message} is provided, but must be provided
|
||||
#' parameter is not needed if `message` is provided, but must be provided
|
||||
#' otherwise.
|
||||
#' @export
|
||||
#' @rdname validate
|
||||
@@ -1211,7 +1238,7 @@ need <- function(expr, message = paste(label, "must be provided"), label) {
|
||||
#' operation is stopped by raising a "silent" exception (not logged by Shiny,
|
||||
#' nor displayed in the Shiny app's UI).
|
||||
#'
|
||||
#' The \code{req} function was designed to be used in one of two ways. The first
|
||||
#' The `req` function was designed to be used in one of two ways. The first
|
||||
#' is to call it like a statement (ignoring its return value) before attempting
|
||||
#' operations using the required values:
|
||||
#'
|
||||
@@ -1221,9 +1248,9 @@ need <- function(expr, message = paste(label, "must be provided"), label) {
|
||||
#' # Code that uses input$a, input$b, and/or rv$state...
|
||||
#' })}
|
||||
#'
|
||||
#' In this example, if \code{r()} is called and any of \code{input$a},
|
||||
#' \code{input$b}, and \code{rv$state} are \code{NULL}, \code{FALSE}, \code{""},
|
||||
#' etc., then the \code{req} call will trigger an error that propagates all the
|
||||
#' In this example, if `r()` is called and any of `input$a`,
|
||||
#' `input$b`, and `rv$state` are `NULL`, `FALSE`, `""`,
|
||||
#' etc., then the `req` call will trigger an error that propagates all the
|
||||
#' way up to whatever render block or observer is executing.
|
||||
#'
|
||||
#' The second is to use it to wrap an expression that must be truthy:
|
||||
@@ -1236,70 +1263,70 @@ need <- function(expr, message = paste(label, "must be provided"), label) {
|
||||
#' }
|
||||
#' })}
|
||||
#'
|
||||
#' In this example, \code{req(input$plotType)} first checks that
|
||||
#' \code{input$plotType} is truthy, and if so, returns it. This is a convenient
|
||||
#' In this example, `req(input$plotType)` first checks that
|
||||
#' `input$plotType` is truthy, and if so, returns it. This is a convenient
|
||||
#' way to check for a value "inline" with its first use.
|
||||
#'
|
||||
#' \strong{Truthy and falsy values}
|
||||
#' **Truthy and falsy values**
|
||||
#'
|
||||
#' The terms "truthy" and "falsy" generally indicate whether a value, when
|
||||
#' coerced to a \code{\link[base]{logical}}, is \code{TRUE} or \code{FALSE}. We use
|
||||
#' coerced to a [base::logical()], is `TRUE` or `FALSE`. We use
|
||||
#' the term a little loosely here; our usage tries to match the intuitive
|
||||
#' notions of "Is this value missing or available?", or "Has the user provided
|
||||
#' an answer?", or in the case of action buttons, "Has the button been
|
||||
#' clicked?".
|
||||
#'
|
||||
#' For example, a \code{textInput} that has not been filled out by the user has
|
||||
#' a value of \code{""}, so that is considered a falsy value.
|
||||
#' For example, a `textInput` that has not been filled out by the user has
|
||||
#' a value of `""`, so that is considered a falsy value.
|
||||
#'
|
||||
#' To be precise, \code{req} considers a value truthy \emph{unless} it is one
|
||||
#' To be precise, `req` considers a value truthy *unless* it is one
|
||||
#' of:
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \item{\code{FALSE}}
|
||||
#' \item{\code{NULL}}
|
||||
#' \item{\code{""}}
|
||||
#' \item{`FALSE`}
|
||||
#' \item{`NULL`}
|
||||
#' \item{`""`}
|
||||
#' \item{An empty atomic vector}
|
||||
#' \item{An atomic vector that contains only missing values}
|
||||
#' \item{A logical vector that contains all \code{FALSE} or missing values}
|
||||
#' \item{An object of class \code{"try-error"}}
|
||||
#' \item{A value that represents an unclicked \code{\link{actionButton}}}
|
||||
#' \item{A logical vector that contains all `FALSE` or missing values}
|
||||
#' \item{An object of class `"try-error"`}
|
||||
#' \item{A value that represents an unclicked [actionButton()]}
|
||||
#' }
|
||||
#'
|
||||
#' Note in particular that the value \code{0} is considered truthy, even though
|
||||
#' \code{as.logical(0)} is \code{FALSE}.
|
||||
#' Note in particular that the value `0` is considered truthy, even though
|
||||
#' `as.logical(0)` is `FALSE`.
|
||||
#'
|
||||
#' If the built-in rules for truthiness do not match your requirements, you can
|
||||
#' always work around them. Since \code{FALSE} is falsy, you can simply provide
|
||||
#' the results of your own checks to \code{req}:
|
||||
#' always work around them. Since `FALSE` is falsy, you can simply provide
|
||||
#' the results of your own checks to `req`:
|
||||
#'
|
||||
#' \code{req(input$a != 0)}
|
||||
#' `req(input$a != 0)`
|
||||
#'
|
||||
#' \strong{Using \code{req(FALSE)}}
|
||||
#' **Using `req(FALSE)`**
|
||||
#'
|
||||
#' You can use \code{req(FALSE)} (i.e. no condition) if you've already performed
|
||||
#' You can use `req(FALSE)` (i.e. no condition) if you've already performed
|
||||
#' all the checks you needed to by that point and just want to stop the reactive
|
||||
#' chain now. There is no advantange to this, except perhaps ease of readibility
|
||||
#' if you have a complicated condition to check for (or perhaps if you'd like to
|
||||
#' divide your condition into nested \code{if} statements).
|
||||
#' divide your condition into nested `if` statements).
|
||||
#'
|
||||
#' \strong{Using \code{cancelOutput = TRUE}}
|
||||
#' **Using `cancelOutput = TRUE`**
|
||||
#'
|
||||
#' When \code{req(..., cancelOutput = TRUE)} is used, the "silent" exception is
|
||||
#' When `req(..., cancelOutput = TRUE)` is used, the "silent" exception is
|
||||
#' also raised, but it is treated slightly differently if one or more outputs are
|
||||
#' currently being evaluated. In those cases, the reactive chain does not proceed
|
||||
#' or update, but the output(s) are left is whatever state they happen to be in
|
||||
#' (whatever was their last valid state).
|
||||
#'
|
||||
#' Note that this is always going to be the case if
|
||||
#' this is used inside an output context (e.g. \code{output$txt <- ...}). It may
|
||||
#' this is used inside an output context (e.g. `output$txt <- ...`). It may
|
||||
#' or may not be the case if it is used inside a non-output context (e.g.
|
||||
#' \code{\link{reactive}}, \code{\link{observe}} or \code{\link{observeEvent}})
|
||||
#' -- depending on whether or not there is an \code{output$...} that is triggered
|
||||
#' [reactive()], [observe()] or [observeEvent()])
|
||||
#' --- depending on whether or not there is an `output$...` that is triggered
|
||||
#' as a result of those calls. See the examples below for concrete scenarios.
|
||||
#'
|
||||
#' @param ... Values to check for truthiness.
|
||||
#' @param cancelOutput If \code{TRUE} and an output is being evaluated, stop
|
||||
#' @param cancelOutput If `TRUE` and an output is being evaluated, stop
|
||||
#' processing as usual but instead of clearing the output, leave it in
|
||||
#' whatever state it happens to be in.
|
||||
#' @param x An expression whose truthiness value we want to determine
|
||||
@@ -1438,7 +1465,7 @@ stopWithCondition <- function(class, message) {
|
||||
#' This function returns the information about the current Shiny Server, such as
|
||||
#' its version, and whether it is the open source edition or professional
|
||||
#' edition. If the app is not served through the Shiny Server, this function
|
||||
#' just returns \code{list(shinyServer = FALSE)}.
|
||||
#' just returns `list(shinyServer = FALSE)`.
|
||||
#'
|
||||
#' This function will only return meaningful data when using Shiny Server
|
||||
#' version 1.2.2 or later.
|
||||
@@ -1560,6 +1587,24 @@ URLencode <- function(value, reserved = FALSE) {
|
||||
if (reserved) encodeURIComponent(value) else encodeURI(value)
|
||||
}
|
||||
|
||||
# Make user-supplied dates are either NULL or can be coerced
|
||||
# to a yyyy-mm-dd formatted string. If a date is specified, this
|
||||
# function returns a string for consistency across locales.
|
||||
# Also, `as.Date()` is used to coerce strings to date objects
|
||||
# so that strings like "2016-08-9" are expanded to "2016-08-09"
|
||||
dateYMD <- function(date = NULL, argName = "value") {
|
||||
if (!length(date)) return(NULL)
|
||||
tryCatch(date <- format(as.Date(date), "%Y-%m-%d"),
|
||||
error = function(e) {
|
||||
warning(
|
||||
"Couldn't coerce the `", argName,
|
||||
"` argument to a date string with format yyyy-mm-dd",
|
||||
call. = FALSE
|
||||
)
|
||||
}
|
||||
)
|
||||
date
|
||||
}
|
||||
|
||||
# This function takes a name and function, and it wraps that function in a new
|
||||
# function which calls the original function using the specified name. This can
|
||||
@@ -1730,6 +1775,7 @@ createVarPromiseDomain <- function(env, name, value) {
|
||||
|
||||
getSliderType <- function(min, max, value) {
|
||||
vals <- dropNulls(list(value, min, max))
|
||||
if (length(vals) == 0) return("")
|
||||
type <- unique(lapply(vals, function(x) {
|
||||
if (inherits(x, "Date")) "date"
|
||||
else if (inherits(x, "POSIXt")) "datetime"
|
||||
@@ -1740,3 +1786,109 @@ getSliderType <- function(min, max, value) {
|
||||
}
|
||||
type[[1]]
|
||||
}
|
||||
|
||||
# Reads the `shiny.sharedSecret` global option, and returns a function that can
|
||||
# be used to test header values for a match.
|
||||
loadSharedSecret <- function() {
|
||||
normalizeToRaw <- function(value, label = "value") {
|
||||
if (is.null(value)) {
|
||||
raw()
|
||||
} else if (is.character(value)) {
|
||||
charToRaw(paste(value, collapse = "\n"))
|
||||
} else if (is.raw(value)) {
|
||||
value
|
||||
} else {
|
||||
stop("Wrong type for ", label, "; character or raw expected")
|
||||
}
|
||||
}
|
||||
|
||||
sharedSecret <- normalizeToRaw(getOption("shiny.sharedSecret"))
|
||||
if (is.null(sharedSecret)) {
|
||||
function(x) TRUE
|
||||
} else {
|
||||
# We compare the digest of the two values so that their lengths are equalized
|
||||
function(x) {
|
||||
x <- normalizeToRaw(x)
|
||||
# Constant time comparison to avoid timing attacks
|
||||
constantTimeEquals(sharedSecret, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Compares two raw vectors of equal length for equality, in constant time
|
||||
constantTimeEquals <- function(raw1, raw2) {
|
||||
stopifnot(is.raw(raw1))
|
||||
stopifnot(is.raw(raw2))
|
||||
if (length(raw1) != length(raw2)) {
|
||||
return(FALSE)
|
||||
}
|
||||
|
||||
sum(as.integer(xor(raw1, raw2))) == 0
|
||||
}
|
||||
|
||||
cat_line <- function(...) {
|
||||
cat(paste(..., "\n", collapse = ""))
|
||||
}
|
||||
|
||||
select_menu <- function(choices, title = NULL, msg = "Enter one or more numbers (with spaces), or an empty line to exit: \n")
|
||||
{
|
||||
if (!is.null(title)) {
|
||||
cat(title, "\n", sep = "")
|
||||
}
|
||||
nc <- length(choices)
|
||||
op <- paste0(format(seq_len(nc)), ": ", choices)
|
||||
fop <- format(op)
|
||||
cat("", fop, "", sep = "\n")
|
||||
repeat {
|
||||
answer <- readline(msg)
|
||||
answer <- strsplit(answer, "[ ,]+")[[1]]
|
||||
if (all(answer %in% seq_along(choices))) {
|
||||
return(choices[as.integer(answer)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#' @noRd
|
||||
isAppDir <- function(path) {
|
||||
|
||||
if (file.exists(file.path.ci(path, "app.R")))
|
||||
return(TRUE)
|
||||
|
||||
if (file.exists(file.path.ci(path, "server.R"))
|
||||
&& file.exists(file.path.ci(path, "ui.R")))
|
||||
return(TRUE)
|
||||
|
||||
FALSE
|
||||
}
|
||||
|
||||
# Borrowed from rprojroot which borrowed from devtools
|
||||
#' @noRd
|
||||
is_root <- function(path) {
|
||||
identical(
|
||||
normalizePath(path, winslash = "/"),
|
||||
normalizePath(dirname(path), winslash = "/")
|
||||
)
|
||||
}
|
||||
|
||||
#' @noRd
|
||||
findEnclosingApp <- function(path = ".") {
|
||||
orig_path <- path
|
||||
path <- normalizePath(path, winslash = "/", mustWork = TRUE)
|
||||
repeat {
|
||||
if (isAppDir(path))
|
||||
return(path)
|
||||
if (is_root(path))
|
||||
stop("Shiny app not found at ", orig_path, " or in any parent directory.")
|
||||
path <- dirname(path)
|
||||
}
|
||||
}
|
||||
|
||||
# Check if a package is installed, and if version is specified,
|
||||
# that we have at least that version
|
||||
is_available <- function(package, version = NULL) {
|
||||
installed <- nzchar(system.file(package = package))
|
||||
if (is.null(version)) {
|
||||
return(installed)
|
||||
}
|
||||
installed && isTRUE(utils::packageVersion(package) >= version)
|
||||
}
|
||||
|
||||
56
R/viewer.R
Normal file
56
R/viewer.R
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
#' 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
|
||||
#' [utils::browseURL()]).
|
||||
#'
|
||||
#' @return A function that takes a single `url` parameter, suitable for
|
||||
#' passing as the `viewer` argument of [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 `NULL`, use the existing viewer pane
|
||||
#' size. If `"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 [utils::browseURL()].
|
||||
#' @rdname viewer
|
||||
#' @export
|
||||
browserViewer <- function(browser = getOption("browser")) {
|
||||
function(url) {
|
||||
utils::browseURL(url, browser = browser)
|
||||
}
|
||||
}
|
||||
22
README.md
22
README.md
@@ -1,8 +1,11 @@
|
||||
# Shiny
|
||||
# shiny <img src="man/figures/logo.png" align="right" width=120 height=139 alt="" />
|
||||
|
||||
*Travis:* [](https://travis-ci.org/rstudio/shiny)
|
||||
<!-- badges: start -->
|
||||
[](https://CRAN.R-project.org/package=shiny)
|
||||
[](https://github.com/rstudio/shiny/actions)
|
||||
[](https://community.rstudio.com/new-topic?category=shiny&tags=shiny)
|
||||
|
||||
*AppVeyor:* [](https://ci.appveyor.com/project/rstudio/shiny)
|
||||
<!-- badges: end -->
|
||||
|
||||
Shiny is a new package from RStudio that makes it incredibly easy to build interactive web applications with R.
|
||||
|
||||
@@ -34,9 +37,9 @@ install.packages("shiny")
|
||||
To install the latest development builds directly from GitHub, run this instead:
|
||||
|
||||
```r
|
||||
if (!require("devtools"))
|
||||
install.packages("devtools")
|
||||
devtools::install_github("rstudio/shiny")
|
||||
if (!require("remotes"))
|
||||
install.packages("remotes")
|
||||
remotes::install_github("rstudio/shiny")
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
@@ -61,11 +64,8 @@ The Javascript code in Shiny is minified using tools that run on Node.js. See th
|
||||
|
||||
## 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.
|
||||
We welcome contributions to the **shiny** package. Please see our [CONTRIBUTING.md](https://github.com/rstudio/shiny/blob/master/.github/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:
|
||||
|
||||
- COPYING - shiny package license (GPLv3)
|
||||
- NOTICE - Copyright notices for additional included software
|
||||
The shiny package as a whole is licensed under the GPLv3. See the [LICENSE](LICENSE) file for more details.
|
||||
|
||||
49
appveyor.yml
49
appveyor.yml
@@ -1,49 +0,0 @@
|
||||
# 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
|
||||
27
inst/app_template/R/example-module.R
Normal file
27
inst/app_template/R/example-module.R
Normal file
@@ -0,0 +1,27 @@
|
||||
exampleModuleUI <- function(id, label = "Counter") {
|
||||
# All uses of Shiny input/output IDs in the UI must be namespaced,
|
||||
# as in ns("x").
|
||||
ns <- NS(id)
|
||||
tagList(
|
||||
actionButton(ns("button"), label = label),
|
||||
verbatimTextOutput(ns("out"))
|
||||
)
|
||||
}
|
||||
|
||||
exampleModuleServer <- function(id) {
|
||||
# moduleServer() wraps a function to create the server component of a
|
||||
# module.
|
||||
moduleServer(
|
||||
id,
|
||||
function(input, output, session) {
|
||||
count <- reactiveVal(0)
|
||||
observeEvent(input$button, {
|
||||
count(count() + 1)
|
||||
})
|
||||
output$out <- renderText({
|
||||
count()
|
||||
})
|
||||
count
|
||||
}
|
||||
)
|
||||
}
|
||||
5
inst/app_template/R/example.R
Normal file
5
inst/app_template/R/example.R
Normal file
@@ -0,0 +1,5 @@
|
||||
# Given a numeric vector, convert to strings, sort, and convert back to
|
||||
# numeric.
|
||||
lexical_sort <- function(x) {
|
||||
as.numeric(sort(as.character(x)))
|
||||
}
|
||||
56
inst/app_template/app.R
Normal file
56
inst/app_template/app.R
Normal file
@@ -0,0 +1,56 @@
|
||||
ui <- fluidPage(
|
||||
{{
|
||||
# These blocks of code are processed with htmlTemplate()
|
||||
if (isTRUE(module)) {
|
||||
' # ======== Modules ========
|
||||
# exampleModuleUI is defined in R/example-module.R
|
||||
wellPanel(
|
||||
h2("Modules example"),
|
||||
exampleModuleUI("examplemodule1", "Click counter #1"),
|
||||
exampleModuleUI("examplemodule2", "Click counter #2")
|
||||
),
|
||||
# =========================
|
||||
'
|
||||
}
|
||||
}}
|
||||
wellPanel(
|
||||
h2("Sorting example"),
|
||||
sliderInput("size", "Data size", min = 5, max = 20, value = 10),
|
||||
{{
|
||||
if (isTRUE(rdir)) {
|
||||
' div("Lexically sorted sequence:"),'
|
||||
} else {
|
||||
' div("Sorted sequence:"),'
|
||||
}
|
||||
}}
|
||||
verbatimTextOutput("sequence")
|
||||
)
|
||||
)
|
||||
|
||||
server <- function(input, output, session) {
|
||||
{{
|
||||
if (isTRUE(module)) {
|
||||
' # ======== Modules ========
|
||||
# exampleModuleServer is defined in R/example-module.R
|
||||
exampleModuleServer("examplemodule1")
|
||||
exampleModuleServer("examplemodule2")
|
||||
# =========================
|
||||
'
|
||||
}
|
||||
}}
|
||||
data <- reactive({
|
||||
{{
|
||||
if (isTRUE(rdir)) {
|
||||
' # lexical_sort from R/example.R
|
||||
lexical_sort(seq_len(input$size))'
|
||||
} else {
|
||||
' sort(seq_len(input$size))'
|
||||
}
|
||||
}}
|
||||
})
|
||||
output$sequence <- renderText({
|
||||
paste(data(), collapse = " ")
|
||||
})
|
||||
}
|
||||
|
||||
shinyApp(ui, server)
|
||||
3
inst/app_template/tests/shinytest.R
Normal file
3
inst/app_template/tests/shinytest.R
Normal file
@@ -0,0 +1,3 @@
|
||||
library(shinytest)
|
||||
shinytest::testApp("../")
|
||||
|
||||
12
inst/app_template/tests/shinytest/mytest.R
Normal file
12
inst/app_template/tests/shinytest/mytest.R
Normal file
@@ -0,0 +1,12 @@
|
||||
app <- ShinyDriver$new("../../")
|
||||
app$snapshotInit("mytest")
|
||||
|
||||
app$snapshot()
|
||||
{{
|
||||
if (isTRUE(module)) {
|
||||
'
|
||||
app$setInputs(`examplemodule1-button` = "click")
|
||||
app$setInputs(`examplemodule1-button` = "click")
|
||||
app$snapshot()'
|
||||
}
|
||||
}}
|
||||
9
inst/app_template/tests/testthat.R
Normal file
9
inst/app_template/tests/testthat.R
Normal file
@@ -0,0 +1,9 @@
|
||||
library(testthat)
|
||||
|
||||
test_dir(
|
||||
"./testthat",
|
||||
# Run in the app's environment containing all support methods.
|
||||
env = shiny::loadSupport(),
|
||||
# Display the regular progress output and throw an error if any test error is found
|
||||
reporter = c("progress", "fail")
|
||||
)
|
||||
18
inst/app_template/tests/testthat/test-examplemodule.R
Normal file
18
inst/app_template/tests/testthat/test-examplemodule.R
Normal file
@@ -0,0 +1,18 @@
|
||||
context("exampleModuleServer")
|
||||
|
||||
# See ?testServer for more information
|
||||
testServer(exampleModuleServer, {
|
||||
# Set initial value of a button
|
||||
session$setInputs(button = 0)
|
||||
|
||||
# Check the value of the reactiveVal `count()`
|
||||
expect_equal(count(), 1)
|
||||
# Check the value of the renderText()
|
||||
expect_equal(output$out, "1")
|
||||
|
||||
# Simulate a click
|
||||
session$setInputs(button = 1)
|
||||
|
||||
expect_equal(count(), 2)
|
||||
expect_equal(output$out, "2")
|
||||
})
|
||||
20
inst/app_template/tests/testthat/test-server.R
Normal file
20
inst/app_template/tests/testthat/test-server.R
Normal file
@@ -0,0 +1,20 @@
|
||||
context("app")
|
||||
|
||||
testServer(expr = {
|
||||
# Set the `size` slider and check the output
|
||||
session$setInputs(size = 6)
|
||||
expect_equal(output$sequence, "1 2 3 4 5 6")
|
||||
{{
|
||||
if (isTRUE(rdir)) {
|
||||
'
|
||||
session$setInputs(size = 12)
|
||||
expect_equal(output$sequence, "1 10 11 12 2 3 4 5 6 7 8 9")
|
||||
'
|
||||
} else {
|
||||
'
|
||||
session$setInputs(size = 12)
|
||||
expect_equal(output$sequence, "1 2 3 4 5 6 7 8 9 10 11 12")
|
||||
'
|
||||
}
|
||||
}}
|
||||
})
|
||||
7
inst/app_template/tests/testthat/test-sort.R
Normal file
7
inst/app_template/tests/testthat/test-sort.R
Normal file
@@ -0,0 +1,7 @@
|
||||
# Test the lexical_sort function from R/example.R
|
||||
context("sort")
|
||||
|
||||
test_that("Lexical sorting works", {
|
||||
expect_equal(lexical_sort(c(1, 2, 3)), c(1, 2, 3))
|
||||
expect_equal(lexical_sort(c(1, 2, 3, 13, 11, 21)), c(1, 11, 13, 2, 21, 3))
|
||||
})
|
||||
@@ -1,243 +0,0 @@
|
||||
sd_section("UI Layout",
|
||||
"Functions for laying out the user interface for your application.",
|
||||
c(
|
||||
"absolutePanel",
|
||||
"bootstrapPage",
|
||||
"column",
|
||||
"conditionalPanel",
|
||||
"fillPage",
|
||||
"fillRow",
|
||||
"fixedPage",
|
||||
"fluidPage",
|
||||
"headerPanel",
|
||||
"helpText",
|
||||
"icon",
|
||||
"mainPanel",
|
||||
"navbarPage",
|
||||
"navlistPanel",
|
||||
"pageWithSidebar",
|
||||
"sidebarLayout",
|
||||
"sidebarPanel",
|
||||
"tabPanel",
|
||||
"tabsetPanel",
|
||||
"titlePanel",
|
||||
"inputPanel",
|
||||
"flowLayout",
|
||||
"splitLayout",
|
||||
"verticalLayout",
|
||||
"wellPanel",
|
||||
"withMathJax"
|
||||
)
|
||||
)
|
||||
sd_section("UI Inputs",
|
||||
"Functions for creating user interface elements that prompt the user for input values or interaction.",
|
||||
c(
|
||||
"actionButton",
|
||||
"checkboxGroupInput",
|
||||
"checkboxInput",
|
||||
"dateInput",
|
||||
"dateRangeInput",
|
||||
"fileInput",
|
||||
"numericInput",
|
||||
"radioButtons",
|
||||
"selectInput",
|
||||
"varSelectInput",
|
||||
"sliderInput",
|
||||
"submitButton",
|
||||
"textInput",
|
||||
"textAreaInput",
|
||||
"passwordInput",
|
||||
"modalButton",
|
||||
"updateActionButton",
|
||||
"updateCheckboxGroupInput",
|
||||
"updateCheckboxInput",
|
||||
"updateDateInput",
|
||||
"updateDateRangeInput",
|
||||
"updateNumericInput",
|
||||
"updateRadioButtons",
|
||||
"updateSelectInput",
|
||||
"updateSliderInput",
|
||||
"updateTabsetPanel",
|
||||
"insertTab",
|
||||
"showTab",
|
||||
"updateTextInput",
|
||||
"updateTextAreaInput",
|
||||
"updateQueryString",
|
||||
"getQueryString"
|
||||
)
|
||||
)
|
||||
sd_section("UI Outputs",
|
||||
"Functions for creating user interface elements that, in conjunction with rendering functions, display different kinds of output from your application.",
|
||||
c(
|
||||
"htmlOutput",
|
||||
"plotOutput",
|
||||
"outputOptions",
|
||||
"tableOutput",
|
||||
"textOutput",
|
||||
"verbatimTextOutput",
|
||||
"downloadButton",
|
||||
"Progress",
|
||||
"withProgress",
|
||||
"modalDialog",
|
||||
"urlModal",
|
||||
"showModal",
|
||||
"showNotification"
|
||||
)
|
||||
)
|
||||
sd_section("Interface builder functions",
|
||||
"A sub-library for writing HTML using R functions. These functions form the foundation on which the higher level user interface functions are built, and can also be used in your Shiny UI to provide custom HTML, CSS, and JavaScript.",
|
||||
c(
|
||||
"builder",
|
||||
"HTML",
|
||||
"include",
|
||||
"singleton",
|
||||
"tag",
|
||||
"validateCssUnit",
|
||||
"withTags",
|
||||
"htmlTemplate",
|
||||
"bootstrapLib",
|
||||
"suppressDependencies",
|
||||
"insertUI",
|
||||
"removeUI"
|
||||
)
|
||||
)
|
||||
sd_section("Rendering functions",
|
||||
"Functions that you use in your application's server side code, assigning them to outputs that appear in your user interface.",
|
||||
c(
|
||||
"renderPlot",
|
||||
"renderCachedPlot",
|
||||
"renderText",
|
||||
"renderPrint",
|
||||
"renderDataTable",
|
||||
"renderImage",
|
||||
"renderTable",
|
||||
"renderUI",
|
||||
"downloadHandler",
|
||||
"reactivePlot",
|
||||
"reactivePrint",
|
||||
"reactiveTable",
|
||||
"reactiveText",
|
||||
"reactiveUI",
|
||||
"createRenderFunction"
|
||||
)
|
||||
)
|
||||
sd_section("Reactive programming",
|
||||
"A sub-library that provides reactive programming facilities for R.",
|
||||
c(
|
||||
"reactive",
|
||||
"observe",
|
||||
"observeEvent",
|
||||
"reactiveVal",
|
||||
"reactiveValues",
|
||||
"reactiveValuesToList",
|
||||
"is.reactivevalues",
|
||||
"isolate",
|
||||
"invalidateLater",
|
||||
"debounce",
|
||||
"showReactLog",
|
||||
"makeReactiveBinding",
|
||||
"reactiveFileReader",
|
||||
"reactivePoll",
|
||||
"reactiveTimer",
|
||||
"domains",
|
||||
"freezeReactiveValue"
|
||||
)
|
||||
)
|
||||
sd_section("Boilerplate",
|
||||
"Functions that are required boilerplate in ui.R and server.R.",
|
||||
c(
|
||||
"shinyUI",
|
||||
"shinyServer"
|
||||
)
|
||||
)
|
||||
sd_section("Running",
|
||||
"Functions that are used to run or stop Shiny applications.",
|
||||
c(
|
||||
"runApp",
|
||||
"runGadget",
|
||||
"runExample",
|
||||
"runGadget",
|
||||
"runUrl",
|
||||
"stopApp",
|
||||
"viewer",
|
||||
"isRunning"
|
||||
)
|
||||
)
|
||||
sd_section("Bookmarking state",
|
||||
"Functions that are used for bookmarking and restoring state.",
|
||||
c(
|
||||
"bookmarkButton",
|
||||
"enableBookmarking",
|
||||
"setBookmarkExclude",
|
||||
"showBookmarkUrlModal",
|
||||
"onBookmark"
|
||||
)
|
||||
)
|
||||
sd_section("Extending Shiny",
|
||||
"Functions that are intended to be called by third-party packages that extend Shiny.",
|
||||
c(
|
||||
"createWebDependency",
|
||||
"addResourcePath",
|
||||
"registerInputHandler",
|
||||
"removeInputHandler",
|
||||
"markRenderFunction"
|
||||
)
|
||||
)
|
||||
sd_section("Utility functions",
|
||||
"Miscellaneous utilities that may be useful to advanced users or when extending Shiny.",
|
||||
c(
|
||||
"req",
|
||||
"validate",
|
||||
"session",
|
||||
"shinyOptions",
|
||||
"safeError",
|
||||
"onFlush",
|
||||
"restoreInput",
|
||||
"applyInputHandlers",
|
||||
"exprToFunction",
|
||||
"installExprFunction",
|
||||
"parseQueryString",
|
||||
"getCurrentOutputInfo",
|
||||
"plotPNG",
|
||||
"sizeGrowthRatio",
|
||||
"exportTestValues",
|
||||
"setSerializer",
|
||||
"snapshotExclude",
|
||||
"snapshotPreprocessInput",
|
||||
"snapshotPreprocessOutput",
|
||||
"markOutputAttrs",
|
||||
"repeatable",
|
||||
"shinyDeprecated",
|
||||
"serverInfo",
|
||||
"shiny-options",
|
||||
"onStop",
|
||||
"diskCache",
|
||||
"memoryCache",
|
||||
"key_missing"
|
||||
)
|
||||
)
|
||||
sd_section("Plot interaction",
|
||||
"Functions related to interactive plots",
|
||||
c(
|
||||
"brushedPoints",
|
||||
"brushOpts",
|
||||
"clickOpts",
|
||||
"dblclickOpts",
|
||||
"hoverOpts",
|
||||
"nearPoints"
|
||||
)
|
||||
)
|
||||
sd_section("Modules",
|
||||
"Functions for modularizing Shiny apps",
|
||||
c(
|
||||
"NS",
|
||||
"callModule"
|
||||
)
|
||||
)
|
||||
sd_section("Embedding",
|
||||
"Functions that are intended for third-party packages that embed Shiny applications.",
|
||||
c(
|
||||
"shinyApp",
|
||||
"maskReactiveContext"
|
||||
)
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html{{ if (isTRUE(nzchar(lang))) paste0(" lang=\"", lang, "\"") }}>
|
||||
<head>
|
||||
{{ headContent() }}
|
||||
</head>
|
||||
|
||||
13
inst/template/error.html
Normal file
13
inst/template/error.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<html>
|
||||
|
||||
<head lang = "en">
|
||||
<title>An error has occurred</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1>An error has occurred!</h1>
|
||||
<p>{{message}}</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user