mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 07:58:11 -05:00
Compare commits
1231 Commits
v0.1.4
...
htmltools-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9fc873c8d | ||
|
|
0153349979 | ||
|
|
d227842414 | ||
|
|
b6a2122a41 | ||
|
|
a0df8f3490 | ||
|
|
6c14789362 | ||
|
|
880a12b914 | ||
|
|
93d69400e6 | ||
|
|
d4829e49ea | ||
|
|
1c56be3a6b | ||
|
|
07a0dfddc7 | ||
|
|
b86f9086ef | ||
|
|
343ca12c6f | ||
|
|
af3c4f84b6 | ||
|
|
3679e8795f | ||
|
|
39b4805a76 | ||
|
|
3bdcdf96d4 | ||
|
|
b54e5d33bc | ||
|
|
85e020a513 | ||
|
|
5b6268f5bc | ||
|
|
f8b38e4683 | ||
|
|
18e85c32b4 | ||
|
|
831fba9a53 | ||
|
|
b1f233cd8c | ||
|
|
3d0caba695 | ||
|
|
79c92f1f8e | ||
|
|
78f87d9003 | ||
|
|
87f26e47bb | ||
|
|
9d8d04ae28 | ||
|
|
a42f046ff8 | ||
|
|
0824726dbb | ||
|
|
f55155404a | ||
|
|
b711bb553f | ||
|
|
2a36179bdc | ||
|
|
e57221861f | ||
|
|
b00fbda1ae | ||
|
|
357e81aeca | ||
|
|
3189c748b5 | ||
|
|
2700643cbf | ||
|
|
ff628ac0b2 | ||
|
|
f21aefe9e9 | ||
|
|
8babbd69d8 | ||
|
|
11bf02eb56 | ||
|
|
f5fa7d6d4b | ||
|
|
77bff6e6c2 | ||
|
|
e84a76cebd | ||
|
|
342265be94 | ||
|
|
62ec9291d8 | ||
|
|
dee6fbcb8f | ||
|
|
72fa9a2dcb | ||
|
|
ca27a9e31a | ||
|
|
18d0f45cf9 | ||
|
|
424fd515a4 | ||
|
|
00b40d64a1 | ||
|
|
3a7d0a5a9f | ||
|
|
57a02318e3 | ||
|
|
8f6d8cf0d6 | ||
|
|
5b6605b296 | ||
|
|
4d83596595 | ||
|
|
7e12a281f5 | ||
|
|
c63c10e48a | ||
|
|
155554f0b7 | ||
|
|
26b0836756 | ||
|
|
a87dc9bab2 | ||
|
|
9c1555a110 | ||
|
|
fbda2db884 | ||
|
|
2a229774ef | ||
|
|
137e5b13ef | ||
|
|
7920d66cd0 | ||
|
|
9f2dae7f3b | ||
|
|
ffde0ad1f5 | ||
|
|
2c2658a8ec | ||
|
|
6f2f8f6f7a | ||
|
|
4b6dcdd1b0 | ||
|
|
de346fd6c3 | ||
|
|
bf9d7c2012 | ||
|
|
143803f86d | ||
|
|
311143451d | ||
|
|
c9030f401d | ||
|
|
8668ddce74 | ||
|
|
7a495357f7 | ||
|
|
13864a811d | ||
|
|
5b65e4b250 | ||
|
|
dfe4a80501 | ||
|
|
bf82b9742a | ||
|
|
829a466f72 | ||
|
|
1206c70c42 | ||
|
|
3c32c349b9 | ||
|
|
0709f08d65 | ||
|
|
50f78c6e40 | ||
|
|
7e7afc6d38 | ||
|
|
1130eadac8 | ||
|
|
959fc2bbb2 | ||
|
|
f8ae505011 | ||
|
|
cd183a1926 | ||
|
|
bb2796fbc3 | ||
|
|
5de7103890 | ||
|
|
a78c91ba7e | ||
|
|
fca50da57b | ||
|
|
61f2c908b1 | ||
|
|
4c096ac068 | ||
|
|
2c95678be1 | ||
|
|
1a643cecf3 | ||
|
|
aa10b2e8c4 | ||
|
|
0b9317d047 | ||
|
|
4d58f05f38 | ||
|
|
6e879c8156 | ||
|
|
b6ee67aa41 | ||
|
|
07bed0c7c7 | ||
|
|
d2bd59d149 | ||
|
|
7bdac5a44e | ||
|
|
51f5db4374 | ||
|
|
e395ae6555 | ||
|
|
1df9c498cf | ||
|
|
57b3b919a5 | ||
|
|
00c6bbb297 | ||
|
|
b6536a0af3 | ||
|
|
d08a2507fa | ||
|
|
8bc8829577 | ||
|
|
c843e6f68c | ||
|
|
84583e5501 | ||
|
|
4548562138 | ||
|
|
32c170b10a | ||
|
|
97dafa0a55 | ||
|
|
0be1ee46f2 | ||
|
|
34c9ab7643 | ||
|
|
59dbca250f | ||
|
|
4028dbfda1 | ||
|
|
b9dbf610b0 | ||
|
|
d443810520 | ||
|
|
fcd941d33d | ||
|
|
9c063fa37c | ||
|
|
2720cfe346 | ||
|
|
c39e38081e | ||
|
|
3deb4c3f42 | ||
|
|
6945091238 | ||
|
|
c758c4785a | ||
|
|
19269a20fb | ||
|
|
45669cacb1 | ||
|
|
840bc52aae | ||
|
|
bbc36e349f | ||
|
|
a4325adcdd | ||
|
|
23f39649d0 | ||
|
|
87b09a534e | ||
|
|
39f0e5ae0c | ||
|
|
62aaab0926 | ||
|
|
cddfe999aa | ||
|
|
fcbb658ac2 | ||
|
|
3bbf06ba49 | ||
|
|
d9be6f1d2e | ||
|
|
5d70e68a0b | ||
|
|
529f2325b2 | ||
|
|
314d433f86 | ||
|
|
12ea950c5f | ||
|
|
f4d12220ca | ||
|
|
6a9cba90f4 | ||
|
|
6873e1f1cb | ||
|
|
fa0a91a75d | ||
|
|
020bb659c5 | ||
|
|
b1d6687fb0 | ||
|
|
f67e17b287 | ||
|
|
81bd57c5ea | ||
|
|
d803bae874 | ||
|
|
14606f4087 | ||
|
|
599fdc7ee5 | ||
|
|
722e205db5 | ||
|
|
f67849eb47 | ||
|
|
662ca4e40a | ||
|
|
aa61be74d8 | ||
|
|
10296fcd6b | ||
|
|
f8bf146b6c | ||
|
|
52f104c517 | ||
|
|
6c1fc224f0 | ||
|
|
6b9ae3a8b3 | ||
|
|
07f73030c6 | ||
|
|
47130c79ee | ||
|
|
f3a3bdfe4f | ||
|
|
e5e54fe4c1 | ||
|
|
29c0f9a43a | ||
|
|
0b78229c77 | ||
|
|
c2a1d70070 | ||
|
|
260ecd1d9f | ||
|
|
3dce2e761a | ||
|
|
80a54200ce | ||
|
|
51227d438a | ||
|
|
6fb4199d37 | ||
|
|
6ba46aff6b | ||
|
|
5da34d0646 | ||
|
|
f215088939 | ||
|
|
df34dcdb0c | ||
|
|
89f464af99 | ||
|
|
3f6f02f7d2 | ||
|
|
0d861e5389 | ||
|
|
b290c8700c | ||
|
|
81b6fbe263 | ||
|
|
b3af293f66 | ||
|
|
b187485172 | ||
|
|
b449d9759c | ||
|
|
d9d63a3a2e | ||
|
|
fd7b54fb77 | ||
|
|
887f8a606d | ||
|
|
7e3717243f | ||
|
|
221849aa3a | ||
|
|
b52d40ab28 | ||
|
|
3ed68ffd92 | ||
|
|
cc3cd2c141 | ||
|
|
5e30f7efc4 | ||
|
|
35090251ef | ||
|
|
338afb4893 | ||
|
|
194d8a05f8 | ||
|
|
93e276bd9b | ||
|
|
a69517519c | ||
|
|
f646b1efb4 | ||
|
|
fc9bedacc0 | ||
|
|
795eeee809 | ||
|
|
6d7818962e | ||
|
|
068517c933 | ||
|
|
5b030200df | ||
|
|
c732122966 | ||
|
|
d7eb9b2d18 | ||
|
|
b8b09adda1 | ||
|
|
07c8f0c4b7 | ||
|
|
2bd201de63 | ||
|
|
0b7e118a37 | ||
|
|
a546769225 | ||
|
|
81745f932d | ||
|
|
4415bf31d2 | ||
|
|
5c1bcb41d8 | ||
|
|
b659c4c2bb | ||
|
|
65adc8a405 | ||
|
|
4141f78717 | ||
|
|
80cb02d206 | ||
|
|
a5a4510a1e | ||
|
|
95c30649d3 | ||
|
|
8e5cbde08c | ||
|
|
6df8632e29 | ||
|
|
3c1218fff1 | ||
|
|
69c0414791 | ||
|
|
d63f83fcbb | ||
|
|
75c3bf0c2f | ||
|
|
c9a8ab2389 | ||
|
|
2c467c00e1 | ||
|
|
c63ec5a1f2 | ||
|
|
e886558cbb | ||
|
|
8dd6dabe50 | ||
|
|
c090c6adf9 | ||
|
|
84da0befcd | ||
|
|
267751c8b9 | ||
|
|
8add9f7188 | ||
|
|
a100b0991b | ||
|
|
9ce9c5e535 | ||
|
|
b2d004ca1a | ||
|
|
657d50f9a3 | ||
|
|
60e355c4f5 | ||
|
|
adb444a60f | ||
|
|
e7e13ff70d | ||
|
|
a1e81db597 | ||
|
|
f23f2ff0a0 | ||
|
|
c1b18098f1 | ||
|
|
31c39592e3 | ||
|
|
82a1dad22a | ||
|
|
1ecec24727 | ||
|
|
607841e947 | ||
|
|
e234b403ae | ||
|
|
80ce7a36f8 | ||
|
|
705a8666be | ||
|
|
9167905118 | ||
|
|
bdeb6734d8 | ||
|
|
9a7b042594 | ||
|
|
7aea256fd8 | ||
|
|
857b5e6932 | ||
|
|
1a2d675439 | ||
|
|
0c749643de | ||
|
|
09bb1548f9 | ||
|
|
5ffe531844 | ||
|
|
fab24a3200 | ||
|
|
899d5e9d1d | ||
|
|
ba510884f2 | ||
|
|
78e8df8e17 | ||
|
|
deba1609c3 | ||
|
|
88d2425ca3 | ||
|
|
7117f9e058 | ||
|
|
c21c407416 | ||
|
|
4b4ad42063 | ||
|
|
474d514c7d | ||
|
|
6239466da8 | ||
|
|
7746d75582 | ||
|
|
642c9ded08 | ||
|
|
e0ae931ddd | ||
|
|
0d7727a405 | ||
|
|
28f689498a | ||
|
|
eb8fec7f2d | ||
|
|
2e16fa1d70 | ||
|
|
1b856c4909 | ||
|
|
585ad30af1 | ||
|
|
c0cdc4083c | ||
|
|
9b9db4f161 | ||
|
|
84a1d8d25e | ||
|
|
d3115a3bf3 | ||
|
|
964789e9a6 | ||
|
|
eeded51ff8 | ||
|
|
8f24f1b4d6 | ||
|
|
ad910a295a | ||
|
|
cf14c6b1e9 | ||
|
|
49da114caa | ||
|
|
b8376ebbf7 | ||
|
|
29701d7295 | ||
|
|
16279695a9 | ||
|
|
999fc86bc6 | ||
|
|
0276d533fb | ||
|
|
b77fc34a7b | ||
|
|
60c450d57e | ||
|
|
73411c75db | ||
|
|
8d146f7dff | ||
|
|
5c34aa0bb5 | ||
|
|
2b2ed8162d | ||
|
|
9770bd8005 | ||
|
|
4e020818ae | ||
|
|
58471c6971 | ||
|
|
2a2e02bf56 | ||
|
|
75d8cee766 | ||
|
|
1aed36bd16 | ||
|
|
00ce58ed18 | ||
|
|
d11aa1a61c | ||
|
|
56a62d3b4d | ||
|
|
e6dd668657 | ||
|
|
f60a64c8db | ||
|
|
eff1c298c9 | ||
|
|
358b0a122b | ||
|
|
c0f7ba9d46 | ||
|
|
c4edae8196 | ||
|
|
398dab808c | ||
|
|
3530871560 | ||
|
|
1ba26fdb98 | ||
|
|
a3b85b4e3e | ||
|
|
e37a5d0394 | ||
|
|
e5a8e77e2a | ||
|
|
314b59798f | ||
|
|
e9ae16e534 | ||
|
|
c971ca0ce2 | ||
|
|
0ad9a5f9c6 | ||
|
|
c31d91668a | ||
|
|
f5c196d717 | ||
|
|
3b90eed89f | ||
|
|
9828c8b787 | ||
|
|
b3e997134f | ||
|
|
f560baa69b | ||
|
|
8cf5f00c87 | ||
|
|
482c3895d3 | ||
|
|
fc0d4bde35 | ||
|
|
33ed89a036 | ||
|
|
0a5953c104 | ||
|
|
77f6be1a8b | ||
|
|
5bd3f9a571 | ||
|
|
ef59119663 | ||
|
|
45baca7018 | ||
|
|
9b1edb7a97 | ||
|
|
31c071d086 | ||
|
|
ecf4c5c104 | ||
|
|
35fbfece0d | ||
|
|
b7721e42d3 | ||
|
|
386346cee9 | ||
|
|
bbecccc45e | ||
|
|
1a8f84c134 | ||
|
|
66181fdcdf | ||
|
|
b9c05e8a9c | ||
|
|
9c22d6c12a | ||
|
|
f3cedbbd6f | ||
|
|
3f3a660ca1 | ||
|
|
1c6ded8416 | ||
|
|
aa63fdb26f | ||
|
|
3932330ce6 | ||
|
|
3b946b1c69 | ||
|
|
14df829f18 | ||
|
|
788d024be6 | ||
|
|
c20b56e089 | ||
|
|
287f4f239e | ||
|
|
dce66945ec | ||
|
|
92bd1d5200 | ||
|
|
06d2df8211 | ||
|
|
36256856b5 | ||
|
|
a771ae853c | ||
|
|
ef4e10bbb1 | ||
|
|
0dbe4d936e | ||
|
|
731fee11d4 | ||
|
|
6759df52c3 | ||
|
|
914b997076 | ||
|
|
0b8a2fea72 | ||
|
|
fb2538135c | ||
|
|
b4c547c278 | ||
|
|
b243bc846b | ||
|
|
6b8f6162b6 | ||
|
|
158db1532b | ||
|
|
6abfdb59c6 | ||
|
|
009d1f9ced | ||
|
|
555fba6598 | ||
|
|
f9017b72a7 | ||
|
|
99c3c2fc80 | ||
|
|
32381679f2 | ||
|
|
3d031265d1 | ||
|
|
026cda0071 | ||
|
|
fb41ed5a86 | ||
|
|
8a08468a73 | ||
|
|
f600cb4f2c | ||
|
|
f754f028dc | ||
|
|
41b292b45b | ||
|
|
af9be9cae8 | ||
|
|
ccfaea64c5 | ||
|
|
a86fc96730 | ||
|
|
cf51af17fd | ||
|
|
8c1b6a5cf0 | ||
|
|
bcecb8cd76 | ||
|
|
557790b0e5 | ||
|
|
8eb5a45718 | ||
|
|
7b64cef73b | ||
|
|
106203170e | ||
|
|
174d2bfc11 | ||
|
|
abda9c7f97 | ||
|
|
8e95260df9 | ||
|
|
5af1ae1920 | ||
|
|
f0eb9d48c9 | ||
|
|
0ac284009e | ||
|
|
fcf963639e | ||
|
|
ba8c0fb1d5 | ||
|
|
08fe74675b | ||
|
|
f5e7fdf8aa | ||
|
|
f6e447d049 | ||
|
|
531b21c012 | ||
|
|
a057456d5a | ||
|
|
0f043b39f5 | ||
|
|
dc0701e21d | ||
|
|
712f18f4e8 | ||
|
|
e0a82b4aaf | ||
|
|
a7f238ae0b | ||
|
|
0d99b6de7a | ||
|
|
6f627fca96 | ||
|
|
339fbc482b | ||
|
|
72e3ee1d77 | ||
|
|
a9750fb088 | ||
|
|
d80e3b0824 | ||
|
|
46df7a9ea0 | ||
|
|
b851ce49f7 | ||
|
|
d9afde3e15 | ||
|
|
b38c57f308 | ||
|
|
93e7e2e06e | ||
|
|
5ac09180a5 | ||
|
|
3819ca3a62 | ||
|
|
00426b4c9b | ||
|
|
58d8cefcc0 | ||
|
|
c8bb122557 | ||
|
|
bcef603a36 | ||
|
|
639b4d392a | ||
|
|
6d5f06a61d | ||
|
|
3e00e2ad58 | ||
|
|
cad2be5e53 | ||
|
|
58fe5f263f | ||
|
|
79ec6845f8 | ||
|
|
0f81ba8307 | ||
|
|
a30543b035 | ||
|
|
5c4473a1d9 | ||
|
|
7ff47c8c51 | ||
|
|
2544e29be3 | ||
|
|
d7bf564e8f | ||
|
|
0f135f881a | ||
|
|
2f1bb5e1c0 | ||
|
|
02b5f96eee | ||
|
|
3e77871539 | ||
|
|
676affdd03 | ||
|
|
5caf41c067 | ||
|
|
58f9e89fab | ||
|
|
8776f0f4a5 | ||
|
|
84a8c27926 | ||
|
|
f061e3486e | ||
|
|
da23995343 | ||
|
|
4e0a61bd9b | ||
|
|
d3e2fa5df5 | ||
|
|
00b111c974 | ||
|
|
bd265c00a0 | ||
|
|
ef53a63766 | ||
|
|
688c7f1a1c | ||
|
|
2989922253 | ||
|
|
6f6619a5ab | ||
|
|
1594c228e8 | ||
|
|
fb44f52aa9 | ||
|
|
045ead1728 | ||
|
|
4404463e53 | ||
|
|
b79b61c8c8 | ||
|
|
467048a0fc | ||
|
|
2a1edffce3 | ||
|
|
ce833c39d5 | ||
|
|
721a74eee6 | ||
|
|
8f9f4f894c | ||
|
|
842765dad0 | ||
|
|
b23cc47d95 | ||
|
|
85baa596d0 | ||
|
|
e371fff110 | ||
|
|
dffe6b4f39 | ||
|
|
dbf684f385 | ||
|
|
7b20fd91ef | ||
|
|
300c25ded1 | ||
|
|
284e814d2a | ||
|
|
d5cdaddeea | ||
|
|
8635b395a1 | ||
|
|
c2cf4e72f8 | ||
|
|
92def0f71d | ||
|
|
bccae9d71c | ||
|
|
90b6a2f82b | ||
|
|
0462df7de2 | ||
|
|
d4b29ab08d | ||
|
|
09f2dfe181 | ||
|
|
317d013a0b | ||
|
|
e26b2dcd43 | ||
|
|
4676dbc740 | ||
|
|
778f869ddb | ||
|
|
a7e9b1f76d | ||
|
|
096e56aaa8 | ||
|
|
d1bcc557f0 | ||
|
|
e041fab319 | ||
|
|
3394e36325 | ||
|
|
a501458e5a | ||
|
|
da08eef5ef | ||
|
|
0ea714552a | ||
|
|
8cba584e52 | ||
|
|
878f07d2cf | ||
|
|
d297de732f | ||
|
|
c41d4d32b9 | ||
|
|
5d34134888 | ||
|
|
f968ec4cac | ||
|
|
fdb256a534 | ||
|
|
62a2b57613 | ||
|
|
90f5ebfa58 | ||
|
|
66aecee519 | ||
|
|
f0c661d6e2 | ||
|
|
a27f5b4c15 | ||
|
|
95b69f0003 | ||
|
|
6925f0bf7a | ||
|
|
6c1a9ed83b | ||
|
|
3ebef79313 | ||
|
|
57393806b0 | ||
|
|
721d8cfa49 | ||
|
|
9e5b68444f | ||
|
|
9f6c619401 | ||
|
|
1b47e40a3a | ||
|
|
3fc14102e5 | ||
|
|
d907992c39 | ||
|
|
ae2f35c6c5 | ||
|
|
ddd804041d | ||
|
|
2478cbdb6f | ||
|
|
80e992a9fc | ||
|
|
e4b8e08e89 | ||
|
|
6c556b8a72 | ||
|
|
9659c19b23 | ||
|
|
aa6e9d9bf2 | ||
|
|
812f0ac32c | ||
|
|
296b312950 | ||
|
|
29a06406ea | ||
|
|
261cb7d3cd | ||
|
|
ba5a57ac07 | ||
|
|
c0ebe9d7a1 | ||
|
|
2b83012786 | ||
|
|
b9761288bd | ||
|
|
a711e83398 | ||
|
|
2705385681 | ||
|
|
4b2f3dd070 | ||
|
|
876acf2839 | ||
|
|
d0446f068c | ||
|
|
a9396d1e2f | ||
|
|
e87102e586 | ||
|
|
8b213f8d7c | ||
|
|
9a7dc5ba86 | ||
|
|
9acb3f83f8 | ||
|
|
1c676211ee | ||
|
|
89728164eb | ||
|
|
d0c4093f5a | ||
|
|
d7f680fb19 | ||
|
|
48279e060c | ||
|
|
10dd0d07dc | ||
|
|
d82dc4cf77 | ||
|
|
2b9553e4da | ||
|
|
ac112ea287 | ||
|
|
4f6b099615 | ||
|
|
d4dcb162d0 | ||
|
|
46054f513b | ||
|
|
261f67df50 | ||
|
|
22f9b2affe | ||
|
|
06c392c066 | ||
|
|
3d67b3bc17 | ||
|
|
6cefab5d8a | ||
|
|
cc261de37b | ||
|
|
20712641a7 | ||
|
|
e4d0b16fd5 | ||
|
|
f8c25791e9 | ||
|
|
704aa433d4 | ||
|
|
3bd1003164 | ||
|
|
8dd55d7506 | ||
|
|
6252d778c1 | ||
|
|
942248b9e6 | ||
|
|
4793449105 | ||
|
|
428e3bc0fc | ||
|
|
bf2c80cfcf | ||
|
|
06b0685a57 | ||
|
|
231ea25968 | ||
|
|
8658eeddb2 | ||
|
|
d0769eed97 | ||
|
|
b1fcd1f7c8 | ||
|
|
db1259b3e0 | ||
|
|
1a5f42b753 | ||
|
|
75d061a7fa | ||
|
|
9fb4c4140b | ||
|
|
0306877fb9 | ||
|
|
86e3b05a3f | ||
|
|
a4e8907c95 | ||
|
|
916ad6535a | ||
|
|
c129309937 | ||
|
|
0088e9ae77 | ||
|
|
79806b5ad5 | ||
|
|
7d59fbfc36 | ||
|
|
e645bdf249 | ||
|
|
0bedf26849 | ||
|
|
a153c5b4ce | ||
|
|
44f1f3e9ae | ||
|
|
8c82fa86c6 | ||
|
|
d4da934d6a | ||
|
|
56cc664c26 | ||
|
|
eaa0bdfc62 | ||
|
|
c538e9c6d4 | ||
|
|
54b9af0299 | ||
|
|
c7d5b9211c | ||
|
|
7ca22a8718 | ||
|
|
4e37b32976 | ||
|
|
9725b23db1 | ||
|
|
2e60f2b2ce | ||
|
|
004776a522 | ||
|
|
92fa1dde79 | ||
|
|
464821c4e2 | ||
|
|
e95483236a | ||
|
|
a9b97a85ad | ||
|
|
6170befc90 | ||
|
|
5ecb85cb6d | ||
|
|
d2fc04f45d | ||
|
|
fb4da933d4 | ||
|
|
7483900db2 | ||
|
|
9f78dbf200 | ||
|
|
ef9b9bdd6d | ||
|
|
1937aa43ba | ||
|
|
293ea66784 | ||
|
|
e98d8f4ced | ||
|
|
418d2afb2a | ||
|
|
a4c1a6187f | ||
|
|
123ca34040 | ||
|
|
6b3224116c | ||
|
|
635e0c9788 | ||
|
|
dd33a0e0ec | ||
|
|
191deeaba6 | ||
|
|
245072f7a2 | ||
|
|
6b858512b6 | ||
|
|
b857a01c30 | ||
|
|
94c9a3e05b | ||
|
|
8928d2c488 | ||
|
|
25bd5654aa | ||
|
|
83d5b96adf | ||
|
|
7eb90c5718 | ||
|
|
4b1af75724 | ||
|
|
8d07ab6527 | ||
|
|
ce4ea7e7a9 | ||
|
|
50ab5e7517 | ||
|
|
431c1d7f66 | ||
|
|
a55090dc2f | ||
|
|
d76cdb73b0 | ||
|
|
2594664330 | ||
|
|
f9ed075db6 | ||
|
|
099ced4f94 | ||
|
|
13d2513930 | ||
|
|
2211b1c65e | ||
|
|
1fd37ca2b2 | ||
|
|
7070e3748d | ||
|
|
dfaef908c2 | ||
|
|
67540c763b | ||
|
|
14269bd4d9 | ||
|
|
131663032c | ||
|
|
8ac71165e9 | ||
|
|
346758d3f0 | ||
|
|
d3e7f130fb | ||
|
|
aef8837b5d | ||
|
|
dc0832adba | ||
|
|
c0cd269322 | ||
|
|
0ad3ff655e | ||
|
|
ef45a62cc9 | ||
|
|
b79abbdea9 | ||
|
|
a9e4ce005d | ||
|
|
987f2b2a55 | ||
|
|
930e2d1d9d | ||
|
|
f4ada70e56 | ||
|
|
97e658709d | ||
|
|
ec2992cd2d | ||
|
|
619208565b | ||
|
|
dcd689d2ea | ||
|
|
e94de15f83 | ||
|
|
6af7de51a5 | ||
|
|
559c6722ff | ||
|
|
aab2cce978 | ||
|
|
f4a4af0fa4 | ||
|
|
6934838974 | ||
|
|
1aadd25cb5 | ||
|
|
0caf944668 | ||
|
|
6452f62b88 | ||
|
|
e061dfd808 | ||
|
|
4da53ef219 | ||
|
|
347e44f04d | ||
|
|
8997fa7242 | ||
|
|
19ba6efb82 | ||
|
|
d10cbc9984 | ||
|
|
6c7d9ded00 | ||
|
|
6d04e89d7d | ||
|
|
2beb24147d | ||
|
|
16c5f4e377 | ||
|
|
03a6f1753c | ||
|
|
9fb61d8446 | ||
|
|
bc3322d3c9 | ||
|
|
06c7bf7514 | ||
|
|
4c89a000e4 | ||
|
|
86d61e0b44 | ||
|
|
6407390d72 | ||
|
|
648120cabf | ||
|
|
ce5b3f290a | ||
|
|
5308ca1806 | ||
|
|
6df6d408d2 | ||
|
|
b60d6ccdd8 | ||
|
|
de01c9685e | ||
|
|
31d2ecc9fd | ||
|
|
2f8502aec6 | ||
|
|
d377b04dad | ||
|
|
40cc78ae1e | ||
|
|
268f1e8472 | ||
|
|
004b7c782d | ||
|
|
33b293f0aa | ||
|
|
ad584a98ad | ||
|
|
e2509eddb2 | ||
|
|
b9f72d0e78 | ||
|
|
c839bb2db3 | ||
|
|
30161369a8 | ||
|
|
c65aa9732e | ||
|
|
bc72b8fd1c | ||
|
|
f089531bd1 | ||
|
|
8d8ea53804 | ||
|
|
89e405e927 | ||
|
|
ca984a6630 | ||
|
|
fa39a55eca | ||
|
|
c3a1ba2f2d | ||
|
|
86e291f250 | ||
|
|
dd1d4439a9 | ||
|
|
cbfde18f8c | ||
|
|
e2c2e23d2a | ||
|
|
40cc5d5242 | ||
|
|
9765194ace | ||
|
|
628465e6b5 | ||
|
|
58706df120 | ||
|
|
b19225c747 | ||
|
|
c304889e61 | ||
|
|
05a9204678 | ||
|
|
ed8537bb0b | ||
|
|
6a9ae10fcf | ||
|
|
05358904bf | ||
|
|
1a6901c3e3 | ||
|
|
7aaba8244b | ||
|
|
8c45dcde88 | ||
|
|
6c155b04b2 | ||
|
|
cd8ad9a2ec | ||
|
|
a5db7d0246 | ||
|
|
b84b467b96 | ||
|
|
0812aaac88 | ||
|
|
194d2f911e | ||
|
|
e360b36b8a | ||
|
|
b6f66dd287 | ||
|
|
0a4bb48cd3 | ||
|
|
15d62d4a91 | ||
|
|
5b13c44ef9 | ||
|
|
0a4250f3b4 | ||
|
|
f79223ed58 | ||
|
|
2d28218a2a | ||
|
|
35974f2ee1 | ||
|
|
1f73323fb9 | ||
|
|
a3d0736eec | ||
|
|
4bdd486c00 | ||
|
|
c3895c9bd7 | ||
|
|
e9ddd89b32 | ||
|
|
88a8f2d609 | ||
|
|
a5dc5c89e8 | ||
|
|
3a15a35137 | ||
|
|
b644640804 | ||
|
|
aaa4f66671 | ||
|
|
07e021199e | ||
|
|
6b2ca7dc80 | ||
|
|
091d62803e | ||
|
|
547999bae0 | ||
|
|
99013f7998 | ||
|
|
fc396800db | ||
|
|
6d03ae57ac | ||
|
|
4a0aa57355 | ||
|
|
7db737494c | ||
|
|
b285501c44 | ||
|
|
2f9b29994f | ||
|
|
917434cb6b | ||
|
|
28a52bb658 | ||
|
|
82bc19374c | ||
|
|
0b23f30bb7 | ||
|
|
64a62d7aed | ||
|
|
de31cf8e7d | ||
|
|
3484f9afb3 | ||
|
|
81df0ff390 | ||
|
|
d403ec7399 | ||
|
|
6ac77835df | ||
|
|
b113119a9a | ||
|
|
b713057614 | ||
|
|
4268570166 | ||
|
|
ead508c0d0 | ||
|
|
f8e1be8565 | ||
|
|
360f1af32f | ||
|
|
d897df6a30 | ||
|
|
ba4f3a1553 | ||
|
|
6ba9534da4 | ||
|
|
c16ef96754 | ||
|
|
e728491aa2 | ||
|
|
ce356fa266 | ||
|
|
5e46323ca3 | ||
|
|
0a7d047246 | ||
|
|
3fa534a3eb | ||
|
|
c6405f70d3 | ||
|
|
acae6c2c49 | ||
|
|
141fdc2197 | ||
|
|
a7ed8a006f | ||
|
|
b1a0ebd531 | ||
|
|
e8021acccd | ||
|
|
39b0da2a3f | ||
|
|
fd3d18f6c5 | ||
|
|
ecc27d1674 | ||
|
|
7d0514ab36 | ||
|
|
44c3024c00 | ||
|
|
253c92bab7 | ||
|
|
c10850118d | ||
|
|
4f017e9173 | ||
|
|
5ed46c82cb | ||
|
|
64391e906d | ||
|
|
47b4ee07ab | ||
|
|
3000cbf763 | ||
|
|
76b3d314a8 | ||
|
|
ba646de0ad | ||
|
|
395f746a05 | ||
|
|
f7e57cd398 | ||
|
|
3ea6d97ed2 | ||
|
|
affc0d8b67 | ||
|
|
c637e310e9 | ||
|
|
6ee7dcdd51 | ||
|
|
23470267fe | ||
|
|
4a92bb91df | ||
|
|
69522c422c | ||
|
|
bc5e3524eb | ||
|
|
479297fc35 | ||
|
|
516feafcfb | ||
|
|
a135c82ab5 | ||
|
|
10996f1cbd | ||
|
|
23b060e1f5 | ||
|
|
622ff3a256 | ||
|
|
5d457b6834 | ||
|
|
f10f76d127 | ||
|
|
58f3382daf | ||
|
|
0e1139446e | ||
|
|
f433216fae | ||
|
|
ed680baaac | ||
|
|
e0a9d908ed | ||
|
|
bfa4a46bd5 | ||
|
|
03f3ff991e | ||
|
|
619b4824f0 | ||
|
|
021af0186b | ||
|
|
d3caad8b8d | ||
|
|
ec6bec3326 | ||
|
|
dd54740d36 | ||
|
|
8f65156bda | ||
|
|
96c7df5afa | ||
|
|
0c19105fbf | ||
|
|
4145d83248 | ||
|
|
6490705e2a | ||
|
|
10d2432df5 | ||
|
|
815db72671 | ||
|
|
6d0ba61c54 | ||
|
|
5f61267f75 | ||
|
|
94ee42cebb | ||
|
|
b6795e5c63 | ||
|
|
ef85d063c2 | ||
|
|
59755971e5 | ||
|
|
c5ab831a87 | ||
|
|
6715dc2a5d | ||
|
|
af6de64ec0 | ||
|
|
1ac2448f90 | ||
|
|
b5f34b30d3 | ||
|
|
01f4e080df | ||
|
|
d55335e70b | ||
|
|
a8c1dc4bc6 | ||
|
|
2897059503 | ||
|
|
d491f9df5a | ||
|
|
bc40318e40 | ||
|
|
3935434f04 | ||
|
|
4cf1f2de94 | ||
|
|
73156c6780 | ||
|
|
bc52bafa8d | ||
|
|
5c9007b242 | ||
|
|
5857e3f75e | ||
|
|
e202831013 | ||
|
|
4cbbfccb6d | ||
|
|
21f3b1cf34 | ||
|
|
f7b384e9b6 | ||
|
|
1e6ab47ee4 | ||
|
|
78341ea2f1 | ||
|
|
3f8a4d4273 | ||
|
|
bae517c9f8 | ||
|
|
c88ccbf9bc | ||
|
|
5e40f5d509 | ||
|
|
46389131bc | ||
|
|
c6a344d0d9 | ||
|
|
bcc2c377a0 | ||
|
|
bb6afc847e | ||
|
|
e0193151db | ||
|
|
42a80bad8e | ||
|
|
6e3e77f65d | ||
|
|
e155f022a0 | ||
|
|
db65aab347 | ||
|
|
a180c5f357 | ||
|
|
1c0279f17c | ||
|
|
8866eb292b | ||
|
|
6fdda3391e | ||
|
|
fdb8dd4e5b | ||
|
|
9a1d3783ee | ||
|
|
3841d9e322 | ||
|
|
e392eadf8a | ||
|
|
f743d5d0b5 | ||
|
|
4a76bf59ef | ||
|
|
205b29e2f5 | ||
|
|
d511b82264 | ||
|
|
aaae112e60 | ||
|
|
955fd6207f | ||
|
|
4e56c96612 | ||
|
|
dd046f3442 | ||
|
|
5a947f83a1 | ||
|
|
b87b8b54fd | ||
|
|
233c0537a1 | ||
|
|
63d4798a50 | ||
|
|
6c47517684 | ||
|
|
c58b1a0143 | ||
|
|
f489d9131b | ||
|
|
f0109c5588 | ||
|
|
c16becba56 | ||
|
|
4605788696 | ||
|
|
87908313cc | ||
|
|
9cc2eba7b8 | ||
|
|
2459cee57b | ||
|
|
0bf6ce57ed | ||
|
|
7041424f96 | ||
|
|
9509285c16 | ||
|
|
e55ee0e65d | ||
|
|
9ea70497c2 | ||
|
|
3389b9e9fd | ||
|
|
76d4d54639 | ||
|
|
1b692b6c37 | ||
|
|
40d8cef1a2 | ||
|
|
23550c0062 | ||
|
|
949bd940ee | ||
|
|
79bdb9eed5 | ||
|
|
a141f08298 | ||
|
|
dee43a3911 | ||
|
|
ef227d0139 | ||
|
|
cbcf9ce645 | ||
|
|
0e5af2b16c | ||
|
|
85ca3a3b27 | ||
|
|
fc5f5f3b6c | ||
|
|
716fd8c0b9 | ||
|
|
a517393c43 | ||
|
|
c2311faffe | ||
|
|
fe453b0d66 | ||
|
|
7e75b0fc02 | ||
|
|
11b0a0a73d | ||
|
|
82fdb5c3eb | ||
|
|
3f1d532c8b | ||
|
|
f258b00aa7 | ||
|
|
4e71b9576d | ||
|
|
f36567a5cd | ||
|
|
924ebb6c7f | ||
|
|
6e7e8eb44a | ||
|
|
308c583254 | ||
|
|
97b2f7e5ca | ||
|
|
3ea88a07d9 | ||
|
|
588f8bb96a | ||
|
|
c93c0dd721 | ||
|
|
fc59c254fd | ||
|
|
2f8b6a150f | ||
|
|
db60ac5c17 | ||
|
|
e1f09853c5 | ||
|
|
24656713a5 | ||
|
|
7dd0269292 | ||
|
|
8b87cea7aa | ||
|
|
c7559a6946 | ||
|
|
945c6080ad | ||
|
|
44590965d1 | ||
|
|
7ab64d678f | ||
|
|
e406a76b62 | ||
|
|
e26f175a8f | ||
|
|
d4ab84745d | ||
|
|
32dbc3101e | ||
|
|
0a924eb718 | ||
|
|
a284327bfc | ||
|
|
2ea38d6ecc | ||
|
|
6a34bbfddd | ||
|
|
58323ada4b | ||
|
|
5fd723cb80 | ||
|
|
5c626e6957 | ||
|
|
5d949842eb | ||
|
|
b595c17d78 | ||
|
|
b84973ba2b | ||
|
|
61be49e7b2 | ||
|
|
8faf5659ee | ||
|
|
cc9267a646 | ||
|
|
55838bb032 | ||
|
|
67619ac5e8 | ||
|
|
952b342859 | ||
|
|
c7149c460d | ||
|
|
fd0613ea0e | ||
|
|
36d2dddc59 | ||
|
|
63c5b05584 | ||
|
|
4b235e5b87 | ||
|
|
6c51fffdaa | ||
|
|
5d6d638c85 | ||
|
|
90eb515167 | ||
|
|
17526711a2 | ||
|
|
cf0118e090 | ||
|
|
868d6fec42 | ||
|
|
851f5854bf | ||
|
|
eb5428c971 | ||
|
|
81188df7ef | ||
|
|
9fd365cc41 | ||
|
|
999df6e40f | ||
|
|
076d069568 | ||
|
|
2738648197 | ||
|
|
36013009a1 | ||
|
|
1b60233862 | ||
|
|
2cba10dd05 | ||
|
|
b3944127ea | ||
|
|
f1674378ca | ||
|
|
6f0191e1cf | ||
|
|
1848844be6 | ||
|
|
8b6362c749 | ||
|
|
d860d13361 | ||
|
|
4b077dbf4c | ||
|
|
40f73bbfe2 | ||
|
|
f455706d7c | ||
|
|
23e9672476 | ||
|
|
36f992f95f | ||
|
|
b2c6d526ab | ||
|
|
fe1e833677 | ||
|
|
8df1b9e8e5 | ||
|
|
38b0f71b01 | ||
|
|
29d2f115f8 | ||
|
|
0f677b4891 | ||
|
|
2f7dd04168 | ||
|
|
ed3b667985 | ||
|
|
6ae1d8c158 | ||
|
|
404bced97b | ||
|
|
5af49c8a82 | ||
|
|
85aa98e8e2 | ||
|
|
330d102f62 | ||
|
|
32b33a7910 | ||
|
|
17c6a0f28a | ||
|
|
7341eed1cf | ||
|
|
ff99fbfbc9 | ||
|
|
9f67fdc771 | ||
|
|
521143a16b | ||
|
|
2622a25b12 | ||
|
|
a91e925221 | ||
|
|
6c3289d5a5 | ||
|
|
988a91ac06 | ||
|
|
aa7c913e9a | ||
|
|
56db9feaa4 | ||
|
|
5ace0f13c9 | ||
|
|
076e6c9479 | ||
|
|
8277b1192e | ||
|
|
150b978b0e | ||
|
|
6c72096bfe | ||
|
|
87c18cea80 | ||
|
|
e658734084 | ||
|
|
ec4f350baa | ||
|
|
095f583211 | ||
|
|
3c864cf6d2 | ||
|
|
eb4b21ce9f | ||
|
|
ff5349fd90 | ||
|
|
1f34ffa85d | ||
|
|
e98cab1f7c | ||
|
|
aabc9659a2 | ||
|
|
8d8d308f7a | ||
|
|
3ebd4595c6 | ||
|
|
7e1168946f | ||
|
|
134689d8aa | ||
|
|
56282f9cbb | ||
|
|
b4713741b1 | ||
|
|
e42fe3bd61 | ||
|
|
4fd2dade60 | ||
|
|
e12b03504c | ||
|
|
153156c1fa | ||
|
|
3ecc69da2b | ||
|
|
07ad29da41 | ||
|
|
7d0de0b26f | ||
|
|
77fab9c78f | ||
|
|
3a8f3272c7 | ||
|
|
2d44cbac1b | ||
|
|
893d72677b | ||
|
|
979eca4066 | ||
|
|
258d13e746 | ||
|
|
779531da5d | ||
|
|
31d71006d7 | ||
|
|
64ca66c062 | ||
|
|
6e1a2b3427 | ||
|
|
f585235192 | ||
|
|
9355643554 | ||
|
|
ccc6055926 | ||
|
|
6639446bb8 | ||
|
|
e2925c585f | ||
|
|
6c76b0473c | ||
|
|
e1e19632a5 | ||
|
|
3e5364d5c0 | ||
|
|
6c98de4c8b | ||
|
|
9613dde4d2 | ||
|
|
d47df2e538 | ||
|
|
6fcacd5159 | ||
|
|
11b39cb020 | ||
|
|
d81f132db6 | ||
|
|
095697e789 | ||
|
|
62d98c3137 | ||
|
|
e80d5dc172 | ||
|
|
421e29db2d | ||
|
|
9e6e53583c | ||
|
|
3f59a7d84e | ||
|
|
21ffd788ab | ||
|
|
8dadfea724 | ||
|
|
00ce52ecf7 | ||
|
|
50ac13d3fd | ||
|
|
58318fec46 | ||
|
|
a49941113e | ||
|
|
595801cb99 | ||
|
|
0b469f09df | ||
|
|
1e1f4e4a47 | ||
|
|
c63e2ae7c8 | ||
|
|
d3d3fa990e | ||
|
|
21980b7e71 | ||
|
|
844ca0d387 | ||
|
|
972ae35300 | ||
|
|
57bfb8eb96 | ||
|
|
ed6e6a9fb2 | ||
|
|
ed402267b6 | ||
|
|
6eec570828 | ||
|
|
22fc1e3f0b | ||
|
|
ae9bd868f1 | ||
|
|
a887012aca | ||
|
|
bc73048ab9 | ||
|
|
c89dd6c379 | ||
|
|
9662debe5e | ||
|
|
057262d917 | ||
|
|
b6723a6219 | ||
|
|
068f3e0a43 | ||
|
|
95635a8c47 | ||
|
|
3ec2071820 | ||
|
|
1696db3044 | ||
|
|
e1a1eab2b3 | ||
|
|
f7865f3358 | ||
|
|
6d5f8ed5f3 | ||
|
|
96a737379f | ||
|
|
d73feec013 | ||
|
|
2ccead1da5 | ||
|
|
8885f2717e | ||
|
|
4448ffc777 | ||
|
|
022d10c598 | ||
|
|
8e6b7043bd | ||
|
|
66eaaff598 | ||
|
|
478c6c134f | ||
|
|
b5d333ba6c | ||
|
|
81723d55ac | ||
|
|
fb784ce962 | ||
|
|
5a37380900 | ||
|
|
b6300f3a5c | ||
|
|
a3e8a2d623 | ||
|
|
7b3a4bdc39 | ||
|
|
cc0b5e5e0f | ||
|
|
5c3f7d8f94 | ||
|
|
8c3f8cd450 | ||
|
|
046582711a | ||
|
|
15756ec92d | ||
|
|
fc49abc9fb | ||
|
|
4a9ff27f3e | ||
|
|
790e6f370f | ||
|
|
16ccc1321d | ||
|
|
8648c94dd4 | ||
|
|
dc4eb720ae | ||
|
|
0b891ad557 | ||
|
|
e96193ae28 | ||
|
|
3ff9075959 | ||
|
|
c03842056c | ||
|
|
6df226b21c | ||
|
|
7dfa7d7426 | ||
|
|
b8b1a891cf | ||
|
|
7df0e8b0f9 | ||
|
|
ff072ae9d9 | ||
|
|
f81ca39741 | ||
|
|
3db1f2a98c | ||
|
|
4865df9be1 | ||
|
|
0c16f2c334 | ||
|
|
d01149620f | ||
|
|
ab9401f390 | ||
|
|
3223c17b74 | ||
|
|
404035bcf0 | ||
|
|
a0185bb0b4 | ||
|
|
1a591cd9f1 | ||
|
|
e9b81b2033 | ||
|
|
cbfc1e8ed1 | ||
|
|
cb63338805 | ||
|
|
bcdc82ccee | ||
|
|
76a4cf6c34 | ||
|
|
872f23b0f0 | ||
|
|
e61f7405fd | ||
|
|
0714871b56 | ||
|
|
8a89fb2a1a | ||
|
|
036544e3ed | ||
|
|
7a6784d809 | ||
|
|
ed9301705b | ||
|
|
21f9694574 | ||
|
|
3a0b11b89d |
@@ -1,9 +1,13 @@
|
||||
^\.Rproj\.user$
|
||||
^\.git$
|
||||
^examples$
|
||||
^README\.md$
|
||||
^shiny\.Rproj$
|
||||
^shiny\.sh$
|
||||
^shiny\.cmd$
|
||||
^run\.R$
|
||||
^\.gitignore$
|
||||
^res$
|
||||
^man-roxygen$
|
||||
^\.travis\.yml$
|
||||
^staticdocs$
|
||||
^tools$
|
||||
|
||||
2
.Rinstignore
Normal file
2
.Rinstignore
Normal file
@@ -0,0 +1,2 @@
|
||||
^tools$
|
||||
^Rmd$
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,5 @@
|
||||
*.so
|
||||
/src-i386/
|
||||
/src-x86_64/
|
||||
shinyapps/
|
||||
README.html
|
||||
|
||||
27
.travis.yml
Normal file
27
.travis.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
# it is not really python, but there is no R support on Travis CI yet
|
||||
language: python
|
||||
|
||||
# environment variables
|
||||
env:
|
||||
- R_LIBS_USER=~/R R_MY_PKG="$(basename $TRAVIS_REPO_SLUG)"
|
||||
|
||||
# install dependencies
|
||||
install:
|
||||
- sudo apt-add-repository -y "deb http://cran.rstudio.com/bin/linux/ubuntu `lsb_release -cs`/"
|
||||
- sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E084DAB9
|
||||
- sudo apt-add-repository -y ppa:marutter/c2d4u
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install r-base-dev r-cran-shiny r-cran-cairo r-cran-markdown
|
||||
- "[ ! -d ~/R ] && mkdir ~/R"
|
||||
- Rscript -e "install.packages(c('xtable'), repos = 'http://cran.rstudio.org')"
|
||||
- Rscript -e "install.packages('knitr', repos = c('http://rforge.net', 'http://cran.rstudio.org'))"
|
||||
- Rscript -e "install.packages('$R_MY_PKG', dep = TRUE, repos = 'http://cran.rstudio.org')"
|
||||
|
||||
# run tests
|
||||
script:
|
||||
- cd ..; rm -f *.tar.gz; R CMD build $R_MY_PKG
|
||||
- R CMD check $R_MY_PKG*.tar.gz --no-manual
|
||||
|
||||
after_failure:
|
||||
- cat $R_MY_PKG.Rcheck/00install.out || true
|
||||
- cat $R_MY_PKG.Rcheck/00check.log || true
|
||||
71
DESCRIPTION
71
DESCRIPTION
@@ -1,27 +1,64 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 0.1.4
|
||||
Date: 2012-08-30
|
||||
Version: 0.9.1.9008
|
||||
Date: 2014-03-19
|
||||
Author: RStudio, Inc.
|
||||
Maintainer: Joe Cheng <joe@rstudio.org>
|
||||
Description: Shiny makes it incredibly easy to build interactive web
|
||||
applications with R. Automatic "reactive" binding between inputs and
|
||||
outputs and extensive pre-built widgets make it possible to build
|
||||
Maintainer: Winston Chang <winston@rstudio.com>
|
||||
Description: Shiny makes it incredibly easy to build interactive web
|
||||
applications with R. Automatic "reactive" binding between inputs and
|
||||
outputs and extensive pre-built widgets make it possible to build
|
||||
beautiful, responsive, and powerful applications with minimal effort.
|
||||
License: GPL-3
|
||||
Depends: R (>= 2.14.1), methods, websockets (>= 1.1.4), caTools, RJSONIO, xtable
|
||||
Imports: stats, tools, utils, datasets
|
||||
URL: https://github.com/rstudio/shiny, http://rstudio.github.com/shiny/tutorial
|
||||
Depends:
|
||||
R (>= 2.14.1),
|
||||
methods
|
||||
Imports:
|
||||
tools,
|
||||
utils,
|
||||
httpuv (>= 1.2.0),
|
||||
caTools,
|
||||
RJSONIO,
|
||||
xtable,
|
||||
digest,
|
||||
htmltools (>= 0.2.4)
|
||||
Suggests:
|
||||
datasets,
|
||||
Cairo (>= 1.5-5),
|
||||
testthat,
|
||||
knitr (>= 1.6),
|
||||
markdown
|
||||
URL: http://www.rstudio.com/shiny/
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Roxygen: list(wrap = FALSE)
|
||||
Collate:
|
||||
'app.R'
|
||||
'bootstrap-layout.R'
|
||||
'map.R'
|
||||
'timer.R'
|
||||
'tags.R'
|
||||
'react.R'
|
||||
'reactives.R'
|
||||
'shiny.R'
|
||||
'shinywrappers.R'
|
||||
'shinyui.R'
|
||||
'slider.R'
|
||||
'globals.R'
|
||||
'utils.R'
|
||||
'bootstrap.R'
|
||||
'cache.R'
|
||||
'fileupload.R'
|
||||
'graph.R'
|
||||
'hooks.R'
|
||||
'html-deps.R'
|
||||
'htmltools.R'
|
||||
'imageutils.R'
|
||||
'jqueryui.R'
|
||||
'middleware-shiny.R'
|
||||
'middleware.R'
|
||||
'priorityqueue.R'
|
||||
'react.R'
|
||||
'reactive-domains.R'
|
||||
'reactives.R'
|
||||
'run-url.R'
|
||||
'server.R'
|
||||
'shiny.R'
|
||||
'shinyui.R'
|
||||
'shinywrappers.R'
|
||||
'showcase.R'
|
||||
'slider.R'
|
||||
'tar.R'
|
||||
'timer.R'
|
||||
'update-input.R'
|
||||
|
||||
144
NAMESPACE
144
NAMESPACE
@@ -1,12 +1,60 @@
|
||||
# Generated by roxygen2 (4.0.1): do not edit by hand
|
||||
|
||||
S3method("$",reactivevalues)
|
||||
S3method("$",shinyoutput)
|
||||
S3method("$<-",reactivevalues)
|
||||
S3method("$<-",shinyoutput)
|
||||
S3method("[",reactivevalues)
|
||||
S3method("[",shinyoutput)
|
||||
S3method("[<-",reactivevalues)
|
||||
S3method("[<-",shinyoutput)
|
||||
S3method("[[",reactivevalues)
|
||||
S3method("[[",shinyoutput)
|
||||
S3method("[[<-",reactivevalues)
|
||||
S3method("[[<-",shinyoutput)
|
||||
S3method("names<-",reactivevalues)
|
||||
S3method(as.list,reactivevalues)
|
||||
S3method(as.shiny.appobj,character)
|
||||
S3method(as.shiny.appobj,list)
|
||||
S3method(as.shiny.appobj,shiny.appobj)
|
||||
S3method(as.tags,shiny.render.function)
|
||||
S3method(names,reactivevalues)
|
||||
S3method(print,reactive)
|
||||
S3method(print,shiny.appobj)
|
||||
S3method(str,reactivevalues)
|
||||
export(HTML)
|
||||
export(a)
|
||||
export(absolutePanel)
|
||||
export(actionButton)
|
||||
export(actionLink)
|
||||
export(addResourcePath)
|
||||
export(animationOptions)
|
||||
export(as.shiny.appobj)
|
||||
export(basicPage)
|
||||
export(bootstrapPage)
|
||||
export(br)
|
||||
export(checkboxGroupInput)
|
||||
export(checkboxInput)
|
||||
export(code)
|
||||
export(column)
|
||||
export(conditionalPanel)
|
||||
export(dataTableOutput)
|
||||
export(dateInput)
|
||||
export(dateRangeInput)
|
||||
export(div)
|
||||
export(downloadButton)
|
||||
export(downloadHandler)
|
||||
export(downloadLink)
|
||||
export(em)
|
||||
export(exprToFunction)
|
||||
export(fileInput)
|
||||
export(fixedPage)
|
||||
export(fixedPanel)
|
||||
export(fixedRow)
|
||||
export(flowLayout)
|
||||
export(fluidPage)
|
||||
export(fluidRow)
|
||||
export(getDefaultReactiveDomain)
|
||||
export(h1)
|
||||
export(h2)
|
||||
export(h3)
|
||||
@@ -15,49 +63,123 @@ export(h5)
|
||||
export(h6)
|
||||
export(headerPanel)
|
||||
export(helpText)
|
||||
export(HTML)
|
||||
export(hr)
|
||||
export(htmlOutput)
|
||||
export(icon)
|
||||
export(imageOutput)
|
||||
export(img)
|
||||
export(includeCSS)
|
||||
export(includeHTML)
|
||||
export(includeMarkdown)
|
||||
export(includeScript)
|
||||
export(includeText)
|
||||
export(inputPanel)
|
||||
export(installExprFunction)
|
||||
export(invalidateLater)
|
||||
export(is.reactive)
|
||||
export(is.reactivevalues)
|
||||
export(is.singleton)
|
||||
export(isolate)
|
||||
export(knit_print.shiny.appobj)
|
||||
export(knit_print.shiny.render.function)
|
||||
export(mainPanel)
|
||||
export(makeReactiveBinding)
|
||||
export(markRenderFunction)
|
||||
export(maskReactiveContext)
|
||||
export(navbarMenu)
|
||||
export(navbarPage)
|
||||
export(navlistPanel)
|
||||
export(need)
|
||||
export(numericInput)
|
||||
export(observe)
|
||||
export(onReactiveDomainEnded)
|
||||
export(outputOptions)
|
||||
export(p)
|
||||
export(pageWithSidebar)
|
||||
export(parseQueryString)
|
||||
export(plotOutput)
|
||||
export(plotPNG)
|
||||
export(pre)
|
||||
export(radioButtons)
|
||||
export(reactive)
|
||||
export(reactiveFileReader)
|
||||
export(reactivePlot)
|
||||
export(reactivePoll)
|
||||
export(reactivePrint)
|
||||
export(reactiveTable)
|
||||
export(reactiveText)
|
||||
export(reactiveTimer)
|
||||
export(reactiveUI)
|
||||
export(reactiveValues)
|
||||
export(reactiveValuesToList)
|
||||
export(registerInputHandler)
|
||||
export(removeInputHandler)
|
||||
export(renderDataTable)
|
||||
export(renderImage)
|
||||
export(renderPlot)
|
||||
export(renderPrint)
|
||||
export(renderTable)
|
||||
export(renderText)
|
||||
export(renderUI)
|
||||
export(repeatable)
|
||||
export(runApp)
|
||||
export(runExample)
|
||||
export(runGist)
|
||||
export(runGitHub)
|
||||
export(runUrl)
|
||||
export(selectInput)
|
||||
export(selectizeInput)
|
||||
export(shinyApp)
|
||||
export(shinyAppDir)
|
||||
export(shinyServer)
|
||||
export(shinyUI)
|
||||
export(showReactLog)
|
||||
export(sidebarLayout)
|
||||
export(sidebarPanel)
|
||||
export(singleton)
|
||||
export(sliderInput)
|
||||
export(span)
|
||||
export(splitLayout)
|
||||
export(stopApp)
|
||||
export(strong)
|
||||
export(submitButton)
|
||||
export(tableOutput)
|
||||
export(tabPanel)
|
||||
export(tableOutput)
|
||||
export(tabsetPanel)
|
||||
export(tag)
|
||||
export(tagAppendAttributes)
|
||||
export(tagAppendChild)
|
||||
export(tagAppendChildren)
|
||||
export(tagList)
|
||||
export(tagSetChildren)
|
||||
export(tags)
|
||||
export(textInput)
|
||||
export(textOutput)
|
||||
export(titlePanel)
|
||||
export(uiOutput)
|
||||
export(updateCheckboxGroupInput)
|
||||
export(updateCheckboxInput)
|
||||
export(updateDateInput)
|
||||
export(updateDateRangeInput)
|
||||
export(updateNumericInput)
|
||||
export(updateRadioButtons)
|
||||
export(updateSelectInput)
|
||||
export(updateSelectizeInput)
|
||||
export(updateSliderInput)
|
||||
export(updateTabsetPanel)
|
||||
export(updateTextInput)
|
||||
export(validate)
|
||||
export(validateCssUnit)
|
||||
export(verbatimTextOutput)
|
||||
S3method(as.character,shiny.tag)
|
||||
S3method(as.list,reactvaluesreader)
|
||||
S3method(format,shiny.tag)
|
||||
S3method(names,reactvaluesreader)
|
||||
S3method(print,shiny.tag)
|
||||
S3method(reactive,default)
|
||||
S3method(reactive,"function")
|
||||
S3method("$",reactvaluesreader)
|
||||
S3method("$<-",shinyoutput)
|
||||
export(verticalLayout)
|
||||
export(wellPanel)
|
||||
export(withMathJax)
|
||||
export(withReactiveDomain)
|
||||
export(withTags)
|
||||
import(RJSONIO)
|
||||
import(caTools)
|
||||
import(digest)
|
||||
import(htmltools)
|
||||
import(httpuv)
|
||||
import(methods)
|
||||
import(xtable)
|
||||
|
||||
547
NEWS
547
NEWS
@@ -1,3 +1,546 @@
|
||||
shiny 0.9.1.9XXX
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* BREAKING CHANGE: By default, observers now terminate themselves if they were
|
||||
created during a session and that session ends. See ?domains for more details.
|
||||
|
||||
* Most inputs can now accept `NULL` label values to omit the label altogether.
|
||||
|
||||
* New `actionLink` input control; like `actionButton`, but with the appearance
|
||||
of a normal link.
|
||||
|
||||
* `renderPlot` now calls `print` on its result if it's visible (i.e. no more
|
||||
explicit `print()` required for ggplot2).
|
||||
|
||||
* Added `maskReactiveContext` function. It blocks the current reactive context,
|
||||
to evaluate expressions that shouldn't use reactive sources directly. (This
|
||||
should not be commonly needed.)
|
||||
|
||||
* Added `flowLayout`, `splitLayout`, and `inputPanel` functions for putting UI
|
||||
elements side by side. `flowPanel` lays out its children in a left-to-right,
|
||||
top-to-bottom arrangement. `splitLayout` evenly divides its horizontal space
|
||||
among its children (or unevenly divides if `cellWidths` argument is provided).
|
||||
`inputPanel` is like `flowPanel`, but with a light grey background, and is
|
||||
intended to be used to encapsulate small input controls wherever vertical
|
||||
space is at a premium.
|
||||
|
||||
* `sliderInput` and `selectizeInput`/`selectInput` now use a standard horizontal
|
||||
size instead of filling up all available horizontal space.
|
||||
|
||||
* Fixed a bug of renderDataTable() when the data object only has 1 row and 1
|
||||
column. (Thanks, ZJ Dai, #429)
|
||||
|
||||
* `renderPrint` gained a new argument 'width' to control the width of the text
|
||||
output, e.g. renderPrint({mtcars}, width = 40).
|
||||
|
||||
* Fixed #220: the zip file for a directory created by some programs may not have
|
||||
the directory name as its first entry, in which case runUrl() can fail. (#220)
|
||||
|
||||
shiny 0.9.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fixed warning 'Error in Context$new : could not find function "loadMethod"'
|
||||
that was happening to dependent packages on "R CMD check".
|
||||
|
||||
shiny 0.9.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* BREAKING CHANGE: Added a `host` parameter to runApp() and runExample(),
|
||||
which defaults to the shiny.host option if it is non-NULL, or "127.0.0.1"
|
||||
otherwise. This means that by default, Shiny applications can only be
|
||||
accessed on the same machine from which they are served. To allow other
|
||||
clients to connect, as in previous versions of Shiny, use "0.0.0.0"
|
||||
(or the IP address of one of your network interfaces, if you care to be
|
||||
explicit about it).
|
||||
|
||||
* Added a new function `selectizeInput()` to use the JavaScript library
|
||||
selectize.js (https://github.com/brianreavis/selectize.js), which extends
|
||||
the basic select input in many aspects.
|
||||
|
||||
* The `selectInput()` function also gained a new argument `selectize = TRUE`
|
||||
to makes use of selectize.js by default. If you want to revert back to the
|
||||
original select input, you have to call selectInput(..., selectize = FALSE).
|
||||
|
||||
* Added Showcase mode, which displays the R code for an app right in the app
|
||||
itself. You can invoke Showcase mode by passing `display.mode="showcase"`
|
||||
to the `runApp()` function. Or, if an app is designed to run in Showcase
|
||||
mode by default, add a DESCRIPTION file in the app dir with Title, Author,
|
||||
and License fields; with "Type: Shiny"; and with "DisplayMode: Showcase".
|
||||
|
||||
* Upgraded to Bootstrap 2.3.2 and jQuery 1.11.0.
|
||||
|
||||
* Make `tags$head()` and `singleton()` behave correctly when used with
|
||||
`renderUI()` and `uiOutput()`. Previously, "hoisting content to the head"
|
||||
and "only rendering items a single time" were features that worked only
|
||||
when the page was initially loading, not in dynamic rendering.
|
||||
|
||||
* Files are now sourced with the `keep.source` option, to help with debugging
|
||||
and profiling.
|
||||
|
||||
* Support user-defined input parsers for data coming in from JavaScript using
|
||||
the parseShinyInput method.
|
||||
|
||||
* Fixed the bug #299: renderDataTable() can deal with 0-row data frames now.
|
||||
(reported by Harlan Harris)
|
||||
|
||||
* Added `navbarPage()` and `navbarMenu()` functions to create applications
|
||||
with multiple top level panels.
|
||||
|
||||
* Added `navlistPanel()` function to create layouts with a a bootstrap
|
||||
navlist on the left and tabPanels on the right
|
||||
|
||||
* Added `type` parameter to `tabsetPanel()` to enable the use of pill
|
||||
style tabs in addition to the standard ones.
|
||||
|
||||
* Added `position` paramter to `tabsetPanel()` to enable positioning of tabs
|
||||
above, below, left, or right of tab content.
|
||||
|
||||
* Added `fluidPage()` and `fixedPage()` functions as well as related row and
|
||||
column layout functions for creating arbitrary bootstrap grid layouts.
|
||||
|
||||
* Added `hr()` builder function for creating horizontal rules.
|
||||
|
||||
* Automatically concatenate duplicate attributes in tag definitions
|
||||
|
||||
* Added `responsive` parameter to page building functions for opting-out of
|
||||
bootstrap responsive css.
|
||||
|
||||
* Added `theme` parameter to page building functions for specifying alternate
|
||||
bootstrap css styles.
|
||||
|
||||
* Added `icon()` function for embedding icons from the
|
||||
[font awesome](http://fontawesome.io/) icon library
|
||||
|
||||
* Added `makeReactiveBinding` function to turn a "regular" variable into a
|
||||
reactive one (i.e. reading the variable makes the current reactive context
|
||||
dependent on it, and setting the variable is a source of reactivity).
|
||||
|
||||
* Added a function `withMathJax()` to include the MathJax library in an app.
|
||||
|
||||
* The argument `selected` in checkboxGroupInput(), selectInput(), and
|
||||
radioButtons() refers to the value(s) instead of the name(s) of the
|
||||
argument `choices` now. For example, the value of the `selected` argument
|
||||
in selectInput(..., choices = c('Label 1' = 'x1', 'Label 2' = 'x2'),
|
||||
selected = 'Label 2') must be updated to 'x2', although names/labels will
|
||||
be automatically converted to values internally for backward
|
||||
compatibility. The same change applies to updateCheckboxGroupInput(),
|
||||
updateSelectInput(), and updateRadioButtons() as well. (#340)
|
||||
|
||||
* Now it is possible to only update the value of a checkbox group, select input,
|
||||
or radio buttons using the `selected` argument without providing the
|
||||
`choices` argument in updateCheckboxGroupInput(), updateSelectInput(), and
|
||||
updateRadioButtons(), respectively. (#340)
|
||||
|
||||
* Added `absolutePanel` and `fixedPanel` functions for creating absolute-
|
||||
and fixed-position panels. They can be easily made user-draggable by
|
||||
specifying `draggable = TRUE`.
|
||||
|
||||
* For the `options` argument of the function `renderDataTable()`, we can
|
||||
pass literal JavaScript code to the DataTables library via `I()`. This
|
||||
makes it possible to use any JavaScript object in the options, e.g. a
|
||||
JavaScript function (which is not supported in JSON). See
|
||||
`?renderDataTable` for details and examples.
|
||||
|
||||
* DataTables also works under IE8 now.
|
||||
|
||||
* Fixed a bug in DataTables pagination when searching is turned on, which
|
||||
caused failures for matrices as well as empty rows when displaying data
|
||||
frames using renderDataTable().
|
||||
|
||||
* The `options` argument in `renderDataTable()` can also take a function
|
||||
that returns a list. This makes it possible to use reactive values in the
|
||||
options. (#392)
|
||||
|
||||
* `renderDataTable()` respects more DataTables options now: (1) either
|
||||
bPaginate = FALSE or iDisplayLength = -1 will disable pagination (i.e. all
|
||||
rows are returned from the data); besides, this means we can also use -1
|
||||
in the length menu, e.g. aLengthMenu = list(c(10, 30, -1), list(10, 30,
|
||||
'All')); (2) we can disable searching for individual columns through the
|
||||
bSearchable option, e.g. aoColumns = list(list(bSearchable = FALSE),
|
||||
list(bSearchable = TRUE),...) (the search box for the first column is
|
||||
hidden); (3) we can turn off searching entirely (for both global searching
|
||||
and individual columns) using the option bFilter = FALSE.
|
||||
|
||||
* Added an argument `callback` in `renderDataTable()` so that a custom
|
||||
JavaScript function can be applied to the DataTable object. This makes it
|
||||
much easier to use DataTables plug-ins.
|
||||
|
||||
* For numeric columns in a DataTable, the search boxes support lower and
|
||||
upper bounds now: a search query of the form "lower,upper" (without
|
||||
quotes) indicates the limits [lower, upper]. For a column X, this means
|
||||
the rows corresponding to X >= lower & X <= upper are returned. If we omit
|
||||
either the lower limit or the upper limit, only the other limit will be
|
||||
used, e.g. ",upper" means X <= upper.
|
||||
|
||||
* `updateNumericInput(value)` tries to preserve numeric precision by avoiding
|
||||
scientific notation when possible, e.g. 102145 is no longer rounded to
|
||||
1.0214e+05 = 102140. (Thanks, Martin Loos. #401)
|
||||
|
||||
* `sliderInput()` no longer treats a label wrapped in HTML() as plain text,
|
||||
e.g. the label in sliderInput(..., label = HTML('<em>A Label</em>')) will
|
||||
not be escaped any more. (#119)
|
||||
|
||||
* Fixed #306: the trailing slash in a path could fail `addResourcePath()`
|
||||
under Windows. (Thanks, ZJ Dai)
|
||||
|
||||
* Dots are now legal characters for inputId/outputId. (Thanks, Kevin
|
||||
Lindquist. #358)
|
||||
|
||||
shiny 0.8.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Debug hooks are registered on all user-provided functions and (reactive)
|
||||
expressions (e.g., in renderPlot()), which makes it possible to set
|
||||
breakpoints in these functions using the latest version of the RStudio
|
||||
IDE, and the RStudio visual debugging tools can be used to debug Shiny
|
||||
apps. Internally, the registration is done via installExprFunction(),
|
||||
which is a new function introduced in this version to replace
|
||||
exprToFunction() so that the registration can be automatically done.
|
||||
|
||||
* Added a new function renderDataTable() to display tables using the
|
||||
JavaScript library DataTables. It includes basic features like pagination,
|
||||
searching (global search or search by individual columns), sorting (by
|
||||
single or multiple columns). All these features are implemented on the R
|
||||
side; for example, we can use R regular expressions for searching.
|
||||
Besides, it also uses the Bootstrap CSS style. See the full
|
||||
documentation and examples in the tutorial:
|
||||
http://rstudio.github.io/shiny/tutorial/#datatables
|
||||
|
||||
* Added a new option `shiny.error` which can take a function as an error
|
||||
handler. It is called when an error occurs in an app (in user-provided
|
||||
code), e.g., after we set options(shiny.error = recover), we can enter a
|
||||
specified environment in the call stack to debug our code after an error
|
||||
occurs.
|
||||
|
||||
* The argument `launch.browser` in runApp() can also be a function,
|
||||
which takes the URL of the shiny app as its input value.
|
||||
|
||||
* runApp() uses a random port between 3000 and 8000 instead of 8100 now. It
|
||||
will try up to 20 ports in case certain ports are not available.
|
||||
|
||||
* Fixed a bug for conditional panels: the value `input.id` in the condition
|
||||
was not correctly retrieved when the input widget had a type, such as
|
||||
numericInput(). (reported by Jason Bryer)
|
||||
|
||||
* Fixed two bugs in plotOutput(); clickId and hoverId did not give correct
|
||||
coordinates in Firefox, or when the axis limits of the plot were changed.
|
||||
(reported by Chris Warth and Greg D)
|
||||
|
||||
* The minimal required version for the httpuv package was increased to 1.2
|
||||
(on CRAN now).
|
||||
|
||||
|
||||
shiny 0.7.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Stopped sending websocket subprotocol. This fixes a compatibility issue with
|
||||
Google Chrome 30.
|
||||
|
||||
* The `input` and `output` objects are now also accessible via `session$input`
|
||||
and `session$output`.
|
||||
|
||||
* Added click and hover events for static plots; see `?plotOutput` for details.
|
||||
|
||||
* Added optional logging of the execution states of a reactive program, and
|
||||
tools for visualizing the log data. To use, start a new R session and call
|
||||
`options(shiny.reactlog=TRUE)`. Then launch a Shiny app and interact with it.
|
||||
Press Ctrl+F3 (or for Mac, Cmd+F3) in the browser to launch an interactive
|
||||
visualization of the reactivity that has occurred. See `?showReactLog` for
|
||||
more information.
|
||||
|
||||
* Added `includeScript()` and `includeCSS()` functions.
|
||||
|
||||
* Reactive expressions now have class="reactive" attribute. Also added
|
||||
`is.reactive()` and `is.reactivevalues()` functions.
|
||||
|
||||
* New `stopApp()` function, which stops an app and returns a value to the caller
|
||||
of `runApp()`.
|
||||
|
||||
* Added the `shiny.usecairo` option, which can be used to tell Shiny not to use
|
||||
Cairo for PNG output even when it is installed. (Defaults to `TRUE`.)
|
||||
|
||||
* Speed increases for `selectInput()` and `radioButtons()`, and their
|
||||
corresponding updater functions, for when they have many options.
|
||||
|
||||
* Added `tagSetChildren()` and `tagAppendChildren()` functions.
|
||||
|
||||
* The HTTP request object that created the websocket is now accessible from the
|
||||
`session` object, as `session$request`. This is a Rook-like request
|
||||
environment that can be used to access HTTP headers, among other things.
|
||||
(Note: When running in a Shiny Server environment, the request will reflect
|
||||
the proxy HTTP request that was made from the Shiny Server process to the R
|
||||
process, not the request that was made from the web browser to Shiny Server.)
|
||||
|
||||
* Fix `getComputedStyle` issue, for IE8 browser compatibility (#196). Note:
|
||||
Shiny Server is still required for IE8/9 compatibility.
|
||||
|
||||
* Add shiny.sharedSecret option, to require the HTTP header Shiny-Shared-Secret
|
||||
to be set to the given value.
|
||||
|
||||
shiny 0.6.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* `tabsetPanel()` can be directed to start with a specific tab selected.
|
||||
|
||||
* Fix bug where multiple file uploads with 3 or more files result in incorrect
|
||||
data.
|
||||
|
||||
* Add `withTags()` function.
|
||||
|
||||
* Add dateInput and dateRangeInput.
|
||||
|
||||
* `shinyServer()` now takes an optional `session` argument, which is used for
|
||||
communication with the session object.
|
||||
|
||||
* Add functions to update values of existing inputs on a page, instead of
|
||||
replacing them entirely.
|
||||
|
||||
* Allow listening on domain sockets.
|
||||
|
||||
* Added `actionButton()` to Shiny.
|
||||
|
||||
* The server can now send custom JSON messages to the client. On the client
|
||||
side, functions can be registered to handle these messages.
|
||||
|
||||
* Callbacks can be registered to be called at the end of a client session.
|
||||
|
||||
* Add ability to set priority of observers and outputs. Each priority level
|
||||
gets its own queue.
|
||||
|
||||
* Fix bug where the presence of a submit button would prevent sending of
|
||||
metadata until the button was clicked.
|
||||
|
||||
* `reactiveTimer()` and `invalidateLater()` by default no longer invalidate
|
||||
reactive objects after the client session has closed.
|
||||
|
||||
* Shiny apps can be run without a server.r and ui.r file.
|
||||
|
||||
shiny 0.5.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Switch from websockets package for handling websocket connections to httpuv.
|
||||
|
||||
* New method for detecting hidden output objects. Instead of checking that
|
||||
height and width are 0, it checks that the object or any ancestor in the DOM
|
||||
has style display:none.
|
||||
|
||||
* Add `clientData` reactive values object, which carries information about the
|
||||
client. This includes the hidden status of output objects, height/width plot
|
||||
output objects, and the URL of the browser.
|
||||
|
||||
* Add `parseQueryString()` function.
|
||||
|
||||
* Add `renderImage()` function for sending arbitrary image files to the client,
|
||||
and its counterpart, `imageOutput()`.
|
||||
|
||||
* Add support for high-resolution (Retina) displays.
|
||||
|
||||
* Fix bug #55, where `renderTable()` would throw error with an empty data frame.
|
||||
|
||||
shiny 0.4.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix bug where width and height weren't passed along properly from
|
||||
`reactivePlot` to `renderPlot`.
|
||||
|
||||
* Fix bug where infinite recursion would happen when `reactivePlot` was passed
|
||||
a function for width or height.
|
||||
|
||||
shiny 0.4.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Added suspend/resume capability to observers.
|
||||
|
||||
* Output objects are automatically suspended when they are hidden on the user's
|
||||
web browser.
|
||||
|
||||
* `runGist()` accepts GitHub's new URL format, which includes the username.
|
||||
|
||||
* `reactive()` and `observe()` now take expressions instead of functions.
|
||||
|
||||
* `reactiveText()`, `reactivePlot()`, and so on, have been renamed to
|
||||
`renderText()`, `renderPlot()`, etc. They also now take expressions instead
|
||||
of functions.
|
||||
|
||||
* Fixed a bug where empty values in a numericInput were sent to the R process
|
||||
as 0. They are now sent as NA.
|
||||
|
||||
shiny 0.3.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix issue #91: bug where downloading files did not work.
|
||||
|
||||
* Add [[<- operator for shinyoutput object, making it possible to assign values
|
||||
with `output[['plot1']] <- ...`.
|
||||
|
||||
* Reactive functions now preserve the visible/invisible state of their returned
|
||||
values.
|
||||
|
||||
shiny 0.3.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Reactive functions are now evaluated lazily.
|
||||
|
||||
* Add `reactiveValues()`.
|
||||
|
||||
* Using `as.list()` to convert a reactivevalues object (like `input`) to a list
|
||||
is deprecated. The new function `reactiveValuesToList()` should be used
|
||||
instead.
|
||||
|
||||
* Add `isolate()`. This function is used for accessing reactive functions,
|
||||
without them invalidating their parent contexts.
|
||||
|
||||
* Fix issue #58: bug where reactive functions are not re-run when all items in
|
||||
a checkboxGroup are unchecked.
|
||||
|
||||
* Fix issue #71, where `reactiveTable()` would return blank if the first
|
||||
element of a data frame was NA.
|
||||
|
||||
* In `plotOutput`, better validation for CSS units when specifying width and
|
||||
height.
|
||||
|
||||
* `reactivePrint()` no longer displays invisible output.
|
||||
|
||||
* `reactiveText()` no longer displays printed output, only the return value
|
||||
from a function.
|
||||
|
||||
* The `runGitHub()` and `runUrl()` functions have been added, for running
|
||||
Shiny apps from GitHub repositories and zip/tar files at remote URLs.
|
||||
|
||||
* Fix issue #64, where pressing Enter in a textbox would cause a form to
|
||||
submit.
|
||||
|
||||
shiny 0.2.4
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* `runGist` has been updated to use the new download URLs from
|
||||
https://gist.github.com.
|
||||
|
||||
* Shiny now uses `CairoPNG()` for output, when the Cairo package is available.
|
||||
This provides better-looking output on Linux and Windows.
|
||||
|
||||
shiny 0.2.3
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Ignore request variables for routing purposes
|
||||
|
||||
shiny 0.2.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix CRAN warning (assigning to global environment)
|
||||
|
||||
|
||||
shiny 0.2.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* [BREAKING] Modify API of `downloadHandler`: The `content` function now takes
|
||||
a file path, not writable connection, as an argument. This makes it much
|
||||
easier to work with APIs that only write to file paths, not connections.
|
||||
|
||||
|
||||
shiny 0.2.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix subtle name resolution bug--the usual symptom being S4 methods not being
|
||||
invoked correctly when called from inside of ui.R or server.R
|
||||
|
||||
|
||||
shiny 0.1.14
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix slider animator, which broke in 0.1.10
|
||||
|
||||
|
||||
shiny 0.1.13
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix temp file leak in reactivePlot
|
||||
|
||||
|
||||
shiny 0.1.12
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix problems with runGist on Windows
|
||||
* Add feature for on-the-fly file downloads (e.g. CSV data, PDFs)
|
||||
* Add CSS hooks for app-wide busy indicators
|
||||
|
||||
|
||||
shiny 0.1.11
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix input binding with IE8 on Shiny Server
|
||||
* Fix issue #41: reactiveTable should allow print options too
|
||||
* Allow dynamic sizing of reactivePlot (i.e. using a function instead of a fixed
|
||||
value)
|
||||
|
||||
|
||||
shiny 0.1.10
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Support more MIME types when serving out of www
|
||||
* Fix issue #35: Allow modification of untar args
|
||||
* headerPanel can take an explicit window title parameter
|
||||
* checkboxInput uses correct attribute `checked` instead of `selected`
|
||||
* Fix plot rendering with IE8 on Shiny Server
|
||||
|
||||
|
||||
shiny 0.1.9
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Much less flicker when updating plots
|
||||
* More customizable error display
|
||||
* Add `includeText`, `includeHTML`, and `includeMarkdown` functions for putting
|
||||
text, HTML, and Markdown content from external files in the application's UI.
|
||||
|
||||
|
||||
shiny 0.1.8
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Add `runGist` function for conveniently running a Shiny app that is published
|
||||
on gist.github.com.
|
||||
* Fix issue #27: Warnings cause reactive functions to stop executing.
|
||||
* The server.R and ui.R filenames are now case insensitive.
|
||||
* Add `wellPanel` function for creating inset areas on the page.
|
||||
* Add `bootstrapPage` function for creating new Bootstrap based
|
||||
layouts from scratch.
|
||||
|
||||
|
||||
shiny 0.1.7
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix issue #26: Shiny.OutputBindings not correctly exported.
|
||||
* Add `repeatable` function for making easily repeatable versions of random
|
||||
number generating functions.
|
||||
* Transcode JSON into UTF-8 (prevents non-ASCII reactivePrint values from
|
||||
causing errors on Windows).
|
||||
|
||||
|
||||
shiny 0.1.6
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Import package dependencies, instead of attaching them (with the exception of
|
||||
websockets, which doesn't currently work unless attached).
|
||||
* conditionalPanel was animated, now it is not.
|
||||
* bindAll was not correctly sending initial values to the server; fixed.
|
||||
|
||||
|
||||
shiny 0.1.5
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* BREAKING CHANGE: JS APIs Shiny.bindInput and Shiny.bindOutput removed and
|
||||
replaced with Shiny.bindAll; Shiny.unbindInput and Shiny.unbindOutput removed
|
||||
and replaced with Shiny.unbindAll.
|
||||
* Add file upload support (currently only works with Chrome and Firefox). Use
|
||||
a normal HTML file input, or call the `fileInput` UI function.
|
||||
* Shiny.unbindOutputs did not work, now it does.
|
||||
* Generally improved robustness of dynamic input/output bindings.
|
||||
* Add conditionalPanel UI function to allow showing/hiding UI based on a JS
|
||||
expression; for example, whether an input is a particular value. Also works in
|
||||
raw HTML (add the `data-display-if` attribute to the element that should be
|
||||
shown/hidden).
|
||||
* htmlOutput (CSS class `shiny-html-output`) can contain inputs and outputs.
|
||||
|
||||
|
||||
shiny 0.1.4
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -20,11 +563,11 @@ shiny 0.1.3
|
||||
creating custom input controls
|
||||
* Add `step` parameter to numericInput
|
||||
* Read names of input using `names(input)`
|
||||
* Access snapshot of input as a list using `as.list(input)`
|
||||
* Access snapshot of input as a list using `as.list(input)`
|
||||
* Fix issue #10: Plots in tabsets not rendered
|
||||
|
||||
|
||||
shiny 0.1.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Initial private beta release!
|
||||
Initial private beta release!
|
||||
|
||||
288
R/app.R
Normal file
288
R/app.R
Normal file
@@ -0,0 +1,288 @@
|
||||
# TODO: Subapp global.R
|
||||
|
||||
#' 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.
|
||||
#'
|
||||
#' @param ui The UI definition of the app (for example, a call to
|
||||
#' \code{fluidPage()} with nested controls)
|
||||
#' @param server A server function
|
||||
#' @param onStart A function that will be called before the app is actually run.
|
||||
#' This is only needed for \code{shinyAppObj}, since in the \code{shinyAppDir}
|
||||
#' case, a \code{global.R} file can be used for this purpose.
|
||||
#' @param options Named options that should be passed to the `runApp` call. You
|
||||
#' can also specify \code{width} and \code{height} parameters which provide a
|
||||
#' hint to the embedding environment about the ideal height/width for the app.
|
||||
#' @param uiPattern A regular expression that will be applied to each \code{GET}
|
||||
#' request to determine whether the \code{ui} should be used to handle the
|
||||
#' request. Note that the entire request path must match the regular
|
||||
#' expression in order for the match to be considered successful.
|
||||
#' @return An object that represents the app. Printing the object will run the
|
||||
#' app.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' numericInput("n", "n", 1),
|
||||
#' plotOutput("plot")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot( plot(head(cars, input$n)) )
|
||||
#' },
|
||||
#' options=list(launch.browser = rstudio::viewer)
|
||||
#' )
|
||||
#'
|
||||
#' shinyAppDir(system.file("examples/01_hello", package="shiny"))
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
shinyApp <- function(ui, server, onStart=NULL, options=list(), uiPattern="/") {
|
||||
# Ensure that the entire path is a match
|
||||
uiPattern <- sprintf("^%s$", uiPattern)
|
||||
|
||||
httpHandler <- function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
if (!isTRUE(grepl(uiPattern, req$PATH_INFO)))
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
on.exit(close(textConn))
|
||||
|
||||
uiValue <- if (is.function(ui)) {
|
||||
if (length(formals(ui)) > 0)
|
||||
ui(req)
|
||||
else
|
||||
ui()
|
||||
} else {
|
||||
ui
|
||||
}
|
||||
if (is.null(uiValue))
|
||||
return(NULL)
|
||||
|
||||
renderPage(uiValue, textConn)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(httpResponse(200, content=html))
|
||||
}
|
||||
|
||||
serverFuncSource <- function() {
|
||||
server
|
||||
}
|
||||
|
||||
structure(
|
||||
list(
|
||||
httpHandler = httpHandler,
|
||||
serverFuncSource = serverFuncSource,
|
||||
onStart = onStart,
|
||||
options = options),
|
||||
class = "shiny.appobj"
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param appDir Path to directory that contains a Shiny app (i.e. a server.R
|
||||
#' file and either ui.R or www/index.html)
|
||||
#' @export
|
||||
shinyAppDir <- 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.
|
||||
|
||||
if (!file.exists(appDir)) {
|
||||
stop("No Shiny application exists at the path \"", appDir, "\"")
|
||||
}
|
||||
|
||||
# In case it's a relative path, convert to absolute (so we're not adversely
|
||||
# affected by future changes to the path)
|
||||
appDir <- normalizePath(appDir, mustWork = TRUE)
|
||||
|
||||
# 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.
|
||||
uiHandlerSource <- cachedFuncWithFile(appDir, "ui.R", case.sensitive = FALSE,
|
||||
function(uiR) {
|
||||
if (file.exists(uiR)) {
|
||||
# If ui.R contains a call to shinyUI (which sets .globals$ui), use that.
|
||||
# If not, then take the last expression that's returned from ui.R.
|
||||
.globals$ui <- NULL
|
||||
on.exit(.globals$ui <- NULL, add = FALSE)
|
||||
ui <- source(uiR,
|
||||
local = new.env(parent = globalenv()),
|
||||
keep.source = TRUE)$value
|
||||
if (!is.null(.globals$ui)) {
|
||||
ui <- .globals$ui[[1]]
|
||||
}
|
||||
return(uiHttpHandler(ui))
|
||||
} else {
|
||||
return(function(req) NULL)
|
||||
}
|
||||
}
|
||||
)
|
||||
uiHandler <- function(req) {
|
||||
uiHandlerSource()(req)
|
||||
}
|
||||
|
||||
wwwDir <- file.path.ci(appDir, "www")
|
||||
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),
|
||||
# use that. If not, then take the last expression that's returned from
|
||||
# server.R.
|
||||
.globals$server <- NULL
|
||||
on.exit(.globals$server <- NULL, add = TRUE)
|
||||
result <- source(
|
||||
serverR,
|
||||
local = new.env(parent = globalenv()),
|
||||
keep.source = TRUE
|
||||
)$value
|
||||
if (!is.null(.globals$server)) {
|
||||
result <- .globals$server[[1]]
|
||||
}
|
||||
return(result)
|
||||
}
|
||||
)
|
||||
|
||||
# This function stands in for the server function, and reloads the
|
||||
# real server function as necessary whenever server.R changes
|
||||
serverFuncSource <- function() {
|
||||
serverFunction <- serverSource()
|
||||
if (is.null(serverFunction)) {
|
||||
return(function(input, output) NULL)
|
||||
} else if (is.function(serverFunction)) {
|
||||
# This is what we normally expect; run the server function
|
||||
return(serverFunction)
|
||||
} else {
|
||||
stop("server.R returned an object of unexpected type: ",
|
||||
typeof(serverFunction))
|
||||
}
|
||||
}
|
||||
|
||||
oldwd <- NULL
|
||||
onStart <- function() {
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
if (file.exists(file.path.ci(appDir, "global.R")))
|
||||
source(file.path.ci(appDir, "global.R"), keep.source = TRUE)
|
||||
}
|
||||
onEnd <- function() {
|
||||
setwd(oldwd)
|
||||
}
|
||||
|
||||
structure(
|
||||
list(
|
||||
httpHandler = joinHandlers(c(uiHandler, wwwDir, fallbackWWWDir)),
|
||||
serverFuncSource = serverFuncSource,
|
||||
onStart = onStart,
|
||||
onEnd = onEnd,
|
||||
options = options),
|
||||
class = "shiny.appobj"
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param x Object to convert to a Shiny app.
|
||||
#' @export
|
||||
as.shiny.appobj <- function(x) {
|
||||
UseMethod("as.shiny.appobj", x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
as.shiny.appobj.shiny.appobj <- function(x) {
|
||||
x
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
as.shiny.appobj.list <- function(x) {
|
||||
shinyApp(ui = x$ui, server = x$server)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
as.shiny.appobj.character <- function(x) {
|
||||
shinyAppDir(x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param ... Additional parameters to be passed to print.
|
||||
#' @export
|
||||
print.shiny.appobj <- function(x, ...) {
|
||||
opts <- x$options %OR% list()
|
||||
opts <- opts[names(opts) %in%
|
||||
c("port", "launch.browser", "host", "quiet", "display.mode")]
|
||||
|
||||
args <- c(list(x), opts)
|
||||
|
||||
do.call(runApp, args)
|
||||
}
|
||||
|
||||
#' 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
|
||||
|
||||
#' @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
|
||||
shiny_warning <- NULL
|
||||
# if there's an R Markdown runtime option set but it isn't set to Shiny, then
|
||||
# emit a warning indicating the runtime is inappropriate for this object
|
||||
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
|
||||
shiny_warning <- list(structure(
|
||||
"Shiny application in a static R Markdown document",
|
||||
class = "rmd_warning"))
|
||||
|
||||
# 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 <- tags$iframe(src=path, width=width, height=height,
|
||||
class="shiny-frame")
|
||||
}
|
||||
|
||||
# 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_warning, cacheable = FALSE)
|
||||
}
|
||||
|
||||
# Lets us use a nicer syntax in knitr chunks than literally
|
||||
# calling output$value <- renderFoo(...) and fooOutput().
|
||||
#' @rdname knitr_methods
|
||||
#' @export
|
||||
knit_print.shiny.render.function <- function(x, ...) {
|
||||
output <- knitr::knit_print(tagList(x))
|
||||
attr(output, "knit_cacheable") <- FALSE
|
||||
output
|
||||
}
|
||||
421
R/bootstrap-layout.R
Normal file
421
R/bootstrap-layout.R
Normal file
@@ -0,0 +1,421 @@
|
||||
|
||||
#' Create a page with fluid layout
|
||||
#'
|
||||
#' Functions for creating fluid page layouts. A fluid page layout consists of
|
||||
#' rows which in turn include columns. Rows exist for the purpose of making sure
|
||||
#' their elements appear on the same line (if the browser has adequate width).
|
||||
#' Columns exist for the purpose of defining how much horizontal space within a
|
||||
#' 12-unit wide grid it's elements should occupy. Fluid pages scale their
|
||||
#' components in realtime to fill all available browser width.
|
||||
#'
|
||||
#' @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.
|
||||
#' @param responsive \code{TRUE} to use responsive layout (automatically adapt
|
||||
#' and resize page elements based on the size of the viewing device)
|
||||
#' @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"}.
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the \link{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
|
||||
#' alternative to low-level row and column functions you can also use
|
||||
#' higher-level layout functions like \code{\link{sidebarLayout}}.
|
||||
#'
|
||||
#' @note See the
|
||||
#' \href{https://github.com/rstudio/shiny/wiki/Shiny-Application-Layout-Guide}{
|
||||
#' Shiny-Application-Layout-Guide} for additional details on laying out fluid
|
||||
#' pages.
|
||||
#'
|
||||
#' @seealso \code{\link{column}}, \code{\link{sidebarLayout}}
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fluidPage(
|
||||
#'
|
||||
#' # Application title
|
||||
#' titlePanel("Hello Shiny!"),
|
||||
#'
|
||||
#' sidebarLayout(
|
||||
#'
|
||||
#' # Sidebar with a slider input
|
||||
#' sidebarPanel(
|
||||
#' sliderInput("obs",
|
||||
#' "Number of observations:",
|
||||
#' min = 0,
|
||||
#' max = 1000,
|
||||
#' value = 500)
|
||||
#' ),
|
||||
#'
|
||||
#' # Show a plot of the generated distribution
|
||||
#' mainPanel(
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' shinyUI(fluidPage(
|
||||
#' title = "Hello Shiny!",
|
||||
#' fluidRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidPage <- function(..., title = NULL, responsive = TRUE, theme = NULL) {
|
||||
bootstrapPage(div(class = "container-fluid", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
theme = theme)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidRow <- function(...) {
|
||||
div(class = "row-fluid", ...)
|
||||
}
|
||||
|
||||
#' Create a page with a fixed layout
|
||||
#'
|
||||
#' Functions for creating fixed page layouts. A fixed page layout consists of
|
||||
#' rows which in turn include columns. Rows exist for the purpose of making sure
|
||||
#' their elements appear on the same line (if the browser has adequate width).
|
||||
#' Columns exist for the purpose of defining how much horizontal space within a
|
||||
#' 12-unit wide grid it's elements should occupy. Fixed pages limit their width
|
||||
#' to 940 pixels on a typical display, and 724px or 1170px on smaller and larger
|
||||
#' displays respectively.
|
||||
#'
|
||||
#' @param ... Elements to include within the container
|
||||
#' @param title The browser window title (defaults to the host URL of the page)
|
||||
#' @param responsive \code{TRUE} to use responsive layout (automatically adapt
|
||||
#' and resize page elements based on the size of the viewing device)
|
||||
#' @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"}.
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the \link{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}.
|
||||
#'
|
||||
#' @note See the
|
||||
#' \href{https://github.com/rstudio/shiny/wiki/Shiny-Application-Layout-Guide}{
|
||||
#' Shiny Application Layout Guide} for additional details on laying out fixed
|
||||
#' pages.
|
||||
#'
|
||||
#' @seealso \code{\link{column}}
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fixedPage(
|
||||
#' title = "Hello, Shiny!",
|
||||
#' fixedRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' @rdname fixedPage
|
||||
#' @export
|
||||
fixedPage <- function(..., title = NULL, responsive = TRUE, theme = NULL) {
|
||||
bootstrapPage(div(class = "container", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
theme = theme)
|
||||
}
|
||||
|
||||
#' @rdname fixedPage
|
||||
#' @export
|
||||
fixedRow <- function(...) {
|
||||
div(class = "row", ...)
|
||||
}
|
||||
|
||||
|
||||
#' Create a column within a UI definition
|
||||
#'
|
||||
#' Create a column for use within a \code{\link{fluidRow}} or
|
||||
#' \code{\link{fixedRow}}
|
||||
#'
|
||||
#' @param width The grid width of the column (must be between 1 and 12)
|
||||
#' @param ... Elements to include within the column
|
||||
#' @param offset The number of columns to offset this column from the end of the
|
||||
#' previous column.
|
||||
#'
|
||||
#' @return A column that can be included within a
|
||||
#' \code{\link{fluidRow}} or \code{\link{fixedRow}}.
|
||||
#'
|
||||
#'
|
||||
#' @seealso \code{\link{fluidRow}}, \code{\link{fixedRow}}.
|
||||
#'
|
||||
#' @examples
|
||||
#' fluidRow(
|
||||
#' column(4,
|
||||
#' sliderInput("obs", "Number of observations:",
|
||||
#' min = 1, max = 1000, value = 500)
|
||||
#' ),
|
||||
#' column(8,
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' fluidRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' @export
|
||||
column <- function(width, ..., offset = 0) {
|
||||
|
||||
if (!is.numeric(width) || (width < 1) || (width > 12))
|
||||
stop("column width must be between 1 and 12")
|
||||
|
||||
colClass <- paste0("span", width)
|
||||
if (offset > 0)
|
||||
colClass <- paste0(colClass, " offset", offset)
|
||||
div(class = colClass, ...)
|
||||
}
|
||||
|
||||
|
||||
#' Create a panel containing an application title.
|
||||
#'
|
||||
#' @param title An application title to display
|
||||
#' @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
|
||||
#' explicitly using the `title` parameter of the top-level page function.
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' titlePanel("Hello Shiny!")
|
||||
#'
|
||||
#' @export
|
||||
titlePanel <- function(title, windowTitle=title) {
|
||||
tagList(
|
||||
tags$head(tags$title(windowTitle)),
|
||||
h2(style = "padding: 10px 0px;", 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
|
||||
#' 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 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
|
||||
#' layout.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Define UI
|
||||
#' shinyUI(fluidPage(
|
||||
#'
|
||||
#' # Application title
|
||||
#' titlePanel("Hello Shiny!"),
|
||||
#'
|
||||
#' sidebarLayout(
|
||||
#'
|
||||
#' # Sidebar with a slider input
|
||||
#' sidebarPanel(
|
||||
#' sliderInput("obs",
|
||||
#' "Number of observations:",
|
||||
#' min = 0,
|
||||
#' max = 1000,
|
||||
#' value = 500)
|
||||
#' ),
|
||||
#'
|
||||
#' # Show a plot of the generated distribution
|
||||
#' mainPanel(
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' @export
|
||||
sidebarLayout <- function(sidebarPanel,
|
||||
mainPanel,
|
||||
position = c("left", "right"),
|
||||
fluid = TRUE) {
|
||||
|
||||
# determine the order
|
||||
position <- match.arg(position)
|
||||
if (position == "left") {
|
||||
firstPanel <- sidebarPanel
|
||||
secondPanel <- mainPanel
|
||||
}
|
||||
else if (position == "right") {
|
||||
firstPanel <- mainPanel
|
||||
secondPanel <- sidebarPanel
|
||||
}
|
||||
|
||||
# return as as row
|
||||
if (fluid)
|
||||
fluidRow(firstPanel, secondPanel)
|
||||
else
|
||||
fixedRow(firstPanel, secondPanel)
|
||||
}
|
||||
|
||||
#' 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
|
||||
#' layout.
|
||||
#'
|
||||
#' @seealso \code{\link{fluidPage}}, \code{\link{flowLayout}}
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fluidPage(
|
||||
#' verticalLayout(
|
||||
#' a(href="http://example.com/link1", "Link One"),
|
||||
#' a(href="http://example.com/link2", "Link Two"),
|
||||
#' a(href="http://example.com/link3", "Link Three")
|
||||
#' )
|
||||
#' ))
|
||||
#' @export
|
||||
verticalLayout <- function(..., fluid = TRUE) {
|
||||
lapply(list(...), function(row) {
|
||||
col <- column(12, row)
|
||||
if (fluid)
|
||||
fluidRow(col)
|
||||
else
|
||||
fixedRow(col)
|
||||
})
|
||||
}
|
||||
|
||||
#' Flow layout
|
||||
#'
|
||||
#' 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. `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}}
|
||||
#'
|
||||
#' #' @examples
|
||||
#' flowLayout(
|
||||
#' numericInput("rows", "How many rows?", 5),
|
||||
#' selectInput("letter", "Which letter?", LETTERS),
|
||||
#' sliderInput("value", "What value?", 0, 100, 50)
|
||||
#' )
|
||||
#' @export
|
||||
flowLayout <- function(..., cellArgs = list()) {
|
||||
|
||||
children <- list(...)
|
||||
childIdx <- !nzchar(names(children) %OR% character(length(children)))
|
||||
attribs <- children[!childIdx]
|
||||
children <- children[childIdx]
|
||||
|
||||
do.call(tags$div, c(list(class = "shiny-flow-layout"),
|
||||
attribs,
|
||||
lapply(children, function(x) {
|
||||
do.call(tags$div, c(cellArgs, list(x)))
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
#' Input panel
|
||||
#'
|
||||
#' A \code{\link{flowLayout}} with a grey border and light grey background,
|
||||
#' suitable for wrapping inputs.
|
||||
#'
|
||||
#' @param ... Input controls or other HTML elements.
|
||||
#'
|
||||
#' @export
|
||||
inputPanel <- function(...) {
|
||||
div(class = "shiny-input-panel",
|
||||
flowLayout(...)
|
||||
)
|
||||
}
|
||||
|
||||
#' Split layout
|
||||
#'
|
||||
#' Lays out elements horizontally, dividing the available horizontal space into
|
||||
#' equal parts (by default).
|
||||
#'
|
||||
#' @param ... Unnamed arguments will become child elements of the layout. Named
|
||||
#' 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
|
||||
#' values as pixels.
|
||||
#' @param cellArgs Any additional attributes that should be used for each cell
|
||||
#' of the layout.
|
||||
#'
|
||||
#' #' @examples
|
||||
#' # Equal sizing
|
||||
#' splitLayout(
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2")
|
||||
#' )
|
||||
#'
|
||||
#' # Custom widths
|
||||
#' splitLayout(cellWidths = c("25%", "75%"),
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2")
|
||||
#' )
|
||||
#'
|
||||
#' # All cells at 300 pixels wide, with cell padding
|
||||
#' # and a border around everything
|
||||
#' splitLayout(
|
||||
#' style = "border: 1px solid silver;",
|
||||
#' cellWidths = 300,
|
||||
#' cellArgs = list(style = "padding: 6px"),
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2"),
|
||||
#' plotOutput("plot3")
|
||||
#' )
|
||||
#' @export
|
||||
splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) {
|
||||
|
||||
children <- list(...)
|
||||
childIdx <- !nzchar(names(children) %OR% character(length(children)))
|
||||
attribs <- children[!childIdx]
|
||||
children <- children[childIdx]
|
||||
count <- length(children)
|
||||
|
||||
if (length(cellWidths) == 0 || is.na(cellWidths)) {
|
||||
cellWidths <- sprintf("%.3f%%", 100 / count)
|
||||
}
|
||||
cellWidths <- rep(cellWidths, length.out = count)
|
||||
cellWidths <- sapply(cellWidths, validateCssUnit)
|
||||
|
||||
do.call(tags$div, c(list(class = "shiny-split-layout"),
|
||||
attribs,
|
||||
mapply(children, cellWidths, FUN = function(x, w) {
|
||||
do.call(tags$div, c(
|
||||
list(style = sprintf("width: %s;", w)),
|
||||
cellArgs,
|
||||
list(x)
|
||||
))
|
||||
}, SIMPLIFY = FALSE)
|
||||
))
|
||||
}
|
||||
1712
R/bootstrap.R
1712
R/bootstrap.R
File diff suppressed because it is too large
Load Diff
80
R/cache.R
Normal file
80
R/cache.R
Normal file
@@ -0,0 +1,80 @@
|
||||
# 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 <- setRefClass(
|
||||
'CacheContext',
|
||||
fields = list(
|
||||
.dirty = 'logical',
|
||||
.tests = 'list'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
.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)
|
||||
}
|
||||
110
R/fileupload.R
Normal file
110
R/fileupload.R
Normal file
@@ -0,0 +1,110 @@
|
||||
# For HTML5-capable browsers, file uploads happen through a series of requests.
|
||||
#
|
||||
# 1. Client tells server that one or more files are about to be uploaded; the
|
||||
# server responds with a "job ID" that the client should use for the rest of
|
||||
# the upload.
|
||||
#
|
||||
# 2. For each file (sequentially):
|
||||
# a. Client tells server the name, size, and type of the file.
|
||||
# b. Client sends server a small-ish blob of data.
|
||||
# c. Repeat 2b until the entire file has been uploaded.
|
||||
# d. Client tells server that the current file is done.
|
||||
#
|
||||
# 3. Repeat 2 until all files have been uploaded.
|
||||
#
|
||||
# 4. Client tells server that all files have been uploaded, along with the
|
||||
# input ID that this data should be associated with.
|
||||
#
|
||||
# Unfortunately this approach will not work for browsers that don't support
|
||||
# HTML5 File API, but the fallback approach we would like to use (multipart
|
||||
# form upload, i.e. traditional HTTP POST-based file upload) doesn't work with
|
||||
# the websockets package's HTTP server at the moment.
|
||||
|
||||
FileUploadOperation <- setRefClass(
|
||||
'FileUploadOperation',
|
||||
fields = list(
|
||||
.parent = 'ANY',
|
||||
.id = 'character',
|
||||
.files = 'data.frame',
|
||||
.dir = 'character',
|
||||
.currentFileInfo = 'list',
|
||||
.currentFileData = 'ANY',
|
||||
.pendingFileInfos = 'list'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function(parent, id, dir, fileInfos) {
|
||||
.parent <<- parent
|
||||
.id <<- id
|
||||
.files <<- data.frame(name=character(),
|
||||
size=numeric(),
|
||||
type=character(),
|
||||
datapath=character(),
|
||||
stringsAsFactors=FALSE)
|
||||
.dir <<- dir
|
||||
.pendingFileInfos <<- fileInfos
|
||||
},
|
||||
fileBegin = function() {
|
||||
if (length(.pendingFileInfos) < 1)
|
||||
stop("fileBegin called too many times")
|
||||
|
||||
file <- .pendingFileInfos[[1]]
|
||||
.currentFileInfo <<- file
|
||||
.pendingFileInfos <<- tail(.pendingFileInfos, -1)
|
||||
|
||||
filename <- file.path(.dir, as.character(length(.files$name)))
|
||||
row <- data.frame(name=file$name, size=file$size, type=file$type,
|
||||
datapath=filename, stringsAsFactors=FALSE)
|
||||
|
||||
if (length(.files$name) == 0)
|
||||
.files <<- row
|
||||
else
|
||||
.files <<- rbind(.files, row)
|
||||
|
||||
.currentFileData <<- file(filename, open='wb')
|
||||
},
|
||||
fileChunk = function(rawdata) {
|
||||
writeBin(rawdata, .currentFileData)
|
||||
},
|
||||
fileEnd = function() {
|
||||
close(.currentFileData)
|
||||
},
|
||||
finish = function() {
|
||||
if (length(.pendingFileInfos) > 0)
|
||||
stop("File upload job was stopped prematurely")
|
||||
.parent$onJobFinished(.id)
|
||||
return(.files)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#' @include map.R
|
||||
FileUploadContext <- setRefClass(
|
||||
'FileUploadContext',
|
||||
fields = list(
|
||||
.basedir = 'character',
|
||||
.operations = 'Map'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function(dir=tempdir()) {
|
||||
.basedir <<- dir
|
||||
},
|
||||
createUploadOperation = function(fileInfos) {
|
||||
while (TRUE) {
|
||||
id <- paste(as.raw(p_runif(12, min=0, max=0xFF)), collapse='')
|
||||
dir <- file.path(.basedir, id)
|
||||
if (!dir.create(dir))
|
||||
next
|
||||
|
||||
op <- FileUploadOperation$new(.self, id, dir, fileInfos)
|
||||
.operations$set(id, op)
|
||||
return(id)
|
||||
}
|
||||
},
|
||||
getUploadOperation = function(jobId) {
|
||||
.operations$get(jobId)
|
||||
},
|
||||
onJobFinished = function(jobId) {
|
||||
.operations$remove(jobId)
|
||||
}
|
||||
)
|
||||
)
|
||||
9
R/globals.R
Normal file
9
R/globals.R
Normal file
@@ -0,0 +1,9 @@
|
||||
# A scope where we can put mutable global state
|
||||
.globals <- new.env(parent = emptyenv())
|
||||
|
||||
.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))
|
||||
}
|
||||
103
R/graph.R
Normal file
103
R/graph.R
Normal file
@@ -0,0 +1,103 @@
|
||||
writeReactLog <- function(file=stdout()) {
|
||||
cat(RJSONIO::toJSON(.graphEnv$log, pretty=TRUE), file=file)
|
||||
}
|
||||
|
||||
#' 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
|
||||
#' any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your
|
||||
#' web browser to launch the reactive log visualization.
|
||||
#'
|
||||
#' The reactive log visualization only includes reactive activity up
|
||||
#' until the time the report was loaded. If you want to see more recent
|
||||
#' activity, refresh the browser.
|
||||
#'
|
||||
#' Note that Shiny does not distinguish between reactive dependencies
|
||||
#' that "belong" to one Shiny user session versus another, so the
|
||||
#' visualization will include all reactive activity that has taken place
|
||||
#' in the process, not just for a particular application or session.
|
||||
#'
|
||||
#' 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
|
||||
#' 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.
|
||||
#'
|
||||
#' For security and performance reasons, do not enable
|
||||
#' \code{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.
|
||||
#'
|
||||
#' @export
|
||||
showReactLog <- function() {
|
||||
browseURL(renderReactLog())
|
||||
}
|
||||
|
||||
renderReactLog <- function() {
|
||||
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)
|
||||
cat('\n', file=tc)
|
||||
flush(tc)
|
||||
html <- sub('__DATA__', paste(textConnectionValue(tc), collapse='\r\n'), html, fixed=TRUE)
|
||||
file <- tempfile(fileext = '.html')
|
||||
writeLines(html, file)
|
||||
return(file)
|
||||
}
|
||||
|
||||
.graphAppend <- function(logEntry, domain = getDefaultReactiveDomain()) {
|
||||
if (isTRUE(getOption('shiny.reactlog', FALSE)))
|
||||
.graphEnv$log <- c(.graphEnv$log, list(logEntry))
|
||||
|
||||
if (!is.null(domain)) {
|
||||
domain$reactlog(logEntry)
|
||||
}
|
||||
}
|
||||
|
||||
.graphDependsOn <- function(id, label) {
|
||||
.graphAppend(list(action='dep', id=id, dependsOn=label))
|
||||
}
|
||||
|
||||
.graphDependsOnId <- function(id, dependee) {
|
||||
.graphAppend(list(action='depId', id=id, dependsOn=dependee))
|
||||
}
|
||||
|
||||
.graphCreateContext <- function(id, label, type, prevId, domain) {
|
||||
.graphAppend(list(
|
||||
action='ctx', id=id, label=paste(label, collapse='\n'),
|
||||
srcref=attr(label, "srcref"), srcfile=attr(label, "srcfile"),
|
||||
type=type, prevId=prevId
|
||||
), domain = domain)
|
||||
}
|
||||
|
||||
.graphEnterContext <- function(id) {
|
||||
.graphAppend(list(action='enter', id=id))
|
||||
}
|
||||
|
||||
.graphExitContext <- function(id) {
|
||||
.graphAppend(list(action='exit', id=id))
|
||||
}
|
||||
|
||||
.graphValueChange <- function(label, value) {
|
||||
.graphAppend(list(
|
||||
action = 'valueChange',
|
||||
id = label,
|
||||
value = paste(capture.output(str(value)), collapse='\n')
|
||||
))
|
||||
}
|
||||
|
||||
.graphInvalidate <- function(id, domain) {
|
||||
.graphAppend(list(action='invalidate', id=id), domain)
|
||||
}
|
||||
|
||||
.graphEnv <- new.env()
|
||||
.graphEnv$log <- list()
|
||||
24
R/hooks.R
Normal file
24
R/hooks.R
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
|
||||
# Call an application hook. Application hooks are provided so that front ends
|
||||
# can know when a Shiny application is running:
|
||||
#
|
||||
# shiny.onAppStart -- called when an application begins running
|
||||
# shiny.onAppStop -- called when an appliation stops
|
||||
#
|
||||
# Both hooks are passed the url where the application is accessible (appUrl).
|
||||
# Note that the appUrl can be NULL if the application was run on a UNIX domain
|
||||
# socket rather than a TCP/IP port/
|
||||
callAppHook <- function(name, appUrl) {
|
||||
for (hook in getHooksList(paste0("shiny.", name)))
|
||||
hook(appUrl)
|
||||
}
|
||||
|
||||
# The value for getHook can be a single function or a list of functions,
|
||||
# This function ensures that the result can always be processed as a list
|
||||
getHooksList <- function(name) {
|
||||
hooks <- getHook(name)
|
||||
if (!is.list(hooks))
|
||||
hooks <- list(hooks)
|
||||
hooks
|
||||
}
|
||||
15
R/html-deps.R
Normal file
15
R/html-deps.R
Normal file
@@ -0,0 +1,15 @@
|
||||
createWebDependency <- function(dependency) {
|
||||
if (is.null(dependency))
|
||||
return(NULL)
|
||||
|
||||
if (!inherits(dependency, "html_dependency"))
|
||||
stop("Unexpected non-html_dependency type")
|
||||
|
||||
if (is.null(dependency$src$href)) {
|
||||
prefix <- paste(dependency$name, "-", dependency$version, sep = "")
|
||||
addResourcePath(prefix, dependency$src$file)
|
||||
dependency$src$href <- prefix
|
||||
}
|
||||
|
||||
return(dependency)
|
||||
}
|
||||
101
R/htmltools.R
Normal file
101
R/htmltools.R
Normal file
@@ -0,0 +1,101 @@
|
||||
#' @export
|
||||
a <- htmltools::a
|
||||
|
||||
#' @export
|
||||
br <- htmltools::br
|
||||
|
||||
#' @export
|
||||
code <- htmltools::code
|
||||
|
||||
#' @export
|
||||
div <- htmltools::div
|
||||
|
||||
#' @export
|
||||
em <- htmltools::em
|
||||
|
||||
#' @export
|
||||
h1 <- htmltools::h1
|
||||
|
||||
#' @export
|
||||
h2 <- htmltools::h2
|
||||
|
||||
#' @export
|
||||
h3 <- htmltools::h3
|
||||
|
||||
#' @export
|
||||
h4 <- htmltools::h4
|
||||
|
||||
#' @export
|
||||
h5 <- htmltools::h5
|
||||
|
||||
#' @export
|
||||
h6 <- htmltools::h6
|
||||
|
||||
#' @export
|
||||
hr <- htmltools::hr
|
||||
|
||||
#' @export
|
||||
HTML <- htmltools::HTML
|
||||
|
||||
#' @export
|
||||
img <- htmltools::img
|
||||
|
||||
#' @export
|
||||
includeCSS <- htmltools::includeCSS
|
||||
|
||||
#' @export
|
||||
includeHTML <- htmltools::includeHTML
|
||||
|
||||
#' @export
|
||||
includeMarkdown <- htmltools::includeMarkdown
|
||||
|
||||
#' @export
|
||||
includeScript <- htmltools::includeScript
|
||||
|
||||
#' @export
|
||||
includeText <- htmltools::includeText
|
||||
|
||||
#' @export
|
||||
is.singleton <- htmltools::is.singleton
|
||||
|
||||
#' @export
|
||||
p <- htmltools::p
|
||||
|
||||
#' @export
|
||||
pre <- htmltools::pre
|
||||
|
||||
#' @export
|
||||
singleton <- htmltools::singleton
|
||||
|
||||
#' @export
|
||||
span <- htmltools::span
|
||||
|
||||
#' @export
|
||||
strong <- htmltools::strong
|
||||
|
||||
#' @export
|
||||
tag <- htmltools::tag
|
||||
|
||||
#' @export
|
||||
tagAppendAttributes <- htmltools::tagAppendAttributes
|
||||
|
||||
#' @export
|
||||
tagAppendChild <- htmltools::tagAppendChild
|
||||
|
||||
#' @export
|
||||
tagAppendChildren <- htmltools::tagAppendChildren
|
||||
|
||||
#' @export
|
||||
tagList <- htmltools::tagList
|
||||
|
||||
#' @export
|
||||
tags <- htmltools::tags
|
||||
|
||||
#' @export
|
||||
tagSetChildren <- htmltools::tagSetChildren
|
||||
|
||||
#' @export
|
||||
validateCssUnit <- htmltools::validateCssUnit
|
||||
|
||||
#' @export
|
||||
withTags <- htmltools::withTags
|
||||
52
R/imageutils.R
Normal file
52
R/imageutils.R
Normal file
@@ -0,0 +1,52 @@
|
||||
#' 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
|
||||
#' 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
|
||||
#' 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)}.
|
||||
#'
|
||||
#' @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}.
|
||||
#' @param width Width in pixels.
|
||||
#' @param height Height in pixels.
|
||||
#' @param res Resolution in pixels per inch. This value is passed to
|
||||
#' \code{\link{png}}. Note that this affects the resolution of PNG rendering in
|
||||
#' R; it won't change the actual ppi of the browser.
|
||||
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#'
|
||||
#' @export
|
||||
plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
||||
width=400, height=400, res=72, ...) {
|
||||
# If quartz is available, use png() (which will default to quartz).
|
||||
# Otherwise, if the Cairo package is installed, use CairoPNG().
|
||||
# Finally, if neither quartz nor Cairo, use png().
|
||||
if (capabilities("aqua")) {
|
||||
pngfun <- png
|
||||
} else if (getOption('shiny.usecairo', TRUE) &&
|
||||
nchar(system.file(package = "Cairo"))) {
|
||||
pngfun <- Cairo::CairoPNG
|
||||
} else {
|
||||
pngfun <- png
|
||||
}
|
||||
|
||||
pngfun(filename=filename, width=width, height=height, res=res, ...)
|
||||
# Call plot.new() so that even if no plotting operations are performed
|
||||
# at least we have a blank background
|
||||
plot.new()
|
||||
dv <- dev.cur()
|
||||
tryCatch(shinyCallingHandlers(func()), finally = dev.off(dv))
|
||||
|
||||
filename
|
||||
}
|
||||
104
R/jqueryui.R
Normal file
104
R/jqueryui.R
Normal file
@@ -0,0 +1,104 @@
|
||||
#' Panel with absolute positioning
|
||||
#'
|
||||
#' 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
|
||||
#' 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
|
||||
#' 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
|
||||
#' inside of certain types of panels.
|
||||
#'
|
||||
#' The \code{fixedPanel} function is the same as \code{absolutePanel} with
|
||||
#' \code{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
|
||||
#' 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\%"}.
|
||||
#'
|
||||
#' 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\%"}.
|
||||
#'
|
||||
#' @param ... Attributes (named arguments) or children (unnamed arguments) that
|
||||
#' should be included in the panel.
|
||||
#'
|
||||
#' @param top Distance between the top of the panel, and the top of the page or
|
||||
#' parent container.
|
||||
#' @param left Distance between the left side of the panel, and the left of the
|
||||
#' page or parent container.
|
||||
#' @param right Distance between the right side of the panel, and the right of
|
||||
#' the page or parent container.
|
||||
#' @param bottom Distance between the bottom of the panel, and the bottom of the
|
||||
#' 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
|
||||
#' 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
|
||||
#' 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")}.
|
||||
#' @return An HTML element or list of elements.
|
||||
#'
|
||||
#' @export
|
||||
absolutePanel <- function(...,
|
||||
top = NULL, left = NULL, right = NULL, bottom = NULL,
|
||||
width = NULL, height = NULL,
|
||||
draggable = FALSE, fixed = FALSE,
|
||||
cursor = c('auto', 'move', 'default', 'inherit')) {
|
||||
cssProps <- list(
|
||||
top = top,
|
||||
left = left,
|
||||
right = right,
|
||||
bottom = bottom,
|
||||
width = width,
|
||||
height = height
|
||||
)
|
||||
cssProps <- cssProps[!sapply(cssProps, is.null)]
|
||||
cssProps <- sapply(cssProps, validateCssUnit)
|
||||
cssProps[['position']] <- ifelse(fixed, 'fixed', 'absolute')
|
||||
cssProps[['cursor']] <- match.arg(cursor)
|
||||
if (identical(cssProps[['cursor']], 'auto'))
|
||||
cssProps[['cursor']] <- ifelse(draggable, 'move', 'inherit')
|
||||
|
||||
style <- paste(paste(names(cssProps), cssProps, sep = ':', collapse = ';'), ';', sep='')
|
||||
divTag <- tags$div(style=style, ...)
|
||||
if (isTRUE(draggable)) {
|
||||
divTag <- tagAppendAttributes(divTag, class='draggable')
|
||||
return(tagList(
|
||||
# IMPORTANT NOTE: If you update jqueryui, make sure you DON'T include the datepicker,
|
||||
# as it collides with our bootstrap datepicker!
|
||||
singleton(tags$head(tags$script(src='shared/jqueryui/1.10.4/jquery-ui.min.js'))),
|
||||
divTag,
|
||||
tags$script('$(".draggable").draggable();')
|
||||
))
|
||||
} else {
|
||||
return(divTag)
|
||||
}
|
||||
}
|
||||
|
||||
#' @rdname absolutePanel
|
||||
#' @export
|
||||
fixedPanel <- function(...,
|
||||
top = NULL, left = NULL, right = NULL, bottom = NULL,
|
||||
width = NULL, height = NULL,
|
||||
draggable = FALSE,
|
||||
cursor = c('move', 'default', 'inherit')) {
|
||||
absolutePanel(..., top=top, left=left, right=right, bottom=bottom,
|
||||
width=width, height=height, draggable=draggable, cursor=cursor,
|
||||
fixed=TRUE)
|
||||
}
|
||||
35
R/map.R
35
R/map.R
@@ -20,30 +20,32 @@ Map <- setRefClass(
|
||||
},
|
||||
get = function(key) {
|
||||
if (.self$containsKey(key))
|
||||
return(base::get(key, pos=.env, inherits=F))
|
||||
else
|
||||
return(NULL)
|
||||
base::get(key, pos=.env, inherits=FALSE)
|
||||
},
|
||||
set = function(key, value) {
|
||||
assign(key, value, pos=.env, inherits=F)
|
||||
return(value)
|
||||
assign(key, value, pos=.env, inherits=FALSE)
|
||||
value
|
||||
},
|
||||
mset = function(...) {
|
||||
args <- list(...)
|
||||
for (key in names(args))
|
||||
set(key, args[[key]])
|
||||
},
|
||||
remove = function(key) {
|
||||
if (.self$containsKey(key)) {
|
||||
result <- .self$get(key)
|
||||
rm(list = key, pos=.env, inherits=F)
|
||||
return(result)
|
||||
rm(list = key, pos=.env, inherits=FALSE)
|
||||
result
|
||||
}
|
||||
return(NULL)
|
||||
},
|
||||
containsKey = function(key) {
|
||||
exists(key, where=.env, inherits=F)
|
||||
exists(key, where=.env, inherits=FALSE)
|
||||
},
|
||||
keys = function() {
|
||||
ls(envir=.env, all.names=T)
|
||||
ls(envir=.env, all.names=TRUE)
|
||||
},
|
||||
values = function() {
|
||||
mget(.self$keys(), envir=.env, inherits=F)
|
||||
mget(.self$keys(), envir=.env, inherits=FALSE)
|
||||
},
|
||||
clear = function() {
|
||||
.env <<- new.env(parent=emptyenv())
|
||||
@@ -55,19 +57,10 @@ Map <- setRefClass(
|
||||
)
|
||||
)
|
||||
|
||||
`[.Map` <- function(map, name) {
|
||||
map$get(name)
|
||||
}
|
||||
|
||||
`[<-.Map` <- function(map, name, value) {
|
||||
map$set(name, value)
|
||||
return(map)
|
||||
}
|
||||
|
||||
as.list.Map <- function(map) {
|
||||
sapply(map$keys(),
|
||||
map$get,
|
||||
simplify=F)
|
||||
simplify=FALSE)
|
||||
}
|
||||
length.Map <- function(map) {
|
||||
map$size()
|
||||
|
||||
71
R/middleware-shiny.R
Normal file
71
R/middleware-shiny.R
Normal file
@@ -0,0 +1,71 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
reactLogHandler <- function(req) {
|
||||
if (!identical(req$PATH_INFO, '/reactlog'))
|
||||
return(NULL)
|
||||
|
||||
if (!getOption('shiny.reactlog', FALSE)) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
return(httpResponse(
|
||||
status=200,
|
||||
content=list(file=renderReactLog(), owned=TRUE)
|
||||
))
|
||||
}
|
||||
|
||||
sessionHandler <- function(req) {
|
||||
path <- req$PATH_INFO
|
||||
if (is.null(path))
|
||||
return(NULL)
|
||||
|
||||
matches <- regmatches(path, regexec('^(/session/([0-9a-f]+))(/.*)$', path))
|
||||
if (length(matches[[1]]) == 0)
|
||||
return(NULL)
|
||||
|
||||
session <- matches[[1]][3]
|
||||
subpath <- matches[[1]][4]
|
||||
|
||||
shinysession <- appsByToken$get(session)
|
||||
if (is.null(shinysession))
|
||||
return(NULL)
|
||||
|
||||
subreq <- as.environment(as.list(req, all.names=TRUE))
|
||||
subreq$PATH_INFO <- subpath
|
||||
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, matches[[1]][2], sep='')
|
||||
|
||||
return(shinysession$handleRequest(subreq))
|
||||
}
|
||||
|
||||
dynamicHandler <- function(filePath, dependencyFiles=filePath) {
|
||||
lastKnownTimestamps <- NA
|
||||
metaHandler <- function(req) NULL
|
||||
|
||||
if (!file.exists(filePath))
|
||||
return(metaHandler)
|
||||
|
||||
cacheContext <- CacheContext$new()
|
||||
|
||||
return (function(req) {
|
||||
# Check if we need to rebuild
|
||||
if (cacheContext$isDirty()) {
|
||||
cacheContext$reset()
|
||||
for (dep in dependencyFiles)
|
||||
cacheContext$addDependencyFile(dep)
|
||||
|
||||
clearClients()
|
||||
if (file.exists(filePath)) {
|
||||
local({
|
||||
cacheContext$with(function() {
|
||||
sys.source(filePath, envir=new.env(parent=globalenv()), keep.source=TRUE)
|
||||
})
|
||||
})
|
||||
}
|
||||
metaHandler <<- joinHandlers(.globals$clients)
|
||||
clearClients()
|
||||
}
|
||||
|
||||
return(metaHandler(req))
|
||||
})
|
||||
}
|
||||
354
R/middleware.R
Normal file
354
R/middleware.R
Normal file
@@ -0,0 +1,354 @@
|
||||
# This file contains a general toolkit for routing and combining bits of
|
||||
# HTTP-handling logic. It is similar in spirit to Rook (and Rack, and WSGI, and
|
||||
# Connect, and...) but adds cascading and routing.
|
||||
#
|
||||
# This file is called "middleware" because that's the term used for these bits
|
||||
# of logic in these other frameworks. However, our code uses the word "handler"
|
||||
# so we'll stick to that for the rest of this document; just know that they're
|
||||
# basically the same concept.
|
||||
#
|
||||
# ## Intro to handlers
|
||||
#
|
||||
# A **handler** (or sometimes, **httpHandler**) is a function that takes a
|
||||
# `req` parameter--a request object as described in the Rook specification--and
|
||||
# returns `NULL`, or an `httpResponse`.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
httpResponse <- function(status = 200,
|
||||
content_type = "text/html; charset=UTF-8",
|
||||
content = "",
|
||||
headers = list()) {
|
||||
# Make sure it's a list, not a vector
|
||||
headers <- as.list(headers)
|
||||
if (is.null(headers$`X-UA-Compatible`))
|
||||
headers$`X-UA-Compatible` <- "chrome=1"
|
||||
resp <- list(status = status, content_type = content_type, content = content,
|
||||
headers = headers)
|
||||
class(resp) <- 'httpResponse'
|
||||
return(resp)
|
||||
}
|
||||
|
||||
#
|
||||
# You can think of a web application as being simply an aggregation of these
|
||||
# functions, each of which performs one kind of duty. Each handler in turn gets
|
||||
# a look at the request and can decide whether it knows how to handle it. If
|
||||
# so, it returns an `httpResponse` and processing terminates; if not, it
|
||||
# returns `NULL` and the next handler gets to execute. If the final handler
|
||||
# returns `NULL`, a 404 response should be returned.
|
||||
#
|
||||
# We have a similar construct for websockets: **websocket handlers** or
|
||||
# **wsHandlers**. These take a single `ws` argument which is the websocket
|
||||
# connection that was just opened, and they can either return `TRUE` if they
|
||||
# are handling the connection, and `NULL` to pass responsibility on to the next
|
||||
# wsHandler.
|
||||
#
|
||||
# ### Combining handlers
|
||||
#
|
||||
# Since it's so common for httpHandlers to be invoked in this "cascading"
|
||||
# fashion, we'll introduce a function that takes zero or more handlers and
|
||||
# returns a single handler. And while we're at it, making a directory of static
|
||||
# content available is such a common thing to do, we'll allow strings
|
||||
# representing paths to be used instead of handlers; any such strings we
|
||||
# encounter will be converted into `staticHandler` objects.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
joinHandlers <- function(handlers) {
|
||||
# Zero handlers; return a null handler
|
||||
if (length(handlers) == 0)
|
||||
return(function(req) NULL)
|
||||
|
||||
# Just one handler (function)? Return it.
|
||||
if (is.function(handlers))
|
||||
return(handlers)
|
||||
|
||||
handlers <- lapply(handlers, function(h) {
|
||||
if (is.character(h))
|
||||
return(staticHandler(h))
|
||||
else
|
||||
return(h)
|
||||
})
|
||||
|
||||
# Filter out NULL
|
||||
handlers <- handlers[!sapply(handlers, is.null)]
|
||||
|
||||
if (length(handlers) == 0)
|
||||
return(function(req) NULL)
|
||||
if (length(handlers) == 1)
|
||||
return(handlers[[1]])
|
||||
|
||||
function(req) {
|
||||
for (handler in handlers) {
|
||||
response <- handler(req)
|
||||
if (!is.null(response))
|
||||
return(response)
|
||||
}
|
||||
return(NULL)
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Note that we don't have an equivalent of `joinHandlers` for wsHandlers. It's
|
||||
# easy to imagine it, we just haven't needed one.
|
||||
#
|
||||
# ### Handler routing
|
||||
#
|
||||
# Handlers do not have a built-in notion of routing. Conceptually, given a list
|
||||
# of handlers, all the handlers are peers and they all get to see every request
|
||||
# (well, up until the point that a handler returns a response).
|
||||
#
|
||||
# You could implement routing in each handler by checking the request's
|
||||
# `PATH_INFO` field, but since it's such a common need, let's make it simple by
|
||||
# introducing a `routeHandler` function. This is a handler
|
||||
# [decorator](http://en.wikipedia.org/wiki/Decorator_pattern) and it's
|
||||
# responsible for 1) filtering out requests that don't match the given route,
|
||||
# and 2) temporarily modifying the request object to take the matched part of
|
||||
# the route off of the `PATH_INFO` (and add it to the end of `SCRIPT_NAME`).
|
||||
# This way, the handler doesn't need to figure out about what part of its URL
|
||||
# path has already been matched via routing.
|
||||
#
|
||||
# (BTW, it's safe for `routeHandler` calls to nest.)
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
routeHandler <- function(prefix, handler) {
|
||||
force(prefix)
|
||||
force(handler)
|
||||
|
||||
if (identical("", prefix))
|
||||
return(handler)
|
||||
|
||||
if (length(prefix) != 1 || !isTRUE(grepl("^/[^\\]+$", prefix))) {
|
||||
stop("Invalid URL prefix \"", prefix, "\"")
|
||||
}
|
||||
|
||||
pathPattern <- paste("^\\Q", prefix, "\\E/", sep = "")
|
||||
function(req) {
|
||||
if (isTRUE(grepl(pathPattern, req$PATH_INFO))) {
|
||||
origScript <- req$SCRIPT_NAME
|
||||
origPath <- req$PATH_INFO
|
||||
on.exit({
|
||||
req$SCRIPT_NAME <- origScript
|
||||
req$PATH_INFO <- origPath
|
||||
}, add = TRUE)
|
||||
pathInfo <- substr(req$PATH_INFO, nchar(prefix)+1, nchar(req$PATH_INFO))
|
||||
req$SCRIPT_NAME <- paste(req$SCRIPT_NAME, prefix, sep = "")
|
||||
req$PATH_INFO <- pathInfo
|
||||
return(handler(req))
|
||||
} else {
|
||||
return(NULL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# We have a version for websocket handlers as well. Pity about the copy/paste
|
||||
# job.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
routeWSHandler <- function(prefix, wshandler) {
|
||||
force(prefix)
|
||||
force(wshandler)
|
||||
|
||||
if (identical("", prefix))
|
||||
return(wshandler)
|
||||
|
||||
if (length(prefix) != 1 || !isTRUE(grepl("^/[^\\]+$", prefix))) {
|
||||
stop("Invalid URL prefix \"", prefix, "\"")
|
||||
}
|
||||
|
||||
pathPattern <- paste("^\\Q", prefix, "\\E/", sep = "")
|
||||
function(ws) {
|
||||
req <- ws$request
|
||||
if (isTRUE(grepl(pathPattern, req$PATH_INFO))) {
|
||||
origScript <- req$SCRIPT_NAME
|
||||
origPath <- req$PATH_INFO
|
||||
on.exit({
|
||||
req$SCRIPT_NAME <- origScript
|
||||
req$PATH_INFO <- origPath
|
||||
}, add = TRUE)
|
||||
pathInfo <- substr(req$PATH_INFO, nchar(prefix)+1, nchar(req$PATH_INFO))
|
||||
req$SCRIPT_NAME <- paste(req$SCRIPT_NAME, prefix, sep = "")
|
||||
req$PATH_INFO <- pathInfo
|
||||
return(wshandler(ws))
|
||||
} else {
|
||||
return(NULL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# ### Handler implementations
|
||||
#
|
||||
# Now let's actually write some handlers. Note that these functions aren't
|
||||
# *themselves* handlers, you call them and they *return* a handler. Handler
|
||||
# factory functions, if you will.
|
||||
#
|
||||
# Here's one that serves up static assets from a directory.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
staticHandler <- function(root) {
|
||||
force(root)
|
||||
return(function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
path <- req$PATH_INFO
|
||||
|
||||
if (is.null(path))
|
||||
return(httpResponse(400, content="<h1>Bad Request</h1>"))
|
||||
|
||||
if (path == '/')
|
||||
path <- '/index.html'
|
||||
|
||||
abs.path <- resolve(root, path)
|
||||
if (is.null(abs.path))
|
||||
return(NULL)
|
||||
|
||||
ext <- tools::file_ext(abs.path)
|
||||
content.type <- getContentType(ext)
|
||||
response.content <- readBin(abs.path, 'raw', n=file.info(abs.path)$size)
|
||||
return(httpResponse(200, content.type, response.content))
|
||||
})
|
||||
}
|
||||
|
||||
#
|
||||
# ## Handler manager
|
||||
#
|
||||
# The handler manager gives you a place to register handlers (of both http and
|
||||
# websocket varieties) and provides an httpuv-compatible set of callbacks for
|
||||
# invoking them.
|
||||
#
|
||||
# Create one of these, make zero or more calls to `addHandler` and
|
||||
# `addWSHandler` methods (order matters--first one wins!), and then pass the
|
||||
# return value of `createHttpuvApp` to httpuv's `startServer` function.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
HandlerList <- setRefClass("HandlerList",
|
||||
fields = list(
|
||||
handlers = "list"
|
||||
),
|
||||
methods = list(
|
||||
add = function(handler, key, tail = FALSE) {
|
||||
if (!is.null(handlers[[key]]))
|
||||
stop("Key ", key, " already in use")
|
||||
newList <- structure(names=key, list(handler))
|
||||
|
||||
if (length(handlers) == 0)
|
||||
handlers <<- newList
|
||||
else if (tail)
|
||||
handlers <<- c(handlers, newList)
|
||||
else
|
||||
handlers <<- c(newList, handlers)
|
||||
},
|
||||
remove = function(key) {
|
||||
handlers[key] <<- NULL
|
||||
},
|
||||
clear = function() {
|
||||
handlers <<- list()
|
||||
},
|
||||
invoke = function(...) {
|
||||
for (handler in handlers) {
|
||||
result <- handler(...)
|
||||
if (!is.null(result))
|
||||
return(result)
|
||||
}
|
||||
return(NULL)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
HandlerManager <- setRefClass("HandlerManager",
|
||||
fields = list(
|
||||
handlers = "HandlerList",
|
||||
wsHandlers = "HandlerList"
|
||||
),
|
||||
methods = list(
|
||||
addHandler = function(handler, key, tail = FALSE) {
|
||||
handlers$add(handler, key, tail)
|
||||
},
|
||||
removeHandler = function(key) {
|
||||
handlers$remove(key)
|
||||
},
|
||||
addWSHandler = function(wsHandler, key, tail = FALSE) {
|
||||
wsHandlers$add(wsHandler, key, tail)
|
||||
},
|
||||
removeWSHandler = function(key) {
|
||||
wsHandlers$remove(key)
|
||||
},
|
||||
clear = function() {
|
||||
handlers$clear()
|
||||
wsHandlers$clear()
|
||||
},
|
||||
createHttpuvApp = function() {
|
||||
list(
|
||||
onHeaders = function(req) {
|
||||
maxSize <- getOption('shiny.maxRequestSize', 5 * 1024 * 1024)
|
||||
if (maxSize <= 0)
|
||||
return(NULL)
|
||||
|
||||
reqSize <- 0
|
||||
if (length(req$CONTENT_LENGTH) > 0)
|
||||
reqSize <- as.numeric(req$CONTENT_LENGTH)
|
||||
else if (length(req$HTTP_TRANSFER_ENCODING) > 0)
|
||||
reqSize <- Inf
|
||||
|
||||
if (reqSize > maxSize) {
|
||||
return(list(status = 413L,
|
||||
headers = list(
|
||||
'Content-Type' = 'text/plain'
|
||||
),
|
||||
body = 'Maximum upload size exceeded'))
|
||||
}
|
||||
else {
|
||||
return(NULL)
|
||||
}
|
||||
},
|
||||
call = .httpServer(
|
||||
function (req) {
|
||||
return(handlers$invoke(req))
|
||||
},
|
||||
getOption('shiny.sharedSecret', NULL)
|
||||
),
|
||||
onWSOpen = function(ws) {
|
||||
return(wsHandlers$invoke(ws))
|
||||
}
|
||||
)
|
||||
},
|
||||
.httpServer = function(handler, sharedSecret) {
|
||||
filter <- getOption('shiny.http.response.filter', NULL)
|
||||
if (is.null(filter))
|
||||
filter <- function(req, response) response
|
||||
|
||||
function(req) {
|
||||
if (!is.null(sharedSecret)
|
||||
&& !identical(sharedSecret, 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')))
|
||||
}
|
||||
|
||||
response <- handler(req)
|
||||
if (is.null(response))
|
||||
response <- httpResponse(404, content="<h1>Not Found</h1>")
|
||||
|
||||
if (inherits(response, "httpResponse")) {
|
||||
headers <- as.list(response$headers)
|
||||
headers$'Content-Type' <- response$content_type
|
||||
|
||||
response <- filter(req, response)
|
||||
return(list(status=response$status,
|
||||
body=response$content,
|
||||
headers=headers))
|
||||
} else {
|
||||
# Assume it's a Rook-compatible response
|
||||
return(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#
|
||||
# ## Next steps
|
||||
#
|
||||
# See server.R and middleware-shiny.R to see actual implementation and usage of
|
||||
# handlers in the context of Shiny.
|
||||
110
R/priorityqueue.R
Normal file
110
R/priorityqueue.R
Normal file
@@ -0,0 +1,110 @@
|
||||
# "...like a regular queue or stack data structure, but where additionally each
|
||||
# element has a "priority" associated with it. In a priority queue, an element
|
||||
# with high priority is served before an element with low priority. If two
|
||||
# elements have the same priority, they are served according to their order in
|
||||
# the queue." (http://en.wikipedia.org/wiki/Priority_queue)
|
||||
|
||||
PriorityQueue <- setRefClass(
|
||||
'PriorityQueue',
|
||||
fields = list(
|
||||
# Keys are priorities, values are subqueues (implemented as list)
|
||||
.itemsByPriority = 'Map',
|
||||
# Sorted vector (largest first)
|
||||
.priorities = 'numeric'
|
||||
),
|
||||
methods = list(
|
||||
# Enqueue an item, with the given priority level (must be integer). Higher
|
||||
# priority numbers are dequeued earlier than lower.
|
||||
enqueue = function(item, priority) {
|
||||
priority <- normalizePriority(priority)
|
||||
|
||||
if (!(priority %in% .priorities)) {
|
||||
.priorities <<- c(.priorities, priority)
|
||||
.priorities <<- sort(.priorities, decreasing=TRUE)
|
||||
.itemsByPriority$set(.key(priority), list(item))
|
||||
} else {
|
||||
.itemsByPriority$set(
|
||||
.key(priority),
|
||||
c(.itemsByPriority$get(.key(priority)), item)
|
||||
)
|
||||
}
|
||||
return(invisible())
|
||||
},
|
||||
# Retrieve a single item by 1) priority number (highest first) and then 2)
|
||||
# insertion order (first in, first out). If there are no items to be
|
||||
# dequeued, then NULL is returned. If it is necessary to distinguish between
|
||||
# a NULL value and the empty case, call isEmpty() before dequeue().
|
||||
dequeue = function() {
|
||||
if (length(.priorities) == 0)
|
||||
return(NULL)
|
||||
|
||||
maxPriority <- .priorities[[1]]
|
||||
items <- .itemsByPriority$get(.key(maxPriority))
|
||||
firstItem <- items[[1]]
|
||||
if (length(items) == 1) {
|
||||
# This is the last item at this priority. Remove both the list and the
|
||||
# priority level.
|
||||
.itemsByPriority$remove(.key(maxPriority))
|
||||
.priorities <<- .priorities[-1]
|
||||
} else {
|
||||
# There are still items at this priority. Remove the current item from
|
||||
# the list, and save it.
|
||||
items <- items[-1]
|
||||
.itemsByPriority$set(.key(maxPriority), items)
|
||||
}
|
||||
return(firstItem)
|
||||
},
|
||||
# Returns TRUE if no items are in the queue, otherwise FALSE.
|
||||
isEmpty = function() {
|
||||
length(.priorities) == 0
|
||||
},
|
||||
# Translates a priority integer to a character that is suitable for using as
|
||||
# a key.
|
||||
.key = function(priority) {
|
||||
sprintf('%a', priority)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
normalizePriority <- function(priority) {
|
||||
|
||||
if (is.null(priority))
|
||||
priority <- 0
|
||||
|
||||
# Cast integers to numeric to prevent any inconsistencies
|
||||
if (is.integer(priority))
|
||||
priority <- as.numeric(priority)
|
||||
|
||||
if (!is.numeric(priority))
|
||||
stop('priority must be an integer or numeric')
|
||||
|
||||
# Check length
|
||||
if (length(priority) == 0) {
|
||||
warning('Zero-length priority vector was passed; using 0')
|
||||
priority <- 0
|
||||
} else if (length(priority) > 1) {
|
||||
warning('Priority has length > 1 and only the first element will be used')
|
||||
priority <- priority[1]
|
||||
}
|
||||
|
||||
# NA == 0
|
||||
if (is.na(priority))
|
||||
priority <- 0
|
||||
|
||||
return(priority)
|
||||
}
|
||||
|
||||
# pq <- PriorityQueue$new()
|
||||
# pq$enqueue('a', 1)
|
||||
# pq$enqueue('b', 1L)
|
||||
# pq$enqueue('c', 1)
|
||||
# pq$enqueue('A', 2)
|
||||
# pq$enqueue('B', 2L)
|
||||
# pq$enqueue('C', 2)
|
||||
# pq$enqueue('d', 1)
|
||||
# pq$enqueue('e', 1L)
|
||||
# pq$enqueue('f', 1)
|
||||
# pq$enqueue('D', 2)
|
||||
# pq$enqueue('E', 2L)
|
||||
# pq$enqueue('F', 2)
|
||||
# # Expect ABCDEFabcdef
|
||||
135
R/react.R
135
R/react.R
@@ -2,38 +2,45 @@ Context <- setRefClass(
|
||||
'Context',
|
||||
fields = list(
|
||||
id = 'character',
|
||||
.label = 'character', # For debug purposes
|
||||
.invalidated = 'logical',
|
||||
.callbacks = 'list',
|
||||
.hintCallbacks = 'list'
|
||||
.invalidateCallbacks = 'list',
|
||||
.flushCallbacks = 'list',
|
||||
.domain = 'ANY'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
initialize = function(domain, label='', type='other', prevId='') {
|
||||
id <<- .getReactiveEnvironment()$nextId()
|
||||
.invalidated <<- F
|
||||
.callbacks <<- list()
|
||||
.hintCallbacks <<- list()
|
||||
.invalidated <<- FALSE
|
||||
.invalidateCallbacks <<- list()
|
||||
.flushCallbacks <<- list()
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.graphCreateContext(id, label, type, prevId, domain)
|
||||
},
|
||||
run = function(func) {
|
||||
"Run the provided function under this context."
|
||||
env <- .getReactiveEnvironment()
|
||||
env$runWith(.self, func)
|
||||
},
|
||||
invalidateHint = function() {
|
||||
"Let this context know it may or may not be invalidated very soon; that
|
||||
is, something in its dependency graph has been invalidated but there's no
|
||||
guarantee that the cascade of invalidations will reach all the way here.
|
||||
This is used to show progress in the UI."
|
||||
lapply(.hintCallbacks, function(func) {
|
||||
func()
|
||||
withReactiveDomain(.domain, {
|
||||
env <- .getReactiveEnvironment()
|
||||
.graphEnterContext(id)
|
||||
tryCatch(
|
||||
env$runWith(.self, func),
|
||||
finally = .graphExitContext(id)
|
||||
)
|
||||
})
|
||||
},
|
||||
invalidate = function() {
|
||||
"Schedule this context for invalidation. It will not actually be
|
||||
invalidated until the next call to \\code{\\link{flushReact}}."
|
||||
"Invalidate this context. It will immediately call the callbacks
|
||||
that have been registered with onInvalidate()."
|
||||
if (.invalidated)
|
||||
return()
|
||||
.invalidated <<- T
|
||||
.getReactiveEnvironment()$addPendingInvalidate(.self)
|
||||
.invalidated <<- TRUE
|
||||
|
||||
.graphInvalidate(id, .domain)
|
||||
lapply(.invalidateCallbacks, function(func) {
|
||||
func()
|
||||
})
|
||||
.invalidateCallbacks <<- list()
|
||||
NULL
|
||||
},
|
||||
onInvalidate = function(func) {
|
||||
@@ -43,23 +50,27 @@ Context <- setRefClass(
|
||||
if (.invalidated)
|
||||
func()
|
||||
else
|
||||
.callbacks <<- c(.callbacks, func)
|
||||
.invalidateCallbacks <<- c(.invalidateCallbacks, func)
|
||||
NULL
|
||||
},
|
||||
onInvalidateHint = function(func) {
|
||||
.hintCallbacks <<- c(.hintCallbacks, func)
|
||||
addPendingFlush = function(priority) {
|
||||
"Tell the reactive environment that this context should be flushed the
|
||||
next time flushReact() called."
|
||||
.getReactiveEnvironment()$addPendingFlush(.self, priority)
|
||||
},
|
||||
executeCallbacks = function() {
|
||||
onFlush = function(func) {
|
||||
"Register a function to be called when this context is flushed."
|
||||
.flushCallbacks <<- c(.flushCallbacks, func)
|
||||
},
|
||||
executeFlushCallbacks = function() {
|
||||
"For internal use only."
|
||||
lapply(.callbacks, function(func) {
|
||||
tryCatch({
|
||||
lapply(.flushCallbacks, function(func) {
|
||||
withCallingHandlers({
|
||||
func()
|
||||
}, warning = function(e) {
|
||||
# TODO: Callbacks in app
|
||||
print(e)
|
||||
}, error = function(e) {
|
||||
# TODO: Callbacks in app
|
||||
print(e)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -68,52 +79,66 @@ Context <- setRefClass(
|
||||
|
||||
ReactiveEnvironment <- setRefClass(
|
||||
'ReactiveEnvironment',
|
||||
fields = c('.currentContext', '.nextId', '.pendingInvalidate'),
|
||||
fields = list(
|
||||
.currentContext = 'ANY',
|
||||
.nextId = 'integer',
|
||||
.pendingFlush = 'PriorityQueue',
|
||||
.inFlush = 'logical'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
.currentContext <<- NULL
|
||||
.nextId <<- 0L
|
||||
.pendingInvalidate <<- list()
|
||||
.pendingFlush <<- PriorityQueue$new()
|
||||
.inFlush <<- FALSE
|
||||
},
|
||||
nextId = function() {
|
||||
.nextId <<- .nextId + 1L
|
||||
return(as.character(.nextId))
|
||||
},
|
||||
currentContext = function() {
|
||||
if (is.null(.currentContext))
|
||||
stop('Operation not allowed without an active reactive context. ',
|
||||
'(You tried to do something that can only be done from inside a ',
|
||||
'reactive function.)')
|
||||
if (is.null(.currentContext)) {
|
||||
if (isTRUE(getOption('shiny.suppressMissingContextError', FALSE))) {
|
||||
return(getDummyContext())
|
||||
} else {
|
||||
stop('Operation not allowed without an active reactive context. ',
|
||||
'(You tried to do something that can only be done from inside a ',
|
||||
'reactive expression or observer.)')
|
||||
}
|
||||
}
|
||||
return(.currentContext)
|
||||
},
|
||||
runWith = function(ctx, func) {
|
||||
old.ctx <- .currentContext
|
||||
.currentContext <<- ctx
|
||||
on.exit(.currentContext <<- old.ctx)
|
||||
func()
|
||||
shinyCallingHandlers(func())
|
||||
},
|
||||
addPendingInvalidate = function(ctx) {
|
||||
.pendingInvalidate <<- c(.pendingInvalidate, ctx)
|
||||
addPendingFlush = function(ctx, priority) {
|
||||
.pendingFlush$enqueue(ctx, priority)
|
||||
},
|
||||
flush = function() {
|
||||
while (length(.pendingInvalidate) > 0) {
|
||||
contexts <- .pendingInvalidate
|
||||
.pendingInvalidate <<- list()
|
||||
lapply(contexts, function(ctx) {
|
||||
ctx$executeCallbacks()
|
||||
NULL
|
||||
})
|
||||
# If already in a flush, don't start another one
|
||||
if (.inFlush) return()
|
||||
.inFlush <<- TRUE
|
||||
on.exit(.inFlush <<- FALSE)
|
||||
|
||||
while (!.pendingFlush$isEmpty()) {
|
||||
ctx <- .pendingFlush$dequeue()
|
||||
ctx$executeFlushCallbacks()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
.getReactiveEnvironment <- function() {
|
||||
if (!exists('.ReactiveEnvironment', envir=.GlobalEnv, inherits=F)) {
|
||||
assign('.ReactiveEnvironment', ReactiveEnvironment$new(), envir=.GlobalEnv)
|
||||
.getReactiveEnvironment <- local({
|
||||
reactiveEnvironment <- NULL
|
||||
function() {
|
||||
if (is.null(reactiveEnvironment))
|
||||
reactiveEnvironment <<- ReactiveEnvironment$new()
|
||||
return(reactiveEnvironment)
|
||||
}
|
||||
get('.ReactiveEnvironment', envir=.GlobalEnv, inherits=F)
|
||||
}
|
||||
})
|
||||
|
||||
# Causes any pending invalidations to run.
|
||||
flushReact <- function() {
|
||||
@@ -125,3 +150,15 @@ flushReact <- function() {
|
||||
getCurrentContext <- function() {
|
||||
.getReactiveEnvironment()$currentContext()
|
||||
}
|
||||
|
||||
getDummyContext <- function() {}
|
||||
local({
|
||||
dummyContext <- NULL
|
||||
getDummyContext <<- function() {
|
||||
if (is.null(dummyContext)) {
|
||||
dummyContext <<- Context$new(getDefaultReactiveDomain(), '[none]',
|
||||
type='isolate')
|
||||
}
|
||||
return(dummyContext)
|
||||
}
|
||||
})
|
||||
|
||||
252
R/reactive-domains.R
Normal file
252
R/reactive-domains.R
Normal file
@@ -0,0 +1,252 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
#
|
||||
# Over the last few months we've seen a number of cases where it'd be helpful
|
||||
# for objects that are instantiated within a Shiny app to know what Shiny
|
||||
# session they are "owned" by. I put "owned" in quotes because there isn't a
|
||||
# built-in notion of object ownership in Shiny today, any more than there is a
|
||||
# notion of one object owning another in R.
|
||||
#
|
||||
# But it's intuitive to everyone, I think, that the outputs for a session are
|
||||
# owned by that session, and any logic that is executed as part of the output
|
||||
# is done on behalf of that session. And it seems like in the vast majority of
|
||||
# cases, observers that are created inside a shinyServer function (i.e. one per
|
||||
# session) are also intuitively owned by the session that's starting up.
|
||||
#
|
||||
# This notion of ownership is important/helpful for a few scenarios that have
|
||||
# come up in recent months:
|
||||
#
|
||||
# 1. The showcase mode that Jonathan implemented recently highlights
|
||||
# observers/reactives as they execute. In order for sessions to only receive
|
||||
# highlights for their own code execution, we need to know which sessions own
|
||||
# which observers. 2. We've seen a number of apps crash out when observers
|
||||
# outlive their sessions and then try to do things with their sessions (the
|
||||
# most common error message was something like "Can't write to a closed
|
||||
# websocket", but we now silently ignore writes to closed websockets). It'd be
|
||||
# convenient for the default behavior of observers to be that they don't
|
||||
# outlive their parent sessions. 3. The reactive log visualizer currently
|
||||
# visualizes all reactivity in the process; it would be great if by default it
|
||||
# only visualized the current session. 4. When an observer has an error, it
|
||||
# would be great to be able to send the error to the session so it can do its
|
||||
# own handling (such as sending the error info to the client so the user can be
|
||||
# notified). 5. Shiny Server Pro wants to show the admin how much time is being
|
||||
# spent servicing each session.
|
||||
#
|
||||
# So what are the rules for establishing ownership?
|
||||
#
|
||||
# 1. Define the "current domain" as a global variable whose value will own any
|
||||
# newly created observer (by default). A domain is a reference class or
|
||||
# environment that contains the functions `onEnded(callback)`, `isEnded()`, and
|
||||
# `reactlog(logEntry)`.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
createMockDomain <- function() {
|
||||
callbacks <- list()
|
||||
ended <- FALSE
|
||||
domain <- new.env(parent = emptyenv())
|
||||
domain$onEnded <- function(callback) {
|
||||
callbacks <<- c(callbacks, callback)
|
||||
}
|
||||
domain$isEnded <- function() {
|
||||
ended
|
||||
}
|
||||
domain$reactlog <- function(logEntry) NULL
|
||||
domain$end <- function() {
|
||||
if (!ended) {
|
||||
ended <<- TRUE
|
||||
lapply(callbacks, do.call, list())
|
||||
}
|
||||
invisible()
|
||||
}
|
||||
return(domain)
|
||||
}
|
||||
|
||||
#
|
||||
# 2. The initial value of "current domain" is null.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
.globals$domain <- NULL
|
||||
|
||||
#
|
||||
# 3. Objects that can be owned include observers, reactive expressions,
|
||||
# invalidateLater instances, reactiveTimer instances. Whenever one of these is
|
||||
# created, by default its owner will be the current domain.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
getDefaultReactiveDomain <- function() {
|
||||
.globals$domain
|
||||
}
|
||||
|
||||
#
|
||||
# 4. While a session is being created and the shinyServer function is executed,
|
||||
# the current domain is set to the new session. When the shinyServer function
|
||||
# is done executing, the previous value of the current domain is restored. This
|
||||
# is made foolproof using a `withReactiveDomain` function.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
withReactiveDomain <- function(domain, expr) {
|
||||
oldValue <- .globals$domain
|
||||
.globals$domain <- domain
|
||||
on.exit(.globals$domain <- oldValue)
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
#
|
||||
# 5. While an observer or reactive expression is executing, the current domain
|
||||
# is set to the owner of the observer. When the observer completes, the
|
||||
# previous value of the current domain is restored.
|
||||
#
|
||||
# 6. Note that once created, an observer/reactive expression belongs to the
|
||||
# same domain forever, regardless of how many times it is invalidated and
|
||||
# re-executed, and regardless of what caused the invalidation to happen.
|
||||
#
|
||||
# 7. When a session ends, any observers that it owns are suspended, any
|
||||
# invalidateLater/reactiveTimers are stopped.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
onReactiveDomainEnded <- function(domain, callback, failIfNull = FALSE) {
|
||||
if (is.null(domain)) {
|
||||
if (isTRUE(failIfNull))
|
||||
stop("onReactiveDomainEnded called with null domain and failIfNull=TRUE")
|
||||
else
|
||||
return()
|
||||
}
|
||||
domain$onEnded(callback)
|
||||
}
|
||||
|
||||
#
|
||||
# 8. If an uncaught error occurs while executing an observer, the session gets
|
||||
# a chance to handle it. I suppose the default behavior would be to send the
|
||||
# message to the client if possible, and then perhaps end the session (or not,
|
||||
# I could argue either way).
|
||||
#
|
||||
# The basic idea here is inspired by Node.js domains, which you can think of as
|
||||
# a way to track execution contexts across callback- or listener-oriented
|
||||
# asynchronous code. They use it to unify error handling code across a graph of
|
||||
# related objects. Our domains will be to unify both lifetime and error
|
||||
# handling across a graph of related reactive primitives.
|
||||
#
|
||||
# (You could imagine that as a client update is being processed, the session
|
||||
# associated with that client would become the current domain. IIRC this is how
|
||||
# showcase mode is implemented today. I don't think this would cover any cases
|
||||
# not covered by rule 5 above, and the absence of rule 5 would leave cases that
|
||||
# this rule would not cover.)
|
||||
#
|
||||
# Pitfalls/open issues:
|
||||
#
|
||||
# 1. Our current approach has the issue of observers staying alive longer than
|
||||
# they ought to. This proposal introduces the opposite risk: that
|
||||
# observers/invalidateLater/reactiveTimer instances, having implicitly been
|
||||
# assigned a parent, are suspended/disposed earlier than they ought to have
|
||||
# been. I find this especially worrisome for invalidateLater/reactiveTimer,
|
||||
# which will often be called in a reactive expression, and thus execute under
|
||||
# unpredictable circumstances. Perhaps those should continue to accept an
|
||||
# explicit "session=" parameter that the user is warned about if they don't
|
||||
# provide a value.
|
||||
#
|
||||
# 2. Are there situations where it is ambiguous what the right thing to do is,
|
||||
# and we should warn/error to ask the user to provide a domain explicitly?
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' Reactive domains
|
||||
#'
|
||||
#' Reactive domains are a mechanism for establishing ownership over reactive
|
||||
#' primitives (like reactive expressions and observers), even if the set of
|
||||
#' reactive primitives is dynamically created. This is useful for lifetime
|
||||
#' management (i.e. destroying observers when the Shiny session that created
|
||||
#' 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
|
||||
#' access the current default reactive domain by calling
|
||||
#' \code{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}}.
|
||||
#'
|
||||
#' 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.
|
||||
#'
|
||||
#' Implementers of new reactive primitives can use \code{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
|
||||
#' 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}
|
||||
#' @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}
|
||||
NULL
|
||||
|
||||
#
|
||||
# Example 1
|
||||
# ---
|
||||
# ```
|
||||
# obs1 <- observe({
|
||||
# })
|
||||
# shinyServer(function(input, output) {
|
||||
# obs2 <- observe({
|
||||
# obs3 <- observe({
|
||||
# })
|
||||
# })
|
||||
# })
|
||||
# # obs1 would have no domain, obs2 and obs3 would be owned by the session
|
||||
# ```
|
||||
#
|
||||
# Example 2
|
||||
# ---
|
||||
# ```
|
||||
# globalValues <- reactiveValues(broadcast="")
|
||||
# shinyServer(function(input, output) {
|
||||
# sessionValues <- reactiveValues()
|
||||
# output$messageOutput <- renderText({
|
||||
# globalValues$broadcast
|
||||
# obs1 <- observe({...})
|
||||
# })
|
||||
# observe({
|
||||
# if (input$goButton == 0) return()
|
||||
# isolate( globalValues$broadcast <- input$messageInput )
|
||||
# })
|
||||
# })
|
||||
# # The observer behind messageOutput would be owned by the session,
|
||||
# # as would all the many instances of obs1 that were created.
|
||||
# ```
|
||||
# ---
|
||||
#
|
||||
# Example 3
|
||||
# ---
|
||||
# ```
|
||||
# rexpr1 <- reactive({
|
||||
# invalidateLater(1000)
|
||||
# obs1 <- observe({...})
|
||||
# })
|
||||
# observeSomething <- function() {
|
||||
# obs2 <- observe({...})
|
||||
# })
|
||||
# shinyServer(function(input, output) {
|
||||
# obs3 <- observe({
|
||||
# observeSomething()
|
||||
# rexpr1()
|
||||
# })
|
||||
# })
|
||||
# # rexpr1, the invalidateLater call, and obs1 would all have no owner;
|
||||
# # obs2 and obs3 would be owned by the session.
|
||||
# ```
|
||||
1177
R/reactives.R
1177
R/reactives.R
File diff suppressed because it is too large
Load Diff
167
R/run-url.R
Normal file
167
R/run-url.R
Normal file
@@ -0,0 +1,167 @@
|
||||
#' Run a Shiny application from https://gist.github.com
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted on GitHub as a gist.
|
||||
#'
|
||||
#' @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 all valid values.
|
||||
#' @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.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGist(3239667)
|
||||
#' runGist("https://gist.github.com/jcheng5/3239667")
|
||||
#'
|
||||
#' # Old URL format without username
|
||||
#' runGist("https://gist.github.com/3239667")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runGist <- function(gist,
|
||||
port=NULL,
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive())) {
|
||||
|
||||
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
|
||||
sprintf('https://gist.github.com/%s/download', gist)
|
||||
} else if(grepl('^https://gist.github.com/([^/]+/)?([0-9a-f]+)$', gist)) {
|
||||
paste(gist, '/download', sep='')
|
||||
} else {
|
||||
stop('Unrecognized gist identifier format')
|
||||
}
|
||||
|
||||
runUrl(gistUrl, filetype=".tar.gz", subdir=NULL, port=port,
|
||||
launch.browser=launch.browser)
|
||||
}
|
||||
|
||||
|
||||
#' Run a Shiny application from a GitHub repository
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted in a GitHub repository.
|
||||
#'
|
||||
#' @param repo Name of the repository
|
||||
#' @param username GitHub username
|
||||
#' @param ref Desired git reference. Could be a commit, tag, or branch
|
||||
#' name. Defaults to \code{"master"}.
|
||||
#' @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 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.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGitHub("shiny_example", "rstudio")
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the repo
|
||||
#' runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runGitHub <- function(repo, username = getOption("github.user"),
|
||||
ref = "master", subdir = NULL, port = NULL,
|
||||
launch.browser = getOption('shiny.launch.browser', interactive())) {
|
||||
|
||||
if (is.null(ref)) {
|
||||
stop("Must specify either a ref. ")
|
||||
}
|
||||
|
||||
message("Downloading github repo(s) ",
|
||||
paste(repo, ref, sep = "/", collapse = ", "),
|
||||
" from ",
|
||||
paste(username, collapse = ", "))
|
||||
name <- paste(username, "-", repo, sep = "")
|
||||
|
||||
url <- paste("https://github.com/", username, "/", repo, "/archive/",
|
||||
ref, ".tar.gz", sep = "")
|
||||
|
||||
runUrl(url, subdir=subdir, port=port, launch.browser=launch.browser)
|
||||
}
|
||||
|
||||
|
||||
#' Run a Shiny application from a URL
|
||||
#'
|
||||
#' Download and launch 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 a subdirectory in the
|
||||
#' archive. For example, the files might be \code{myapp/server.r} and
|
||||
#' \code{myapp/ui.r}.
|
||||
#'
|
||||
#' @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 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 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.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the archive
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
#' subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, port = NULL,
|
||||
launch.browser = getOption('shiny.launch.browser', interactive())) {
|
||||
|
||||
if (!is.null(subdir) && ".." %in% strsplit(subdir, '/')[[1]])
|
||||
stop("'..' not allowed in subdir")
|
||||
|
||||
if (is.null(filetype))
|
||||
filetype <- basename(url)
|
||||
|
||||
if (grepl("\\.tar\\.gz$", filetype))
|
||||
fileext <- ".tar.gz"
|
||||
else if (grepl("\\.tar$", filetype))
|
||||
fileext <- ".tar"
|
||||
else if (grepl("\\.zip$", filetype))
|
||||
fileext <- ".zip"
|
||||
else
|
||||
stop("Unknown file extension.")
|
||||
|
||||
message("Downloading ", url)
|
||||
filePath <- tempfile('shinyapp', fileext=fileext)
|
||||
fileDir <- tempfile('shinyapp')
|
||||
dir.create(fileDir, showWarnings = FALSE)
|
||||
if (download(url, filePath, mode = "wb", quiet = TRUE) != 0)
|
||||
stop("Failed to download URL ", url)
|
||||
on.exit(unlink(filePath))
|
||||
|
||||
if (fileext %in% c(".tar", ".tar.gz")) {
|
||||
# Regular untar commonly causes two problems on Windows with github tarballs:
|
||||
# 1) If RTools' tar.exe is in the path, you get cygwin path warnings which
|
||||
# throw list=TRUE off;
|
||||
# 2) If the internal untar implementation is used, it chokes on the 'g'
|
||||
# type flag that github uses (to stash their commit hash info).
|
||||
# By using our own forked/modified untar2 we sidestep both issues.
|
||||
first <- untar2(filePath, list=TRUE)[1]
|
||||
untar2(filePath, exdir = fileDir)
|
||||
|
||||
} else if (fileext == ".zip") {
|
||||
first <- as.character(unzip(filePath, list=TRUE)$Name)[1]
|
||||
unzip(filePath, exdir = fileDir)
|
||||
}
|
||||
on.exit(unlink(fileDir, recursive = TRUE), add = TRUE)
|
||||
|
||||
appdir <- file.path(fileDir, first)
|
||||
if (!file_test('-d', appdir)) appdir <- dirname(appdir)
|
||||
|
||||
if (!is.null(subdir)) appdir <- file.path(appdir, subdir)
|
||||
runApp(appdir, port=port, launch.browser=launch.browser)
|
||||
}
|
||||
801
R/server.R
Normal file
801
R/server.R
Normal file
@@ -0,0 +1,801 @@
|
||||
#' @include globals.R
|
||||
|
||||
appsByToken <- Map$new()
|
||||
|
||||
# Create a map for input handlers and register the defaults.
|
||||
inputHandlers <- Map$new()
|
||||
|
||||
#' Register an Input Handler
|
||||
#'
|
||||
#' 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 RJSONIO) before making it available in the \code{input}
|
||||
#' variable of the \code{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
|
||||
#' 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".
|
||||
#'
|
||||
#' Currently Shiny registers the following handlers: \code{shiny.matrix},
|
||||
#' \code{shiny.number}, and \code{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.
|
||||
#' @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 RJSONIO.}
|
||||
#' \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.
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Register an input handler which rounds a input number to the nearest integer
|
||||
#' registerInputHandler("mypackage.validint", function(x, shinysession, name) {
|
||||
#' if (is.null(x)) return(NA)
|
||||
#' round(x)
|
||||
#' })
|
||||
#'
|
||||
#' ## On the Javascript side, the associated input binding must have a corresponding getType method:
|
||||
#' getType: function(el) {
|
||||
#' return "mypackage.validint";
|
||||
#' }
|
||||
#'
|
||||
#' }
|
||||
#' @seealso \code{\link{removeInputHandler}}
|
||||
#' @export
|
||||
registerInputHandler <- function(type, fun, force=FALSE){
|
||||
if (inputHandlers$containsKey(type) && !force){
|
||||
stop("There is already an input handler for type: ", type)
|
||||
}
|
||||
inputHandlers$set(type, fun)
|
||||
}
|
||||
|
||||
#' Deregister an Input Handler
|
||||
#'
|
||||
#' Removes an Input Handler. Rather than using the previously specified handler
|
||||
#' for data of this type, the default RJSONIO 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}}
|
||||
#' @export
|
||||
removeInputHandler <- function(type){
|
||||
inputHandlers$remove(type)
|
||||
}
|
||||
|
||||
# Takes a list-of-lists and returns a matrix. The lists
|
||||
# must all be the same length. NULL is replaced by NA.
|
||||
registerInputHandler("shiny.matrix", function(data, ...) {
|
||||
if (length(data) == 0)
|
||||
return(matrix(nrow=0, ncol=0))
|
||||
|
||||
m <- matrix(unlist(lapply(data, function(x) {
|
||||
sapply(x, function(y) {
|
||||
ifelse(is.null(y), NA, y)
|
||||
})
|
||||
})), nrow = length(data[[1]]), ncol = length(data))
|
||||
return(m)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.number", function(val, ...){
|
||||
ifelse(is.null(val), NA, val)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.date", function(val, ...){
|
||||
# First replace NULLs with NA, then convert to Date vector
|
||||
datelist <- ifelse(lapply(val, is.null), NA, val)
|
||||
as.Date(unlist(datelist))
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.action", function(val, ...) {
|
||||
# mark up the action button value with a special class so we can recognize it later
|
||||
class(val) <- c(class(val), "shinyActionButtonValue")
|
||||
val
|
||||
})
|
||||
|
||||
# Provide a character representation of the WS that can be used
|
||||
# as a key in a Map.
|
||||
wsToKey <- function(WS) {
|
||||
as.character(WS$socket)
|
||||
}
|
||||
|
||||
.globals$clients <- function(req) NULL
|
||||
|
||||
|
||||
clearClients <- function() {
|
||||
.globals$clients <- function(req) NULL
|
||||
}
|
||||
|
||||
|
||||
registerClient <- function(client) {
|
||||
.globals$clients <- append(.globals$clients, 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; and must begin with a-z or A-Z.
|
||||
#' 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-z][a-z0-9\\-_.]*$', prefix, ignore.case=TRUE, perl=TRUE)) {
|
||||
stop("addResourcePath called with invalid prefix; please see documentation")
|
||||
}
|
||||
|
||||
if (prefix %in% c('shared')) {
|
||||
stop("addResourcePath called with the reserved prefix '", prefix, "'; ",
|
||||
"please use a different prefix")
|
||||
}
|
||||
|
||||
directoryPath <- normalizePath(directoryPath, mustWork=TRUE)
|
||||
|
||||
existing <- .globals$resources[[prefix]]
|
||||
|
||||
if (!is.null(existing)) {
|
||||
if (!identical(existing$directoryPath, directoryPath)) {
|
||||
warning("Overriding existing prefix ", prefix, " => ",
|
||||
existing$directoryPath)
|
||||
}
|
||||
}
|
||||
|
||||
.globals$resources[[prefix]] <- list(directoryPath=directoryPath,
|
||||
func=staticHandler(directoryPath))
|
||||
}
|
||||
|
||||
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.
|
||||
#'
|
||||
#' @param func The server function for this application. See the details section
|
||||
#' for more information.
|
||||
#'
|
||||
#' @details
|
||||
#' Call \code{shinyServer} from your application's \code{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
|
||||
#' needed.
|
||||
#'
|
||||
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{tutorial} for more
|
||||
#' on how to write a server function.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # A very simple Shiny app that takes a message from the user
|
||||
#' # and outputs an uppercase version of it.
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' output$uppercase <- renderText({
|
||||
#' toupper(input$message)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
shinyServer <- function(func) {
|
||||
.globals$server <- list(func)
|
||||
invisible(func)
|
||||
}
|
||||
|
||||
decodeMessage <- function(data) {
|
||||
readInt <- function(pos) {
|
||||
packBits(rawToBits(data[pos:(pos+3)]), type='integer')
|
||||
}
|
||||
|
||||
if (readInt(1) != 0x01020202L)
|
||||
return(fromJSON(rawToChar(data), asText=TRUE, simplify=FALSE))
|
||||
|
||||
i <- 5
|
||||
parts <- list()
|
||||
while (i <= length(data)) {
|
||||
length <- readInt(i)
|
||||
i <- i + 4
|
||||
if (length != 0)
|
||||
parts <- append(parts, list(data[i:(i+length-1)]))
|
||||
else
|
||||
parts <- append(parts, list(raw(0)))
|
||||
i <- i + length
|
||||
}
|
||||
|
||||
mainMessage <- decodeMessage(parts[[1]])
|
||||
mainMessage$blobs <- parts[2:length(parts)]
|
||||
return(mainMessage)
|
||||
}
|
||||
|
||||
createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
appvars <- new.env()
|
||||
appvars$server <- NULL
|
||||
|
||||
sys.www.root <- system.file('www', package='shiny')
|
||||
|
||||
# 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', NULL)
|
||||
|
||||
appHandlers <- list(
|
||||
http = joinHandlers(c(
|
||||
sessionHandler,
|
||||
httpHandlers,
|
||||
sys.www.root,
|
||||
resourcePathHandler,
|
||||
reactLogHandler)),
|
||||
ws = function(ws) {
|
||||
if (!is.null(sharedSecret)
|
||||
&& !identical(sharedSecret, ws$request$HTTP_SHINY_SHARED_SECRET)) {
|
||||
ws$close()
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
shinysession <- ShinySession$new(ws)
|
||||
appsByToken$set(shinysession$token, shinysession)
|
||||
shinysession$setShowcase(.globals$showcaseDefault)
|
||||
|
||||
ws$onMessage(function(binary, msg) {
|
||||
# To ease transition from websockets-based code. Should remove once we're stable.
|
||||
if (is.character(msg))
|
||||
msg <- charToRaw(msg)
|
||||
|
||||
if (getOption('shiny.trace', FALSE)) {
|
||||
if (binary)
|
||||
message("RECV ", '$$binary data$$')
|
||||
else
|
||||
message("RECV ", rawToChar(msg))
|
||||
}
|
||||
|
||||
if (identical(charToRaw("\003\xe9"), msg))
|
||||
return()
|
||||
|
||||
msg <- decodeMessage(msg)
|
||||
|
||||
# Do our own list simplifying here. sapply/simplify2array give names to
|
||||
# character vectors, which is rarely what we want.
|
||||
if (!is.null(msg$data)) {
|
||||
for (name in names(msg$data)) {
|
||||
val <- msg$data[[name]]
|
||||
|
||||
splitName <- strsplit(name, ':')[[1]]
|
||||
if (length(splitName) > 1) {
|
||||
msg$data[[name]] <- NULL
|
||||
|
||||
if (!inputHandlers$containsKey(splitName[[2]])){
|
||||
# No input handler registered for this type
|
||||
stop("No handler registered for for type ", name)
|
||||
}
|
||||
|
||||
msg$data[[ splitName[[1]] ]] <-
|
||||
inputHandlers$get(splitName[[2]])(
|
||||
val,
|
||||
shinysession,
|
||||
splitName[[1]] )
|
||||
}
|
||||
else if (is.list(val) && is.null(names(val))) {
|
||||
val_flat <- unlist(val, recursive = TRUE)
|
||||
|
||||
if (is.null(val_flat)) {
|
||||
# This is to assign NULL instead of deleting the item
|
||||
msg$data[name] <- list(NULL)
|
||||
} else {
|
||||
msg$data[[name]] <- val_flat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(
|
||||
msg$method,
|
||||
init = {
|
||||
|
||||
serverFunc <- serverFuncSource()
|
||||
if (!identicalFunctionBodies(serverFunc, appvars$server)) {
|
||||
appvars$server <- serverFunc
|
||||
if (!is.null(appvars$server))
|
||||
{
|
||||
# Tag this function as the Shiny server function. A debugger may use this
|
||||
# tag to give this function special treatment.
|
||||
# It's very important that it's appvars$server itself and NOT a copy that
|
||||
# is invoked, otherwise new breakpoints won't be picked up.
|
||||
attr(appvars$server, "shinyServerFunction") <- TRUE
|
||||
registerDebugHook("server", appvars, "Server Function")
|
||||
}
|
||||
}
|
||||
|
||||
# Check for switching into/out of showcase mode
|
||||
if (.globals$showcaseOverride &&
|
||||
exists(".clientdata_url_search", where = msg$data)) {
|
||||
mode <- showcaseModeOfQuerystring(msg$data$.clientdata_url_search)
|
||||
if (!is.null(mode))
|
||||
shinysession$setShowcase(mode)
|
||||
}
|
||||
|
||||
shinysession$manageInputs(msg$data)
|
||||
|
||||
# The client tells us what singletons were rendered into
|
||||
# the initial page
|
||||
if (!is.null(msg$data$.clientdata_singletons)) {
|
||||
shinysession$singletons <<- strsplit(
|
||||
msg$data$.clientdata_singletons, ',')[[1]]
|
||||
}
|
||||
|
||||
local({
|
||||
args <- list(
|
||||
input=shinysession$input,
|
||||
output=.createOutputWriter(shinysession))
|
||||
|
||||
# The clientData and session arguments are optional; check if
|
||||
# each exists
|
||||
if ('clientData' %in% names(formals(serverFunc)))
|
||||
args$clientData <- shinysession$clientData
|
||||
|
||||
if ('session' %in% names(formals(serverFunc)))
|
||||
args$session <- shinysession$session
|
||||
|
||||
withReactiveDomain(shinysession$session, {
|
||||
do.call(appvars$server, args)
|
||||
})
|
||||
})
|
||||
},
|
||||
update = {
|
||||
shinysession$manageInputs(msg$data)
|
||||
},
|
||||
shinysession$dispatch(msg)
|
||||
)
|
||||
shinysession$manageHiddenOutputs()
|
||||
|
||||
if (exists(".shiny__stdout", globalenv()) &&
|
||||
exists("HTTP_GUID", ws$request)) {
|
||||
# safe to assume we're in shiny-server
|
||||
shiny_stdout <- get(".shiny__stdout", globalenv())
|
||||
|
||||
# eNter a flushReact
|
||||
writeLines(paste("_n_flushReact ", get("HTTP_GUID", ws$request),
|
||||
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
|
||||
sep=""), con=shiny_stdout)
|
||||
flush(shiny_stdout)
|
||||
|
||||
flushReact()
|
||||
|
||||
# eXit a flushReact
|
||||
writeLines(paste("_x_flushReact ", get("HTTP_GUID", ws$request),
|
||||
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
|
||||
sep=""), con=shiny_stdout)
|
||||
flush(shiny_stdout)
|
||||
} else {
|
||||
flushReact()
|
||||
}
|
||||
lapply(appsByToken$values(), function(shinysession) {
|
||||
shinysession$flushOutput()
|
||||
NULL
|
||||
})
|
||||
})
|
||||
|
||||
ws$onClose(function() {
|
||||
shinysession$close()
|
||||
appsByToken$remove(shinysession$token)
|
||||
})
|
||||
|
||||
return(TRUE)
|
||||
}
|
||||
)
|
||||
return(appHandlers)
|
||||
}
|
||||
|
||||
getEffectiveBody <- function(func) {
|
||||
# Note: NULL values are OK. isS4(NULL) returns FALSE, body(NULL)
|
||||
# returns NULL.
|
||||
if (isS4(func) && class(func) == "functionWithTrace")
|
||||
body(func@original)
|
||||
else
|
||||
body(func)
|
||||
}
|
||||
|
||||
identicalFunctionBodies <- function(a, b) {
|
||||
identical(getEffectiveBody(a), getEffectiveBody(b))
|
||||
}
|
||||
|
||||
handlerManager <- HandlerManager$new()
|
||||
|
||||
addSubApp <- function(appObj, autoRemove = TRUE) {
|
||||
path <- createUniqueId(16, "/app")
|
||||
appHandlers <- createAppHandlers(appObj$httpHandler, appObj$serverFuncSource)
|
||||
|
||||
# remove the leading / from the path so a relative path is returned
|
||||
# (needed for the case where the root URL for the Shiny app isn't /, such
|
||||
# as portmapped URLs)
|
||||
finalPath <- paste(
|
||||
substr(path, 2, nchar(path)),
|
||||
"/?w=", workerId(),
|
||||
"&__subapp__=1",
|
||||
sep="")
|
||||
handlerManager$addHandler(routeHandler(path, appHandlers$http), finalPath)
|
||||
handlerManager$addWSHandler(routeWSHandler(path, appHandlers$ws), finalPath)
|
||||
|
||||
if (autoRemove) {
|
||||
# If a session is currently active, remove this subapp automatically when
|
||||
# the current session ends
|
||||
onReactiveDomainEnded(getDefaultReactiveDomain(), function() {
|
||||
removeSubApp(finalPath)
|
||||
})
|
||||
}
|
||||
|
||||
return(finalPath)
|
||||
}
|
||||
|
||||
removeSubApp <- function(path) {
|
||||
handlerManager$removeHandler(path)
|
||||
handlerManager$removeWSHandler(path)
|
||||
}
|
||||
|
||||
startApp <- function(appObj, port, host, quiet) {
|
||||
appHandlers <- createAppHandlers(appObj$httpHandler, appObj$serverFuncSource)
|
||||
handlerManager$addHandler(appHandlers$http, "/", tail = TRUE)
|
||||
handlerManager$addWSHandler(appHandlers$ws, "/", tail = TRUE)
|
||||
|
||||
if (is.numeric(port) || is.integer(port)) {
|
||||
if (!quiet) {
|
||||
message('\n', 'Listening on http://', host, ':', port)
|
||||
}
|
||||
return(startServer(host, port, handlerManager$createHttpuvApp()))
|
||||
} else if (is.character(port)) {
|
||||
if (!quiet) {
|
||||
message('\n', 'Listening on domain socket ', port)
|
||||
}
|
||||
mask <- attr(port, 'mask')
|
||||
return(startPipeServer(port, mask, handlerManager$createHttpuvApp()))
|
||||
}
|
||||
}
|
||||
|
||||
# Run an application that was created by \code{\link{startApp}}. This
|
||||
# function should normally be called in a \code{while(TRUE)} loop.
|
||||
serviceApp <- function() {
|
||||
if (timerCallbacks$executeElapsed()) {
|
||||
for (shinysession in appsByToken$values()) {
|
||||
shinysession$manageHiddenOutputs()
|
||||
}
|
||||
|
||||
flushReact()
|
||||
|
||||
for (shinysession in appsByToken$values()) {
|
||||
shinysession$flushOutput()
|
||||
}
|
||||
}
|
||||
|
||||
# If this R session is interactive, then call service() with a short timeout
|
||||
# to keep the session responsive to user input
|
||||
maxTimeout <- ifelse(interactive(), 100, 1000)
|
||||
|
||||
timeout <- max(1, min(maxTimeout, timerCallbacks$timeToNextEvent()))
|
||||
service(timeout)
|
||||
}
|
||||
|
||||
.shinyServerMinVersion <- '0.3.4'
|
||||
|
||||
#' 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 directory of the application. Should contain
|
||||
#' \code{server.R}, plus, either \code{ui.R} or a \code{www} directory that
|
||||
#' contains the file \code{index.html}. Defaults to the working directory.
|
||||
#' @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. 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.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Start app in the current working directory
|
||||
#' runApp()
|
||||
#'
|
||||
#' # Start app in a subdirectory called myapp
|
||||
#' runApp("myapp")
|
||||
#'
|
||||
#'
|
||||
#' # 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)) })
|
||||
#' }
|
||||
#' ))
|
||||
#' }
|
||||
#' @export
|
||||
runApp <- function(appDir=getwd(),
|
||||
port=NULL,
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
workerId="", quiet=FALSE,
|
||||
display.mode=c("auto", "normal", "showcase")) {
|
||||
on.exit({
|
||||
handlerManager$clear()
|
||||
}, add = TRUE)
|
||||
|
||||
|
||||
if (is.null(host) || is.na(host))
|
||||
host <- '0.0.0.0'
|
||||
|
||||
# Make warnings print immediately
|
||||
ops <- options(warn = 1)
|
||||
on.exit(options(ops), add = TRUE)
|
||||
|
||||
workerId(workerId)
|
||||
|
||||
if (nzchar(Sys.getenv('SHINY_PORT'))) {
|
||||
# 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 (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)
|
||||
|
||||
# If appDir specifies a path, and display mode is specified in the
|
||||
# DESCRIPTION file at that path, apply it here.
|
||||
if (is.character(appDir)) {
|
||||
desc <- file.path.ci(appDir, "DESCRIPTION")
|
||||
if (file.exists(desc)) {
|
||||
settings <- read.dcf(desc)
|
||||
if ("DisplayMode" %in% colnames(settings)) {
|
||||
mode <- settings[1,"DisplayMode"]
|
||||
if (mode == "Showcase") {
|
||||
setShowcaseDefault(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 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
|
||||
port <- p_randomInt(3000, 8000)
|
||||
}
|
||||
|
||||
# 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appParts <- as.shiny.appobj(appDir)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
if (!is.null(appParts$onEnd))
|
||||
on.exit(appParts$onEnd(), add = TRUE)
|
||||
|
||||
server <- startApp(appParts, port, host, quiet)
|
||||
|
||||
on.exit({
|
||||
stopServer(server)
|
||||
}, add = TRUE)
|
||||
|
||||
if (!is.character(port)) {
|
||||
# http://0.0.0.0/ doesn't work on QtWebKit (i.e. RStudio viewer)
|
||||
browseHost <- if (identical(host, "0.0.0.0")) "127.0.0.1" else host
|
||||
|
||||
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$retval <- NULL
|
||||
.globals$stopped <- FALSE
|
||||
shinyCallingHandlers(
|
||||
while (!.globals$stopped) {
|
||||
serviceApp()
|
||||
Sys.sleep(0.001)
|
||||
}
|
||||
)
|
||||
|
||||
return(.globals$retval)
|
||||
}
|
||||
|
||||
#' 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 = NULL) {
|
||||
.globals$retval <- returnValue
|
||||
.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
|
||||
#' \dontrun{
|
||||
#' # 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)
|
||||
}
|
||||
}
|
||||
264
R/shinyui.R
264
R/shinyui.R
@@ -1,119 +1,80 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
#' @export
|
||||
p <- function(...) tags$p(...)
|
||||
|
||||
#' @export
|
||||
h1 <- function(...) tags$h1(...)
|
||||
|
||||
#' @export
|
||||
h2 <- function(...) tags$h2(...)
|
||||
|
||||
#' @export
|
||||
h3 <- function(...) tags$h3(...)
|
||||
|
||||
#' @export
|
||||
h4 <- function(...) tags$h4(...)
|
||||
|
||||
#' @export
|
||||
h5 <- function(...) tags$h5(...)
|
||||
|
||||
#' @export
|
||||
h6 <- function(...) tags$h6(...)
|
||||
|
||||
#' @export
|
||||
a <- function(...) tags$a(...)
|
||||
|
||||
#' @export
|
||||
br <- function(...) tags$br(...)
|
||||
|
||||
#' @export
|
||||
div <- function(...) tags$div(...)
|
||||
|
||||
#' @export
|
||||
span <- function(...) tags$span(...)
|
||||
|
||||
#' @export
|
||||
pre <- function(...) tags$pre(...)
|
||||
|
||||
#' @export
|
||||
code <- function(...) tags$code(...)
|
||||
|
||||
#' @export
|
||||
img <- function(...) tags$img(...)
|
||||
|
||||
#' @export
|
||||
strong <- function(...) tags$strong(...)
|
||||
|
||||
#' @export
|
||||
em <- function(...) tags$em(...)
|
||||
|
||||
|
||||
#' Include Content Only Once
|
||||
#'
|
||||
#' Use \code{singleton} to wrap contents (tag, text, HTML, or lists) that should
|
||||
#' be included in the generated document only once, yet may appear in the
|
||||
#' document-generating code more than once. Only the first appearance of the
|
||||
#' content (in document order) will be used. Useful for custom components that
|
||||
#' have JavaScript files or stylesheets.
|
||||
#'
|
||||
#' @param x A \code{\link{tag}}, text, \code{\link{HTML}}, or list.
|
||||
#' 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
|
||||
#' time we write math expressions to the output.
|
||||
#' @param ... any HTML elements to apply MathJax to
|
||||
#' @export
|
||||
singleton <- function(x) {
|
||||
class(x) <- c(class(x), 'shiny.singleton')
|
||||
return(x)
|
||||
#' @examples withMathJax(helpText("Some math here $$\\alpha+\\beta$$"))
|
||||
#' # now we can just write "static" content without withMathJax()
|
||||
#' div("more math here $$\\sqrt{2}$$")
|
||||
withMathJax <- function(...) {
|
||||
path <- 'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
|
||||
tagList(
|
||||
tags$head(
|
||||
singleton(tags$script(src = path, type = 'text/javascript'))
|
||||
),
|
||||
...,
|
||||
tags$script(HTML('MathJax.Hub.Queue(["Typeset", MathJax.Hub]);'))
|
||||
)
|
||||
}
|
||||
|
||||
renderPage <- function(ui, connection) {
|
||||
|
||||
# provide a filter so we can intercept head tag requests
|
||||
context <- new.env()
|
||||
context$head <- character()
|
||||
context$singletons <- character()
|
||||
context$filter <- function(content) {
|
||||
if (inherits(content, 'shiny.singleton')) {
|
||||
sig <- digest(content, algo='sha1')
|
||||
if (sig %in% context$singletons)
|
||||
return(FALSE)
|
||||
context$singletons <- c(sig, context$singletons)
|
||||
}
|
||||
|
||||
if (isTag(content) && identical(content$name, "head")) {
|
||||
textConn <- textConnection(NULL, "w")
|
||||
textConnWriter <- function(text) cat(text, file = textConn)
|
||||
tagWriteChildren(content, textConnWriter, 1, context)
|
||||
context$head <- append(context$head, textConnectionValue(textConn))
|
||||
close(textConn)
|
||||
return (FALSE)
|
||||
}
|
||||
else {
|
||||
return (TRUE)
|
||||
}
|
||||
}
|
||||
|
||||
# write ui HTML to a character vector
|
||||
textConn <- textConnection(NULL, "w")
|
||||
tagWrite(ui, function(text) cat(text, file = textConn), 0, context)
|
||||
uiHTML <- textConnectionValue(textConn)
|
||||
close(textConn)
|
||||
|
||||
renderPage <- function(ui, connection, showcase=0) {
|
||||
|
||||
if (showcase > 0)
|
||||
ui <- tagList(tags$head(showcaseHead()), ui)
|
||||
|
||||
result <- renderTags(ui)
|
||||
|
||||
deps <- c(
|
||||
list(
|
||||
htmlDependency("jquery", "1.11.0", c(href="shared"), script = "jquery.js"),
|
||||
htmlDependency("shiny", packageVersion("shiny"), c(href="shared"),
|
||||
script = "shiny.js", stylesheet = "shiny.css")
|
||||
),
|
||||
result$dependencies
|
||||
)
|
||||
deps <- resolveDependencies(deps)
|
||||
deps <- lapply(deps, createWebDependency)
|
||||
depStr <- paste(sapply(deps, function(dep) {
|
||||
sprintf("%s[%s]", dep$name, dep$version)
|
||||
}), collapse = ";")
|
||||
depHtml <- renderDependencies(deps, "href")
|
||||
|
||||
# write preamble
|
||||
writeLines(c('<!DOCTYPE html>',
|
||||
'<html>',
|
||||
'<head>',
|
||||
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>',
|
||||
' <script src="shared/jquery.js" type="text/javascript"></script>',
|
||||
' <script src="shared/shiny.js" type="text/javascript"></script>',
|
||||
' <link rel="stylesheet" type="text/css" href="shared/shiny.css"/>',
|
||||
context$head,
|
||||
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>',
|
||||
sprintf(' <script type="application/shiny-singletons">%s</script>',
|
||||
paste(result$singletons, collapse = ',')
|
||||
),
|
||||
sprintf(' <script type="application/html-dependencies">%s</script>',
|
||||
depStr
|
||||
),
|
||||
depHtml
|
||||
),
|
||||
con = connection)
|
||||
writeLines(c(result$head,
|
||||
'</head>',
|
||||
'<body>',
|
||||
'<body>',
|
||||
recursive=TRUE),
|
||||
con = connection)
|
||||
|
||||
# write UI html to connection
|
||||
writeLines(uiHTML, con = connection)
|
||||
|
||||
|
||||
if (showcase > 0) {
|
||||
# in showcase mode, emit containing elements and app HTML
|
||||
writeLines(as.character(showcaseBody(result$html)),
|
||||
con = connection)
|
||||
} else {
|
||||
# in normal mode, write UI html directly to connection
|
||||
writeLines(result$html, con = connection)
|
||||
}
|
||||
|
||||
# write end document
|
||||
writeLines(c('</body>',
|
||||
'</html>'),
|
||||
@@ -121,57 +82,52 @@ renderPage <- function(ui, connection) {
|
||||
}
|
||||
|
||||
#' Create a Shiny UI handler
|
||||
#'
|
||||
#' Register a UI handler by providing a UI definition (created with e.g.
|
||||
#' \link{pageWithSidebar}) and web server path (typically "/", the default
|
||||
#' value).
|
||||
#'
|
||||
#' @param ui A user-interace definition
|
||||
#' @param path The web server path to server the UI from
|
||||
#' @return Called for its side-effect of registering a UI handler
|
||||
#'
|
||||
#' @examples
|
||||
#' el <- div(HTML("I like <u>turtles</u>"))
|
||||
#' cat(as.character(el))
|
||||
#'
|
||||
#' @examples
|
||||
#' # Define UI
|
||||
#' shinyUI(pageWithSidebar(
|
||||
#'
|
||||
#' # Application title
|
||||
#' headerPanel("Hello Shiny!"),
|
||||
#'
|
||||
#' # Sidebar with a slider input
|
||||
#' sidebarPanel(
|
||||
#' sliderInput("obs",
|
||||
#' "Number of observations:",
|
||||
#' min = 0,
|
||||
#' max = 1000,
|
||||
#' value = 500)
|
||||
#' ),
|
||||
#'
|
||||
#' # Show a plot of the generated distribution
|
||||
#' mainPanel(
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' Historically this function was used in ui.R files to register a user
|
||||
#' interface with Shiny. It is no longer required; simply ensure that the last
|
||||
#' expression to be returned from ui.R is a user interface. This function is
|
||||
#' kept for backwards compatibility with older applications. It returns the
|
||||
#' value that is passed to it.
|
||||
#'
|
||||
#' @param ui A user interace definition
|
||||
#' @return The user interface definition, without modifications or side effects.
|
||||
#'
|
||||
#' @export
|
||||
shinyUI <- function(ui, path='/') {
|
||||
|
||||
registerClient({
|
||||
|
||||
function(ws, header) {
|
||||
if (header$RESOURCE != path)
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
on.exit(close(textConn))
|
||||
|
||||
renderPage(ui, textConn)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(http_response(ws, 200, content=html))
|
||||
}
|
||||
})
|
||||
shinyUI <- function(ui) {
|
||||
.globals$ui <- list(ui)
|
||||
ui
|
||||
}
|
||||
|
||||
uiHttpHandler <- function(ui, path = "/") {
|
||||
|
||||
force(ui)
|
||||
|
||||
function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
if (req$PATH_INFO != path)
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
on.exit(close(textConn))
|
||||
|
||||
showcaseMode <- .globals$showcaseDefault
|
||||
if (.globals$showcaseOverride) {
|
||||
mode <- showcaseModeOfReq(req)
|
||||
if (!is.null(mode))
|
||||
showcaseMode <- mode
|
||||
}
|
||||
uiValue <- if (is.function(ui)) {
|
||||
if (length(formals(ui)) > 0)
|
||||
ui(req)
|
||||
else
|
||||
ui()
|
||||
}
|
||||
else
|
||||
ui
|
||||
renderPage(uiValue, textConn, showcaseMode)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(httpResponse(200, content=html))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +1,631 @@
|
||||
suppressPackageStartupMessages({
|
||||
library(caTools)
|
||||
library(xtable)
|
||||
})
|
||||
globalVariables('func')
|
||||
|
||||
#' Plot Output
|
||||
#'
|
||||
#' Creates a reactive plot that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-plot-output}.
|
||||
#'
|
||||
#' @param func A function that generates a plot.
|
||||
#' @param width The width of the rendered plot, in pixels; or \code{'auto'} to use
|
||||
#' the \code{offsetWidth} of the HTML element that is bound to this plot.
|
||||
#' @param height The height of the rendered plot, in pixels; or \code{'auto'} to use
|
||||
#' the \code{offsetHeight} of the HTML element that is bound to this plot.
|
||||
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#'
|
||||
#' Mark a function as a render function
|
||||
#'
|
||||
#' Should be called by implementers of \code{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
|
||||
#' output widgets out of just the render function.
|
||||
#'
|
||||
#' @param uiFunc A function that renders Shiny UI. Must take a single argument:
|
||||
#' an output ID.
|
||||
#' @param renderFunc A function that is suitable for assigning to a Shiny output
|
||||
#' slot.
|
||||
#' @return The \code{renderFunc} function, with annotations.
|
||||
#'
|
||||
#' @export
|
||||
reactivePlot <- function(func, width='auto', height='auto', ...) {
|
||||
args <- list(...)
|
||||
|
||||
return(function(shinyapp, name, ...) {
|
||||
png.file <- tempfile(fileext='.png')
|
||||
|
||||
# Note that these are reactive calls. A change to the width and height
|
||||
# will inherently cause a reactive plot to redraw (unless width and
|
||||
# height were explicitly specified).
|
||||
prefix <- '.shinyout_'
|
||||
if (width == 'auto')
|
||||
width <- shinyapp$session$get(paste(prefix, name, '_width', sep=''));
|
||||
if (height == 'auto')
|
||||
height <- shinyapp$session$get(paste(prefix, name, '_height', sep=''));
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
return(NULL)
|
||||
|
||||
do.call(png, c(args, filename=png.file, width=width, height=height))
|
||||
tryCatch(
|
||||
func(),
|
||||
finally=dev.off())
|
||||
|
||||
bytes <- file.info(png.file)$size
|
||||
if (is.na(bytes))
|
||||
return(NULL)
|
||||
|
||||
b64 <- base64encode(readBin(png.file, 'raw', n=bytes))
|
||||
return(paste("data:image/png;base64,", b64, sep=''))
|
||||
})
|
||||
markRenderFunction <- function(uiFunc, renderFunc) {
|
||||
class(renderFunc) <- c("shiny.render.function", "function")
|
||||
attr(renderFunc, "outputFunc") <- uiFunc
|
||||
renderFunc
|
||||
}
|
||||
|
||||
#' Table Output
|
||||
#'
|
||||
#' Creates a reactive table that is suitable for assigning to an \code{output}
|
||||
useRenderFunction <- function(renderFunc) {
|
||||
outputFunction <- attr(renderFunc, "outputFunc")
|
||||
id <- createUniqueId(8, "out")
|
||||
o <- getDefaultReactiveDomain()$output
|
||||
if (!is.null(o))
|
||||
o[[id]] <- renderFunc
|
||||
return(outputFunction(id))
|
||||
}
|
||||
|
||||
#' @S3method as.tags shiny.render.function
|
||||
as.tags.shiny.render.function <- function(x, ...) {
|
||||
useRenderFunction(x)
|
||||
}
|
||||
|
||||
#' Plot Output
|
||||
#'
|
||||
#' Renders a reactive plot that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-plot-output}.
|
||||
#'
|
||||
#' @seealso For more details on how the plots are generated, and how to control
|
||||
#' the output, see \code{\link{plotPNG}}.
|
||||
#'
|
||||
#' @param expr An expression that generates a plot.
|
||||
#' @param width The width of the rendered plot, in pixels; or \code{'auto'} to
|
||||
#' use the \code{offsetWidth} of the HTML element that is bound to this plot.
|
||||
#' You can also pass in a function that returns the width in pixels or
|
||||
#' \code{'auto'}; in the body of the function you may reference reactive
|
||||
#' values and functions.
|
||||
#' @param height The height of the rendered plot, in pixels; or \code{'auto'} to
|
||||
#' use the \code{offsetHeight} of the HTML element that is bound to this plot.
|
||||
#' You can also pass in a function that returns the width in pixels or
|
||||
#' \code{'auto'}; in the body of the function you may reference reactive
|
||||
#' values and functions.
|
||||
#' @param res Resolution of resulting plot, in pixels per inch. This value is
|
||||
#' passed to \code{\link{png}}. Note that this affects the resolution of PNG
|
||||
#' rendering in R; it won't change the actual ppi of the browser.
|
||||
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that generates a plot (deprecated; use \code{expr}
|
||||
#' instead).
|
||||
#'
|
||||
#' @export
|
||||
renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPlot: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
args <- list(...)
|
||||
|
||||
if (is.function(width))
|
||||
widthWrapper <- reactive({ width() })
|
||||
else
|
||||
widthWrapper <- NULL
|
||||
|
||||
if (is.function(height))
|
||||
heightWrapper <- reactive({ height() })
|
||||
else
|
||||
heightWrapper <- NULL
|
||||
|
||||
# If renderPlot isn't going to adapt to the height of the div, then the
|
||||
# div needs to adapt to the height of renderPlot. By default, plotOutput
|
||||
# sets the height to 400px, so to make it adapt we need to override it
|
||||
# with NULL.
|
||||
outputFunc <- if (identical(height, 'auto'))
|
||||
plotOutput
|
||||
else
|
||||
function(outputId) plotOutput(outputId, height = NULL)
|
||||
|
||||
return(markRenderFunction(outputFunc, function(shinysession, name, ...) {
|
||||
if (!is.null(widthWrapper))
|
||||
width <- widthWrapper()
|
||||
if (!is.null(heightWrapper))
|
||||
height <- heightWrapper()
|
||||
|
||||
# Note that these are reactive calls. A change to the width and height
|
||||
# will inherently cause a reactive plot to redraw (unless width and
|
||||
# height were explicitly specified).
|
||||
prefix <- 'output_'
|
||||
if (width == 'auto')
|
||||
width <- shinysession$clientData[[paste(prefix, name, '_width', sep='')]];
|
||||
if (height == 'auto')
|
||||
height <- shinysession$clientData[[paste(prefix, name, '_height', sep='')]];
|
||||
|
||||
if (is.null(width) || is.null(height) || width <= 0 || height <= 0)
|
||||
return(NULL)
|
||||
|
||||
# Resolution multiplier
|
||||
pixelratio <- shinysession$clientData$pixelratio
|
||||
if (is.null(pixelratio))
|
||||
pixelratio <- 1
|
||||
|
||||
coordmap <- NULL
|
||||
plotFunc <- function() {
|
||||
# Actually perform the plotting
|
||||
result <- withVisible(func())
|
||||
if (result$visible) {
|
||||
# Use capture.output to squelch printing to the actual console; we
|
||||
# are only interested in plot output
|
||||
capture.output(print(result$value))
|
||||
}
|
||||
|
||||
# Now capture some graphics device info before we close it
|
||||
usrCoords <- par('usr')
|
||||
usrBounds <- usrCoords
|
||||
if (par('xlog')) {
|
||||
usrBounds[c(1,2)] <- 10 ^ usrBounds[c(1,2)]
|
||||
}
|
||||
if (par('ylog')) {
|
||||
usrBounds[c(3,4)] <- 10 ^ usrBounds[c(3,4)]
|
||||
}
|
||||
|
||||
coordmap <<- list(
|
||||
usr = c(
|
||||
left = usrCoords[1],
|
||||
right = usrCoords[2],
|
||||
bottom = usrCoords[3],
|
||||
top = usrCoords[4]
|
||||
),
|
||||
# The bounds of the plot area, in DOM pixels
|
||||
bounds = c(
|
||||
left = grconvertX(usrBounds[1], 'user', 'nfc') * width,
|
||||
right = grconvertX(usrBounds[2], 'user', 'nfc') * width,
|
||||
bottom = (1-grconvertY(usrBounds[3], 'user', 'nfc')) * height,
|
||||
top = (1-grconvertY(usrBounds[4], 'user', 'nfc')) * height
|
||||
),
|
||||
log = c(
|
||||
x = par('xlog'),
|
||||
y = par('ylog')
|
||||
),
|
||||
pixelratio = pixelratio
|
||||
)
|
||||
}
|
||||
|
||||
outfile <- do.call(plotPNG, c(plotFunc, width=width*pixelratio,
|
||||
height=height*pixelratio, res=res*pixelratio, args))
|
||||
on.exit(unlink(outfile))
|
||||
|
||||
# Return a list of attributes for the img
|
||||
return(list(
|
||||
src=shinysession$fileUrl(name, outfile, contentType='image/png'),
|
||||
width=width, height=height, coordmap=coordmap
|
||||
))
|
||||
}))
|
||||
}
|
||||
|
||||
#' Image file output
|
||||
#'
|
||||
#' Renders a reactive image that is suitable for assigning to an \code{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}
|
||||
#' 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
|
||||
#' 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.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-image-output}.
|
||||
#'
|
||||
#' @seealso For more details on how the images are generated, and how to control
|
||||
#' the output, see \code{\link{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
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param deleteFile Should the file in \code{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}.
|
||||
#'
|
||||
#' @export
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#'
|
||||
#' shinyServer(function(input, output, clientData) {
|
||||
#'
|
||||
#' # A plot of fixed size
|
||||
#' output$plot1 <- renderImage({
|
||||
#' # A temp file to save the output. It will be deleted after renderImage
|
||||
#' # sends it, because deleteFile=TRUE.
|
||||
#' outfile <- tempfile(fileext='.png')
|
||||
#'
|
||||
#' # Generate a png
|
||||
#' png(outfile, width=400, height=400)
|
||||
#' hist(rnorm(input$n))
|
||||
#' dev.off()
|
||||
#'
|
||||
#' # Return a list
|
||||
#' list(src = outfile,
|
||||
#' alt = "This is alternate text")
|
||||
#' }, deleteFile = TRUE)
|
||||
#'
|
||||
#' # A dynamically-sized plot
|
||||
#' output$plot2 <- renderImage({
|
||||
#' # Read plot2's width and height. These are reactive values, so this
|
||||
#' # expression will re-run whenever these values change.
|
||||
#' width <- clientData$output_plot2_width
|
||||
#' height <- clientData$output_plot2_height
|
||||
#'
|
||||
#' # A temp file to save the output.
|
||||
#' outfile <- tempfile(fileext='.png')
|
||||
#'
|
||||
#' png(outfile, width=width, height=height)
|
||||
#' hist(rnorm(input$obs))
|
||||
#' dev.off()
|
||||
#'
|
||||
#' # Return a list containing the filename
|
||||
#' list(src = outfile,
|
||||
#' width = width,
|
||||
#' height = height,
|
||||
#' alt = "This is alternate text")
|
||||
#' }, deleteFile = TRUE)
|
||||
#'
|
||||
#' # Send a pre-rendered image, and don't delete the image after sending it
|
||||
#' output$plot3 <- renderImage({
|
||||
#' # When input$n is 1, filename is ./images/image1.jpeg
|
||||
#' filename <- normalizePath(file.path('./images',
|
||||
#' paste('image', input$n, '.jpeg', sep='')))
|
||||
#'
|
||||
#' # Return a list containing the filename
|
||||
#' list(src = filename)
|
||||
#' }, deleteFile = FALSE)
|
||||
#' })
|
||||
#'
|
||||
#' }
|
||||
renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
deleteFile=TRUE) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
return(markRenderFunction(imageOutput, function(shinysession, name, ...) {
|
||||
imageinfo <- func()
|
||||
# Should the file be deleted after being sent? If .deleteFile not set or if
|
||||
# TRUE, then delete; otherwise don't delete.
|
||||
if (deleteFile) {
|
||||
on.exit(unlink(imageinfo$src))
|
||||
}
|
||||
|
||||
# If contentType not specified, autodetect based on extension
|
||||
if (is.null(imageinfo$contentType)) {
|
||||
contentType <- getContentType(sub('^.*\\.', '', basename(imageinfo$src)))
|
||||
} else {
|
||||
contentType <- imageinfo$contentType
|
||||
}
|
||||
|
||||
# Extra values are everything in imageinfo except 'src' and 'contentType'
|
||||
extra_attr <- imageinfo[!names(imageinfo) %in% c('src', 'contentType')]
|
||||
|
||||
# Return a list with src, and other img attributes
|
||||
c(src = shinysession$fileUrl(name, file=imageinfo$src, contentType=contentType),
|
||||
extra_attr)
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
#' Table Output
|
||||
#'
|
||||
#' Creates a reactive table that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} and have the CSS class
|
||||
#' name \code{shiny-html-output}.
|
||||
#'
|
||||
#' @param func A function that returns an R object that can be used with
|
||||
#'
|
||||
#' @param expr An expression that returns an R object that can be used with
|
||||
#' \code{\link[xtable]{xtable}}.
|
||||
#' @param ... Arguments to be passed through to \code{\link[xtable]{xtable}}.
|
||||
#'
|
||||
#' @param ... Arguments to be passed through to \code{\link[xtable]{xtable}} and
|
||||
#' \code{\link[xtable]{print.xtable}}.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns an R object that can be used with
|
||||
#' \code{\link[xtable]{xtable}} (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @export
|
||||
reactiveTable <- function(func, ...) {
|
||||
reactive(function() {
|
||||
renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderTable: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
markRenderFunction(tableOutput, function() {
|
||||
classNames <- getOption('shiny.table.class', 'data table table-bordered table-condensed')
|
||||
data <- func()
|
||||
|
||||
if (is.null(data) || identical(data, data.frame()))
|
||||
return("")
|
||||
|
||||
return(paste(
|
||||
capture.output(
|
||||
print(xtable(data, ...),
|
||||
type='html',
|
||||
print(xtable(data, ...),
|
||||
type='html',
|
||||
html.table.attributes=paste('class="',
|
||||
htmlEscape(classNames, T),
|
||||
htmlEscape(classNames, TRUE),
|
||||
'"',
|
||||
sep=''))),
|
||||
sep=''), ...)),
|
||||
collapse="\n"))
|
||||
})
|
||||
}
|
||||
|
||||
#' Printable Output
|
||||
#'
|
||||
#' Makes a reactive version of the given function that also turns its printable
|
||||
#' result into a string. The reactive function is suitable for assigning to an
|
||||
#' \code{output} slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag can be anything (though \code{pre} is
|
||||
#'
|
||||
#' Makes a reactive version of the given function that captures any printed
|
||||
#' output, and also captures its printable result (unless
|
||||
#' \code{\link{invisible}}), into a string. The resulting function is suitable
|
||||
#' for assigning to an \code{output} slot.
|
||||
#'
|
||||
#' 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 be printed inside a
|
||||
#'
|
||||
#' The result of executing \code{func} will be printed inside a
|
||||
#' \code{\link[utils]{capture.output}} call.
|
||||
#'
|
||||
#' @param func A function that returns a printable R object.
|
||||
#'
|
||||
#'
|
||||
#' Note that unlike most other Shiny output functions, if the given function
|
||||
#' returns \code{NULL} then \code{NULL} will actually be visible in the output.
|
||||
#' To display nothing, make your function return \code{\link{invisible}()}.
|
||||
#'
|
||||
#' @param expr An expression that may print output and/or return a printable R
|
||||
#' object.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' @param func A function that may print output and/or return a printable R
|
||||
#' object (deprecated; use \code{expr} instead).
|
||||
#' @param width The value for \code{\link{options}('width')}.
|
||||
#' @seealso \code{\link{renderText}} for displaying the value returned from a
|
||||
#' function, instead of the printed output.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#' @export
|
||||
reactivePrint <- function(func) {
|
||||
reactive(function() {
|
||||
return(paste(capture.output(print(func())), collapse="\n"))
|
||||
renderPrint <- function(expr, env = parent.frame(), quoted = FALSE, func = NULL,
|
||||
width = getOption('width')) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPrint: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
markRenderFunction(verbatimTextOutput, function() {
|
||||
op <- options(width = width)
|
||||
on.exit(options(op), add = TRUE)
|
||||
paste(capture.output(func()), collapse = "\n")
|
||||
})
|
||||
}
|
||||
|
||||
#' 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
|
||||
#'
|
||||
#' 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
|
||||
#'
|
||||
#' 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
|
||||
#'
|
||||
#' The result of executing \code{func} will passed to \code{cat}, inside a
|
||||
#' \code{\link[utils]{capture.output}} call.
|
||||
#'
|
||||
#' @param func A function that returns an R object that can be used as an
|
||||
#'
|
||||
#' @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 func A function that returns an R object that can be used as an
|
||||
#' argument to \code{cat}.(deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @seealso \code{\link{renderPrint}} for capturing the print output of a
|
||||
#' function, rather than the returned text value.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#' @export
|
||||
renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderText: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
markRenderFunction(textOutput, function() {
|
||||
value <- func()
|
||||
return(paste(capture.output(cat(value)), collapse="\n"))
|
||||
})
|
||||
}
|
||||
|
||||
#' UI Output
|
||||
#'
|
||||
#' \bold{Experimental feature.} Makes a reactive version of a function that
|
||||
#' generates 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}}).
|
||||
#'
|
||||
#' @param expr An expression that returns a Shiny tag object, \code{\link{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
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' or a list of such objects (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @seealso conditionalPanel
|
||||
#'
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' output$moreControls <- renderUI({
|
||||
#' list(
|
||||
#'
|
||||
#' )
|
||||
#' })
|
||||
#' }
|
||||
renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderUI: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
markRenderFunction(uiOutput, function(shinysession, name, ...) {
|
||||
result <- func()
|
||||
if (is.null(result) || length(result) == 0)
|
||||
return(NULL)
|
||||
|
||||
result <- takeSingletons(result, shinysession$singletons, desingleton=FALSE)$ui
|
||||
result <- surroundSingletons(result)
|
||||
dependencies <- lapply(resolveDependencies(findDependencies(result)),
|
||||
createWebDependency)
|
||||
names(dependencies) <- NULL
|
||||
|
||||
# renderTags returns a list with head, singletons, and html
|
||||
output <- list(
|
||||
html = doRenderTags(result),
|
||||
deps = dependencies
|
||||
)
|
||||
|
||||
return(output)
|
||||
})
|
||||
}
|
||||
|
||||
#' File Downloads
|
||||
#'
|
||||
#' Allows content from the Shiny application to be made available to the user as
|
||||
#' 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
|
||||
#' 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
|
||||
#' 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.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # In server.R:
|
||||
#' output$downloadData <- downloadHandler(
|
||||
#' filename = function() {
|
||||
#' paste('data-', Sys.Date(), '.csv', sep='')
|
||||
#' },
|
||||
#' content = function(file) {
|
||||
#' write.csv(data, file)
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' # In ui.R:
|
||||
#' downloadLink('downloadData', 'Download')
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
downloadHandler <- function(filename, content, contentType=NA) {
|
||||
return(markRenderFunction(downloadButton, function(shinysession, name, ...) {
|
||||
shinysession$registerDownload(name, filename, contentType, content)
|
||||
}))
|
||||
}
|
||||
|
||||
#' Table output with the JavaScript library DataTables
|
||||
#'
|
||||
#' Makes a reactive version of the given function that returns a data frame (or
|
||||
#' matrix), which will be rendered with the DataTables library. Paging,
|
||||
#' 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{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.
|
||||
#' @param expr An expression that returns a data frame or a matrix.
|
||||
#' @param options A list of initialization options to be passed to DataTables,
|
||||
#' or a function to return such a list.
|
||||
#' @param searchDelay The delay for searching, in milliseconds (to avoid too
|
||||
#' 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/extras/}).
|
||||
#' @references \url{http://datatables.net}
|
||||
#' @export
|
||||
#' @inheritParams renderPlot
|
||||
#' @examples # pass a callback function to DataTables using I()
|
||||
#' renderDataTable(iris,
|
||||
#' options = list(
|
||||
#' iDisplayLength = 5,
|
||||
#' fnInitComplete = I("function(oSettings, json) {alert('Done.');}")
|
||||
#' )
|
||||
#' )
|
||||
renderDataTable <- function(expr, options = NULL, searchDelay = 500,
|
||||
callback = 'function(oTable) {}',
|
||||
env = parent.frame(), quoted = FALSE) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
markRenderFunction(dataTableOutput, function(shinysession, name, ...) {
|
||||
res <- checkAsIs(if (is.function(options)) options() else options)
|
||||
data <- func()
|
||||
if (length(dim(data)) != 2) return() # expects a rectangular data object
|
||||
action <- shinysession$registerDataObj(name, data, dataTablesJSON)
|
||||
list(
|
||||
colnames = colnames(data), action = action, options = res$options,
|
||||
evalOptions = if (length(res$eval)) I(res$eval), searchDelay = searchDelay,
|
||||
callback = paste(callback, collapse = '\n')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
# Deprecated functions ------------------------------------------------------
|
||||
|
||||
#' Plot output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderPlot}}.
|
||||
#' @param func A function.
|
||||
#' @param width Width.
|
||||
#' @param height Height.
|
||||
#' @param ... Other arguments to pass on.
|
||||
#' @export
|
||||
reactivePlot <- function(func, width='auto', height='auto', ...) {
|
||||
shinyDeprecated(new="renderPlot")
|
||||
renderPlot({ func() }, width=width, height=height, ...)
|
||||
}
|
||||
|
||||
#' Table output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderTable}}.
|
||||
#' @param func A function.
|
||||
#' @param ... Other arguments to pass on.
|
||||
#' @export
|
||||
reactiveTable <- function(func, ...) {
|
||||
shinyDeprecated(new="renderTable")
|
||||
renderTable({ func() })
|
||||
}
|
||||
|
||||
#' Print output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderPrint}}.
|
||||
#' @param func A function.
|
||||
#' @export
|
||||
reactivePrint <- function(func) {
|
||||
shinyDeprecated(new="renderPrint")
|
||||
renderPrint({ func() })
|
||||
}
|
||||
|
||||
#' UI output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderUI}}.
|
||||
#' @param func A function.
|
||||
#' @export
|
||||
reactiveUI <- function(func) {
|
||||
shinyDeprecated(new="renderUI")
|
||||
renderUI({ func() })
|
||||
}
|
||||
|
||||
#' Text output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderText}}.
|
||||
#' @param func A function.
|
||||
#' @export
|
||||
reactiveText <- function(func) {
|
||||
reactive(function() {
|
||||
return(paste(capture.output(cat(func())), collapse="\n"))
|
||||
})
|
||||
}
|
||||
shinyDeprecated(new="renderText")
|
||||
renderText({ func() })
|
||||
}
|
||||
|
||||
162
R/showcase.R
Normal file
162
R/showcase.R
Normal file
@@ -0,0 +1,162 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
# Given the name of a license, return the appropriate link HTML for the
|
||||
# license, which may just be the name of the license if the name is
|
||||
# unrecognized.
|
||||
#
|
||||
# Recognizes the 'standard' set of licenses used for R packages
|
||||
# (see http://cran.r-project.org/doc/manuals/R-exts.html)
|
||||
licenseLink <- function(licenseName) {
|
||||
licenses <- list(
|
||||
"GPL-2" = "https://gnu.org/licenses/gpl-2.0.txt",
|
||||
"GPL-3" = "https://gnu.org/licenses/gpl-3.0.txt",
|
||||
"LGPL-3" = "https://www.gnu.org/licenses/lgpl-3.0.txt",
|
||||
"LGPL-2" = "http://www.gnu.org/licenses/old-licenses/lgpl-2.0.txt",
|
||||
"LGPL-2.1" = "http://www.gnu.org/licenses/lgpl-2.1.txt",
|
||||
"AGPL-3" = "http://www.gnu.org/licenses/agpl-3.0.txt",
|
||||
"Artistic-2.0" = "http://www.r-project.org/Licenses/Artistic-2.0",
|
||||
"BSD_2_clause" = "http://www.r-project.org/Licenses/BSD_2_clause",
|
||||
"BSD_3_clause" = "http://www.r-project.org/Licenses/BSD_3_clause",
|
||||
"MIT" = "http://www.r-project.org/Licenses/MIT")
|
||||
if (exists(licenseName, where = licenses)) {
|
||||
tags$a(href=licenses[[licenseName]], licenseName)
|
||||
} else {
|
||||
licenseName
|
||||
}
|
||||
}
|
||||
|
||||
# Returns tags containing showcase directives intended for the <HEAD> of the
|
||||
# document.
|
||||
showcaseHead <- function() {
|
||||
|
||||
deps <- list(
|
||||
htmlDependency("jqueryui", "1.10.4", c(href="shared/jqueryui/1.10.4"),
|
||||
script = "jquery-ui.min.js"),
|
||||
htmlDependency("showdown", "0.3.1", c(href="shared/showdown/compressed"),
|
||||
script = "showdown.js"),
|
||||
htmlDependency("font-awesome", "4.0.3", c(href="shared/font-awesome"),
|
||||
stylesheet = "css/font-awesome.min.css"),
|
||||
htmlDependency("highlight.js", "6.2", c(href="shared/highlight"),
|
||||
script = "highlight.pack.js")
|
||||
)
|
||||
|
||||
mdfile <- file.path.ci(getwd(), 'Readme.md')
|
||||
html <- with(tags, tagList(
|
||||
script(src="shared/shiny-showcase.js"),
|
||||
link(rel="stylesheet", type="text/css",
|
||||
href="shared/highlight/rstudio.css"),
|
||||
link(rel="stylesheet", type="text/css",
|
||||
href="shared/shiny-showcase.css"),
|
||||
if (file.exists(mdfile))
|
||||
script(type="text/markdown", id="showcase-markdown-content",
|
||||
paste(readLines(mdfile, warn = FALSE), collapse="\n"))
|
||||
else ""
|
||||
))
|
||||
|
||||
return(attachDependencies(html, deps))
|
||||
}
|
||||
|
||||
# Returns tags containing the application metadata (title and author) in
|
||||
# showcase mode.
|
||||
appMetadata <- function(desc) {
|
||||
cols <- colnames(desc)
|
||||
if ("Title" %in% cols)
|
||||
with(tags, h4(class="muted shiny-showcase-apptitle", desc[1,"Title"],
|
||||
if ("Author" %in% cols) small(
|
||||
br(), "by",
|
||||
if ("AuthorUrl" %in% cols)
|
||||
a(href=desc[1,"AuthorUrl"], class="shiny-showcase-appauthor",
|
||||
desc[1,"Author"])
|
||||
else
|
||||
desc[1,"Author"],
|
||||
if ("AuthorEmail" %in% cols)
|
||||
a(href=paste("mailto:", desc[1,"AuthorEmail"], sep = ''),
|
||||
class="shiny-showcase-appauthoreemail",
|
||||
desc[1,"AuthorEmail"])
|
||||
else "")
|
||||
else ""))
|
||||
else ""
|
||||
}
|
||||
|
||||
# Returns tags containing the application's code in Bootstrap-style tabs in
|
||||
# showcase mode.
|
||||
showcaseCodeTabs <- function(codeLicense) {
|
||||
rFiles <- list.files(pattern = "\\.[rR]$")
|
||||
with(tags, div(id="showcase-code-tabs",
|
||||
a(id="showcase-code-position-toggle",
|
||||
class="btn btn-default btn-small",
|
||||
onclick="toggleCodePosition()",
|
||||
i(class="fa fa-level-up", "show with app")),
|
||||
ul(class="nav nav-tabs",
|
||||
lapply(rFiles, function(rFile) {
|
||||
li(class=if (tolower(rFile) == "server.r") "active" else "",
|
||||
a(href=paste("#", gsub(".", "_", rFile, fixed=TRUE),
|
||||
"_code", sep=""),
|
||||
"data-toggle"="tab", rFile))
|
||||
})),
|
||||
div(class="tab-content", id="showcase-code-content",
|
||||
lapply(rFiles, function(rFile) {
|
||||
div(class=paste("tab-pane",
|
||||
if (tolower(rFile) == "server.r") " active" else "",
|
||||
sep=""),
|
||||
id=paste(gsub(".", "_", rFile, fixed=TRUE),
|
||||
"_code", sep=""),
|
||||
pre(class="shiny-code",
|
||||
# we need to prevent the indentation of <code> ... </code>
|
||||
HTML(format(tags$code(
|
||||
class="language-r",
|
||||
paste(readLines(file.path.ci(getwd(), rFile), warn=FALSE),
|
||||
collapse="\n")
|
||||
), indent = FALSE))))
|
||||
})),
|
||||
codeLicense))
|
||||
}
|
||||
|
||||
# Returns tags containing the showcase application information (readme and
|
||||
# code).
|
||||
showcaseAppInfo <- function() {
|
||||
descfile <- file.path.ci(getwd(), "DESCRIPTION")
|
||||
hasDesc <- file.exists(descfile)
|
||||
readmemd <- file.path.ci(getwd(), "Readme.md")
|
||||
hasReadme <- file.exists(readmemd)
|
||||
if (hasDesc) {
|
||||
desc <- read.dcf(descfile)
|
||||
}
|
||||
with(tags,
|
||||
div(class="container-fluid shiny-code-container well",
|
||||
id="showcase-well",
|
||||
div(class="row-fluid",
|
||||
if (hasDesc || hasReadme) {
|
||||
div(id="showcase-app-metadata", class="span4",
|
||||
if (hasDesc) appMetadata(desc) else "",
|
||||
if (hasReadme) div(id="readme-md"))
|
||||
} else "",
|
||||
div(id="showcase-code-inline",
|
||||
class=if (hasReadme || hasDesc) "span8" else "span10 offset1",
|
||||
showcaseCodeTabs(
|
||||
if (hasDesc && "License" %in% colnames(desc)) {
|
||||
small(class="showcase-code-license muted",
|
||||
"Code license: ",
|
||||
licenseLink(desc[1,"License"]))
|
||||
} else "")))))
|
||||
}
|
||||
|
||||
|
||||
# Returns the body of the showcase document, given the HTML it should wrap.
|
||||
showcaseBody <- function(htmlBody) {
|
||||
with(tags, tagList(
|
||||
table(id="showcase-app-code",
|
||||
tr(td(id="showcase-app-container",
|
||||
class="showcase-app-container-expanded",
|
||||
HTML(htmlBody),
|
||||
td(id="showcase-sxs-code",
|
||||
class="showcase-sxs-code-collapsed")))),
|
||||
showcaseAppInfo()))
|
||||
}
|
||||
|
||||
# Sets the defaults for showcase mode (for app boot).
|
||||
setShowcaseDefault <- function(showcaseDefault) {
|
||||
.globals$showcaseDefault <- showcaseDefault
|
||||
.globals$showcaseOverride <- as.logical(showcaseDefault)
|
||||
}
|
||||
97
R/slider.R
97
R/slider.R
@@ -3,19 +3,17 @@ hasDecimals <- function(value) {
|
||||
return (!identical(value, truncatedValue))
|
||||
}
|
||||
|
||||
#' Animation Options
|
||||
#'
|
||||
#' Creates an options object for customizing animations for \link{sliderInput}.
|
||||
#'
|
||||
#' @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 \code{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
|
||||
#' @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.
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
animationOptions <- function(interval=1000,
|
||||
loop=FALSE,
|
||||
@@ -30,35 +28,35 @@ animationOptions <- function(interval=1000,
|
||||
# Create a new slider control (list of slider input element and the script
|
||||
# tag used to configure it). This is a lower level control that should
|
||||
# be wrapped in an "input" construct (e.g. sliderInput in bootstrap.R)
|
||||
#
|
||||
#
|
||||
# this is a wrapper for: https://github.com/egorkhmelev/jslider
|
||||
# (www/shared/slider contains js, css, and img dependencies)
|
||||
# (www/shared/slider contains js, css, and img dependencies)
|
||||
slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
round=FALSE, format='#,##0.#####', locale='us',
|
||||
ticks=TRUE, animate=FALSE) {
|
||||
ticks=TRUE, animate=FALSE, width=NULL) {
|
||||
# validate inputId
|
||||
inputId <- as.character(inputId)
|
||||
if (!is.character(inputId))
|
||||
stop("inputId not specified")
|
||||
|
||||
|
||||
# validate numeric inputs
|
||||
if (!is.numeric(value) || !is.numeric(min) || !is.numeric(max))
|
||||
stop("min, max, amd value must all be numeric values")
|
||||
else if (min(value) < min)
|
||||
stop(paste("slider initial value", value,
|
||||
if (!is.numeric(value) || !is.numeric(min) || !is.numeric(max))
|
||||
stop("min, max, and value must all be numeric values")
|
||||
else if (min(value) < min)
|
||||
stop(paste("slider initial value", value,
|
||||
"is less than the specified minimum"))
|
||||
else if (max(value) > max)
|
||||
stop(paste("slider initial value", value,
|
||||
else if (max(value) > max)
|
||||
stop(paste("slider initial value", value,
|
||||
"is greater than the specified maximum"))
|
||||
else if (min > max)
|
||||
else if (min > max)
|
||||
stop(paste("slider maximum is greater than minimum"))
|
||||
else if (!is.null(step)) {
|
||||
if (!is.numeric(step))
|
||||
if (!is.numeric(step))
|
||||
stop("step is not a numeric value")
|
||||
if (step > (max - min))
|
||||
if (step > (max - min))
|
||||
stop("step is greater than range")
|
||||
}
|
||||
|
||||
|
||||
# step
|
||||
range <- max - min
|
||||
if (is.null(step)) {
|
||||
@@ -68,9 +66,9 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
else
|
||||
step = 1
|
||||
}
|
||||
|
||||
|
||||
# Default state is to not have ticks
|
||||
if (identical(ticks, T)) {
|
||||
if (identical(ticks, TRUE)) {
|
||||
# Automatic ticks
|
||||
tickCount <- (range / step) + 1
|
||||
if (tickCount <= 26)
|
||||
@@ -99,35 +97,36 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
else {
|
||||
ticks <- NULL
|
||||
}
|
||||
|
||||
|
||||
# build slider
|
||||
sliderFragment <- list(
|
||||
singleton(
|
||||
tags$head(
|
||||
tags$link(rel="stylesheet",
|
||||
type="text/css",
|
||||
href="shared/slider/css/jquery.slider.min.css"),
|
||||
|
||||
tags$script(src="shared/slider/js/jquery.slider.min.js")
|
||||
)
|
||||
),
|
||||
tags$input(id=inputId, type="slider",
|
||||
name=inputId, value=paste(value, collapse=';'), class="jslider",
|
||||
'data-from'=min, 'data-to'=max, 'data-step'=step,
|
||||
'data-skin'='plastic', 'data-round'=round, 'data-locale'=locale,
|
||||
'data-format'=format, 'data-scale'=ticks,
|
||||
'data-smooth'=FALSE)
|
||||
dep <- htmlDependency("jslider", "1", c(href="shared/slider"),
|
||||
script = "js/jquery.slider.min.js",
|
||||
stylesheet = "css/jquery.slider.min.css"
|
||||
)
|
||||
|
||||
if (identical(animate, T))
|
||||
sliderFragment <- list(
|
||||
attachDependencies(
|
||||
tags$input(
|
||||
id=inputId, type="slider",
|
||||
name=inputId, value=paste(value, collapse=';'), class="jslider",
|
||||
'data-from'=min, 'data-to'=max, 'data-step'=step,
|
||||
'data-skin'='plastic', 'data-round'=round, 'data-locale'=locale,
|
||||
'data-format'=format, 'data-scale'=ticks,
|
||||
'data-smooth'=FALSE,
|
||||
'data-width'=validateCssUnit(width)
|
||||
),
|
||||
dep
|
||||
)
|
||||
)
|
||||
|
||||
if (identical(animate, TRUE))
|
||||
animate <- animationOptions()
|
||||
|
||||
if (!is.null(animate) && !identical(animate, F)) {
|
||||
|
||||
if (!is.null(animate) && !identical(animate, FALSE)) {
|
||||
if (is.null(animate$playButton))
|
||||
animate$playButton <- 'Play'
|
||||
if (is.null(animate$pauseButton))
|
||||
animate$pauseButton <- 'Pause'
|
||||
|
||||
|
||||
sliderFragment[[length(sliderFragment)+1]] <-
|
||||
tags$div(class='slider-animate-container',
|
||||
tags$a(href='#',
|
||||
@@ -138,6 +137,6 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
tags$span(class='play', animate$playButton),
|
||||
tags$span(class='pause', animate$pauseButton)))
|
||||
}
|
||||
|
||||
return(sliderFragment)
|
||||
|
||||
return(tagList(sliderFragment))
|
||||
}
|
||||
|
||||
364
R/tags.R
364
R/tags.R
@@ -1,364 +0,0 @@
|
||||
|
||||
|
||||
htmlEscape <- local({
|
||||
.htmlSpecials <- list(
|
||||
`&` = '&',
|
||||
`<` = '<',
|
||||
`>` = '>'
|
||||
)
|
||||
.htmlSpecialsPattern <- paste(names(.htmlSpecials), collapse='|')
|
||||
.htmlSpecialsAttrib <- c(
|
||||
.htmlSpecials,
|
||||
`'` = ''',
|
||||
`"` = '"',
|
||||
`\r` = ' ',
|
||||
`\n` = ' '
|
||||
)
|
||||
.htmlSpecialsPatternAttrib <- paste(names(.htmlSpecialsAttrib), collapse='|')
|
||||
|
||||
function(text, attribute=T) {
|
||||
pattern <- if(attribute)
|
||||
.htmlSpecialsPatternAttrib
|
||||
else
|
||||
.htmlSpecialsPattern
|
||||
|
||||
# Short circuit in the common case that there's nothing to escape
|
||||
if (!grepl(pattern, text))
|
||||
return(text)
|
||||
|
||||
specials <- if(attribute)
|
||||
.htmlSpecialsAttrib
|
||||
else
|
||||
.htmlSpecials
|
||||
|
||||
for (chr in names(specials)) {
|
||||
text <- gsub(chr, specials[[chr]], text, fixed=T)
|
||||
}
|
||||
|
||||
return(text)
|
||||
}
|
||||
})
|
||||
|
||||
isTag <- function(x) {
|
||||
inherits(x, "shiny.tag")
|
||||
}
|
||||
|
||||
#' @S3method print shiny.tag
|
||||
print.shiny.tag <- function(x, ...) {
|
||||
print(as.character(x), ...)
|
||||
}
|
||||
|
||||
#' @S3method format shiny.tag
|
||||
format.shiny.tag <- function(x, ...) {
|
||||
as.character.shiny.tag(x)
|
||||
}
|
||||
|
||||
#' @S3method as.character shiny.tag
|
||||
as.character.shiny.tag <- function(x, ...) {
|
||||
f = file()
|
||||
on.exit(close(f))
|
||||
textWriter <- function(text) {
|
||||
cat(text, file=f)
|
||||
}
|
||||
tagWrite(x, textWriter)
|
||||
return(HTML(paste(readLines(f), collapse='\n')))
|
||||
}
|
||||
|
||||
normalizeText <- function(text) {
|
||||
if (!is.null(attr(text, "html")))
|
||||
text
|
||||
else
|
||||
htmlEscape(text, attribute=FALSE)
|
||||
|
||||
}
|
||||
|
||||
#' @export
|
||||
tagAppendChild <- function(tag, child) {
|
||||
tag$children[[length(tag$children)+1]] <- child
|
||||
tag
|
||||
}
|
||||
|
||||
#' @export
|
||||
tag <- function(`_tag_name`, varArgs) {
|
||||
|
||||
# create basic tag data structure
|
||||
tag <- list()
|
||||
class(tag) <- "shiny.tag"
|
||||
tag$name <- `_tag_name`
|
||||
tag$attribs <- list()
|
||||
tag$children <- list()
|
||||
|
||||
# process varArgs
|
||||
varArgsNames <- names(varArgs)
|
||||
if (is.null(varArgsNames))
|
||||
varArgsNames <- character(length=length(varArgs))
|
||||
|
||||
if (length(varArgsNames) > 0) {
|
||||
for (i in 1:length(varArgsNames)) {
|
||||
# save name and value
|
||||
name <- varArgsNames[[i]]
|
||||
value <- varArgs[[i]]
|
||||
|
||||
# process attribs
|
||||
if (nzchar(name))
|
||||
tag$attribs[[name]] <- value
|
||||
|
||||
# process child tags
|
||||
else if (isTag(value)) {
|
||||
tag$children[[length(tag$children)+1]] <- value
|
||||
}
|
||||
|
||||
# recursively process lists of children
|
||||
else if (is.list(value)) {
|
||||
|
||||
tagAppendChildren <- function(tag, children) {
|
||||
for(child in children) {
|
||||
if (isTag(child))
|
||||
tag <- tagAppendChild(tag, child)
|
||||
else if (is.list(child))
|
||||
tag <- tagAppendChildren(tag, child)
|
||||
else if (is.character(child))
|
||||
tag <- tagAppendChild(tag, child)
|
||||
else
|
||||
tag <- tagAppendChild(tag, as.character(child))
|
||||
}
|
||||
return (tag)
|
||||
}
|
||||
|
||||
tag <- tagAppendChildren(tag, value)
|
||||
}
|
||||
|
||||
# add text
|
||||
else if (is.character(value)) {
|
||||
tag <- tagAppendChild(tag, value)
|
||||
}
|
||||
|
||||
# everything else treated as text
|
||||
else {
|
||||
tag <- tagAppendChild(tag, as.character(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# return the tag
|
||||
return (tag)
|
||||
}
|
||||
|
||||
tagWriteChildren <- function(tag, textWriter, indent, context) {
|
||||
for (child in tag$children) {
|
||||
if (isTag(child)) {
|
||||
tagWrite(child, textWriter, indent, context)
|
||||
}
|
||||
else {
|
||||
# first call optional filter -- exit function if it returns false
|
||||
if (is.null(context) || is.null(context$filter) || context$filter(child)) {
|
||||
child <- normalizeText(child)
|
||||
indentText <- paste(rep(" ", indent*3), collapse="")
|
||||
textWriter(paste(indentText, child, "\n", sep=""))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tagWrite <- function(tag, textWriter, indent=0, context = NULL) {
|
||||
|
||||
# optionally process a list of tags
|
||||
if (!isTag(tag) && is.list(tag)) {
|
||||
sapply(tag, function(t) tagWrite(t, textWriter, indent, context))
|
||||
return (NULL)
|
||||
}
|
||||
|
||||
# first call optional filter -- exit function if it returns false
|
||||
if (!is.null(context) && !is.null(context$filter) && !context$filter(tag))
|
||||
return (NULL)
|
||||
|
||||
# compute indent text
|
||||
indentText <- paste(rep(" ", indent*3), collapse="")
|
||||
|
||||
# write tag name
|
||||
textWriter(paste(indentText, "<", tag$name, sep=""))
|
||||
|
||||
# write attributes
|
||||
for (attrib in names(tag$attribs)) {
|
||||
attribValue <- tag$attribs[[attrib]]
|
||||
if (!is.na(attribValue)) {
|
||||
if (is.logical(attribValue))
|
||||
attribValue <- tolower(attribValue)
|
||||
text <- htmlEscape(attribValue, attribute=TRUE)
|
||||
textWriter(paste(" ", attrib,"=\"", text, "\"", sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter(paste(" ", attrib, sep=""))
|
||||
}
|
||||
}
|
||||
|
||||
# write any children
|
||||
if (length(tag$children) > 0) {
|
||||
|
||||
# special case for a single child text node (skip newlines and indentation)
|
||||
if ((length(tag$children) == 1) && is.character(tag$children[[1]]) ) {
|
||||
if (is.null(context) || is.null(context$filter)
|
||||
|| context$filter(tag$children[[1]])) {
|
||||
text <- normalizeText(tag$children[[1]])
|
||||
textWriter(paste(">", text, "</", tag$name, ">\n", sep=""))
|
||||
}
|
||||
}
|
||||
else {
|
||||
textWriter(">\n")
|
||||
tagWriteChildren(tag, textWriter, indent+1, context)
|
||||
textWriter(paste(indentText, "</", tag$name, ">\n", sep=""))
|
||||
}
|
||||
}
|
||||
else {
|
||||
# only self-close void elements
|
||||
# (see: http://dev.w3.org/html5/spec/single-page.html#void-elements)
|
||||
if (tag$name %in% c("area", "base", "br", "col", "command", "embed", "hr",
|
||||
"img", "input", "keygen", "link", "meta", "param",
|
||||
"source", "track", "wbr")) {
|
||||
textWriter("/>\n")
|
||||
}
|
||||
else {
|
||||
textWriter(paste("></", tag$name, ">\n", sep=""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# environment used to store all available tags
|
||||
#' @export
|
||||
tags <- new.env()
|
||||
tags$a <- function(...) tag("a", list(...))
|
||||
tags$abbr <- function(...) tag("abbr", list(...))
|
||||
tags$address <- function(...) tag("address", list(...))
|
||||
tags$area <- function(...) tag("area", list(...))
|
||||
tags$article <- function(...) tag("article", list(...))
|
||||
tags$aside <- function(...) tag("aside", list(...))
|
||||
tags$audio <- function(...) tag("audio", list(...))
|
||||
tags$b <- function(...) tag("b", list(...))
|
||||
tags$base <- function(...) tag("base", list(...))
|
||||
tags$bdi <- function(...) tag("bdi", list(...))
|
||||
tags$bdo <- function(...) tag("bdo", list(...))
|
||||
tags$blockquote <- function(...) tag("blockquote", list(...))
|
||||
tags$body <- function(...) tag("body", list(...))
|
||||
tags$br <- function(...) tag("br", list(...))
|
||||
tags$button <- function(...) tag("button", list(...))
|
||||
tags$canvas <- function(...) tag("canvas", list(...))
|
||||
tags$caption <- function(...) tag("caption", list(...))
|
||||
tags$cite <- function(...) tag("cite", list(...))
|
||||
tags$code <- function(...) tag("code", list(...))
|
||||
tags$col <- function(...) tag("col", list(...))
|
||||
tags$colgroup <- function(...) tag("colgroup", list(...))
|
||||
tags$command <- function(...) tag("command", list(...))
|
||||
tags$data <- function(...) tag("data", list(...))
|
||||
tags$datalist <- function(...) tag("datalist", list(...))
|
||||
tags$dd <- function(...) tag("dd", list(...))
|
||||
tags$del <- function(...) tag("del", list(...))
|
||||
tags$details <- function(...) tag("details", list(...))
|
||||
tags$dfn <- function(...) tag("dfn", list(...))
|
||||
tags$div <- function(...) tag("div", list(...))
|
||||
tags$dl <- function(...) tag("dl", list(...))
|
||||
tags$dt <- function(...) tag("dt", list(...))
|
||||
tags$em <- function(...) tag("em", list(...))
|
||||
tags$embed <- function(...) tag("embed", list(...))
|
||||
tags$eventsource <- function(...) tag("eventsource", list(...))
|
||||
tags$fieldset <- function(...) tag("fieldset", list(...))
|
||||
tags$figcaption <- function(...) tag("figcaption", list(...))
|
||||
tags$figure <- function(...) tag("figure", list(...))
|
||||
tags$footer <- function(...) tag("footer", list(...))
|
||||
tags$form <- function(...) tag("form", list(...))
|
||||
tags$h1 <- function(...) tag("h1", list(...))
|
||||
tags$h2 <- function(...) tag("h2", list(...))
|
||||
tags$h3 <- function(...) tag("h3", list(...))
|
||||
tags$h4 <- function(...) tag("h4", list(...))
|
||||
tags$h5 <- function(...) tag("h5", list(...))
|
||||
tags$h6 <- function(...) tag("h6", list(...))
|
||||
tags$head <- function(...) tag("head", list(...))
|
||||
tags$header <- function(...) tag("header", list(...))
|
||||
tags$hgroup <- function(...) tag("hgroup", list(...))
|
||||
tags$hr <- function(...) tag("hr", list(...))
|
||||
tags$html <- function(...) tag("html", list(...))
|
||||
tags$i <- function(...) tag("i", list(...))
|
||||
tags$iframe <- function(...) tag("iframe", list(...))
|
||||
tags$img <- function(...) tag("img", list(...))
|
||||
tags$input <- function(...) tag("input", list(...))
|
||||
tags$ins <- function(...) tag("ins", list(...))
|
||||
tags$kbd <- function(...) tag("kbd", list(...))
|
||||
tags$keygen <- function(...) tag("keygen", list(...))
|
||||
tags$label <- function(...) tag("label", list(...))
|
||||
tags$legend <- function(...) tag("legend", list(...))
|
||||
tags$li <- function(...) tag("li", list(...))
|
||||
tags$link <- function(...) tag("link", list(...))
|
||||
tags$mark <- function(...) tag("mark", list(...))
|
||||
tags$map <- function(...) tag("map", list(...))
|
||||
tags$menu <- function(...) tag("menu", list(...))
|
||||
tags$meta <- function(...) tag("meta", list(...))
|
||||
tags$meter <- function(...) tag("meter", list(...))
|
||||
tags$nav <- function(...) tag("nav", list(...))
|
||||
tags$noscript <- function(...) tag("noscript", list(...))
|
||||
tags$object <- function(...) tag("object", list(...))
|
||||
tags$ol <- function(...) tag("ol", list(...))
|
||||
tags$optgroup <- function(...) tag("optgroup", list(...))
|
||||
tags$option <- function(...) tag("option", list(...))
|
||||
tags$output <- function(...) tag("output", list(...))
|
||||
tags$p <- function(...) tag("p", list(...))
|
||||
tags$param <- function(...) tag("param", list(...))
|
||||
tags$pre <- function(...) tag("pre", list(...))
|
||||
tags$progress <- function(...) tag("progress", list(...))
|
||||
tags$q <- function(...) tag("q", list(...))
|
||||
tags$ruby <- function(...) tag("ruby", list(...))
|
||||
tags$rp <- function(...) tag("rp", list(...))
|
||||
tags$rt <- function(...) tag("rt", list(...))
|
||||
tags$s <- function(...) tag("s", list(...))
|
||||
tags$samp <- function(...) tag("samp", list(...))
|
||||
tags$script <- function(...) tag("script", list(...))
|
||||
tags$section <- function(...) tag("section", list(...))
|
||||
tags$select <- function(...) tag("select", list(...))
|
||||
tags$small <- function(...) tag("small", list(...))
|
||||
tags$source <- function(...) tag("source", list(...))
|
||||
tags$span <- function(...) tag("span", list(...))
|
||||
tags$strong <- function(...) tag("strong", list(...))
|
||||
tags$style <- function(...) tag("style", list(...))
|
||||
tags$sub <- function(...) tag("sub", list(...))
|
||||
tags$summary <- function(...) tag("summary", list(...))
|
||||
tags$sup <- function(...) tag("sup", list(...))
|
||||
tags$table <- function(...) tag("table", list(...))
|
||||
tags$tbody <- function(...) tag("tbody", list(...))
|
||||
tags$td <- function(...) tag("td", list(...))
|
||||
tags$textarea <- function(...) tag("textarea", list(...))
|
||||
tags$tfoot <- function(...) tag("tfoot", list(...))
|
||||
tags$th <- function(...) tag("th", list(...))
|
||||
tags$thead <- function(...) tag("thead", list(...))
|
||||
tags$time <- function(...) tag("time", list(...))
|
||||
tags$title <- function(...) tag("title", list(...))
|
||||
tags$tr <- function(...) tag("tr", list(...))
|
||||
tags$track <- function(...) tag("track", list(...))
|
||||
tags$u <- function(...) tag("u", list(...))
|
||||
tags$ul <- function(...) tag("ul", list(...))
|
||||
tags$var <- function(...) tag("var", list(...))
|
||||
tags$video <- function(...) tag("video", list(...))
|
||||
tags$wbr <- function(...) tag("wbr", list(...))
|
||||
|
||||
#' Mark Characters as HTML
|
||||
#'
|
||||
#' Marks the given text as HTML, which means the \link{tag} functions will know
|
||||
#' not to perform HTML escaping on it.
|
||||
#'
|
||||
#' @param text The text value to mark with HTML
|
||||
#' @param ... Any additional values to be converted to character and
|
||||
#' concatenated together
|
||||
#' @return The same value, but marked as HTML.
|
||||
#'
|
||||
#' @examples
|
||||
#' el <- div(HTML("I like <u>turtles</u>"))
|
||||
#' cat(as.character(el))
|
||||
#'
|
||||
#' @export
|
||||
HTML <- function(text, ...) {
|
||||
htmlText <- c(text, as.character(list(...)))
|
||||
htmlText <- paste(htmlText, collapse=" ")
|
||||
attr(htmlText, "html") <- TRUE
|
||||
htmlText
|
||||
}
|
||||
|
||||
|
||||
191
R/tar.R
Normal file
191
R/tar.R
Normal file
@@ -0,0 +1,191 @@
|
||||
# This file was pulled from the R code base as of
|
||||
# Thursday, November 22, 2012 at 6:24:55 AM UTC
|
||||
# and edited to remove everything but the copyright
|
||||
# header and untar2, and to make untar2 more tolerant
|
||||
# of the 'x' and 'g' extended block indicators, the
|
||||
# latter of which is used in tar files generated by
|
||||
# GitHub.
|
||||
|
||||
|
||||
# File src/library/utils/R/tar.R
|
||||
# Part of the R package, http://www.R-project.org
|
||||
#
|
||||
# Copyright (C) 1995-2012 The R Core Team
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# A copy of the GNU General Public License is available at
|
||||
# http://www.r-project.org/Licenses/
|
||||
|
||||
untar2 <- function(tarfile, files = NULL, list = FALSE, exdir = ".")
|
||||
{
|
||||
getOct <- function(x, offset, len)
|
||||
{
|
||||
x <- 0L
|
||||
for(i in offset + seq_len(len)) {
|
||||
z <- block[i]
|
||||
if(!as.integer(z)) break; # terminate on nul
|
||||
switch(rawToChar(z),
|
||||
" " = {},
|
||||
"0"=,"1"=,"2"=,"3"=,"4"=,"5"=,"6"=,"7"=
|
||||
{x <- 8*x + (as.integer(z)-48)},
|
||||
stop("invalid octal digit")
|
||||
)
|
||||
}
|
||||
x
|
||||
}
|
||||
|
||||
mydir.create <- function(path, ...) {
|
||||
## for Windows' sake
|
||||
path <- sub("[\\/]$", "", path)
|
||||
if(file_test("-d", path)) return()
|
||||
if(!dir.create(path, showWarnings = TRUE, recursive = TRUE, ...))
|
||||
stop(gettextf("failed to create directory %s", sQuote(path)),
|
||||
domain = NA)
|
||||
}
|
||||
|
||||
warn1 <- character()
|
||||
|
||||
## A tar file is a set of 512 byte records,
|
||||
## a header record followed by file contents (zero-padded).
|
||||
## See http://en.wikipedia.org/wiki/Tar_%28file_format%29
|
||||
if(is.character(tarfile) && length(tarfile) == 1L) {
|
||||
con <- gzfile(path.expand(tarfile), "rb") # reads compressed formats
|
||||
on.exit(close(con))
|
||||
} else if(inherits(tarfile, "connection")) con <- tarfile
|
||||
else stop("'tarfile' must be a character string or a connection")
|
||||
if (!missing(exdir)) {
|
||||
mydir.create(exdir)
|
||||
od <- setwd(exdir)
|
||||
on.exit(setwd(od), add = TRUE)
|
||||
}
|
||||
contents <- character()
|
||||
llink <- lname <- NULL
|
||||
repeat{
|
||||
block <- readBin(con, "raw", n = 512L)
|
||||
if(!length(block)) break
|
||||
if(length(block) < 512L) stop("incomplete block on file")
|
||||
if(all(block == 0)) break
|
||||
ns <- max(which(block[1:100] > 0))
|
||||
name <- rawToChar(block[seq_len(ns)])
|
||||
magic <- rawToChar(block[258:262])
|
||||
if ((magic == "ustar") && block[346] > 0) {
|
||||
ns <- max(which(block[346:500] > 0))
|
||||
prefix <- rawToChar(block[345+seq_len(ns)])
|
||||
name <- file.path(prefix, name)
|
||||
}
|
||||
## mode zero-padded 8 bytes (including nul) at 101
|
||||
## Aargh: bsdtar has this one incorrectly with 6 bytes+space
|
||||
mode <- as.octmode(getOct(block, 100, 8))
|
||||
size <- getOct(block, 124, 12)
|
||||
ts <- getOct(block, 136, 12)
|
||||
ft <- as.POSIXct(as.numeric(ts), origin="1970-01-01", tz="UTC")
|
||||
csum <- getOct(block, 148, 8)
|
||||
block[149:156] <- charToRaw(" ")
|
||||
xx <- as.integer(block)
|
||||
checksum <- sum(xx) %% 2^24 # 6 bytes
|
||||
if(csum != checksum) {
|
||||
## try it with signed bytes.
|
||||
checksum <- sum(ifelse(xx > 127, xx - 128, xx)) %% 2^24 # 6 bytes
|
||||
if(csum != checksum)
|
||||
warning(gettextf("checksum error for entry '%s'", name),
|
||||
domain = NA)
|
||||
}
|
||||
type <- block[157L]
|
||||
ctype <- rawToChar(type)
|
||||
if(type == 0L || ctype == "0") {
|
||||
if(!is.null(lname)) {name <- lname; lname <- NULL}
|
||||
contents <- c(contents, name)
|
||||
remain <- size
|
||||
dothis <- !list
|
||||
if(dothis && length(files)) dothis <- name %in% files
|
||||
if(dothis) {
|
||||
mydir.create(dirname(name))
|
||||
out <- file(name, "wb")
|
||||
}
|
||||
for(i in seq_len(ceiling(size/512L))) {
|
||||
block <- readBin(con, "raw", n = 512L)
|
||||
if(length(block) < 512L)
|
||||
stop("incomplete block on file")
|
||||
if (dothis) {
|
||||
writeBin(block[seq_len(min(512L, remain))], out)
|
||||
remain <- remain - 512L
|
||||
}
|
||||
}
|
||||
if(dothis) {
|
||||
close(out)
|
||||
Sys.chmod(name, mode, FALSE) # override umask
|
||||
Sys.setFileTime(name, ft)
|
||||
}
|
||||
} else if(ctype %in% c("1", "2")) { # hard and symbolic links
|
||||
contents <- c(contents, name)
|
||||
ns <- max(which(block[158:257] > 0))
|
||||
name2 <- rawToChar(block[157L + seq_len(ns)])
|
||||
if(!is.null(lname)) {name <- lname; lname <- NULL}
|
||||
if(!is.null(llink)) {name2 <- llink; llink <- NULL}
|
||||
if(!list) {
|
||||
if(ctype == "1") {
|
||||
if (!file.link(name2, name)) { # will give a warning
|
||||
## link failed, so try a file copy
|
||||
if(file.copy(name2, name))
|
||||
warn1 <- c(warn1, "restoring hard link as a file copy")
|
||||
else
|
||||
warning(gettextf("failed to copy %s to %s", sQuote(name2), sQuote(name)), domain = NA)
|
||||
}
|
||||
} else {
|
||||
if(.Platform$OS.type == "windows") {
|
||||
## this will not work for links to dirs
|
||||
from <- file.path(dirname(name), name2)
|
||||
if (!file.copy(from, name))
|
||||
warning(gettextf("failed to copy %s to %s", sQuote(from), sQuote(name)), domain = NA)
|
||||
else
|
||||
warn1 <- c(warn1, "restoring symbolic link as a file copy")
|
||||
} else {
|
||||
if(!file.symlink(name2, name)) { # will give a warning
|
||||
## so try a file copy: will not work for links to dirs
|
||||
from <- file.path(dirname(name), name2)
|
||||
if (file.copy(from, name))
|
||||
warn1 <- c(warn1, "restoring symbolic link as a file copy")
|
||||
else
|
||||
warning(gettextf("failed to copy %s to %s", sQuote(from), sQuote(name)), domain = NA)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(ctype == "5") {
|
||||
contents <- c(contents, name)
|
||||
if(!list) {
|
||||
mydir.create(name)
|
||||
Sys.chmod(name, mode, TRUE) # FIXME: check result
|
||||
## no point is setting time, as dir will be populated later.
|
||||
}
|
||||
} else if(ctype %in% c("L", "K")) {
|
||||
## This is a GNU extension that should no longer be
|
||||
## in use, but it is.
|
||||
name_size <- 512L * ceiling(size/512L)
|
||||
block <- readBin(con, "raw", n = name_size)
|
||||
if(length(block) < name_size)
|
||||
stop("incomplete block on file")
|
||||
ns <- max(which(block > 0)) # size on file may or may not include final nul
|
||||
if(ctype == "L")
|
||||
lname <- rawToChar(block[seq_len(ns)])
|
||||
else
|
||||
llink <- rawToChar(block[seq_len(ns)])
|
||||
} else if(ctype %in% c("x", "g")) {
|
||||
readBin(con, "raw", n = 512L*ceiling(size/512L))
|
||||
} else stop("unsupported entry type ", sQuote(ctype))
|
||||
}
|
||||
if(length(warn1)) {
|
||||
warn1 <- unique(warn1)
|
||||
for (w in warn1) warning(w, domain = NA)
|
||||
}
|
||||
if(list) contents else invisible(0L)
|
||||
}
|
||||
23
R/timer.R
23
R/timer.R
@@ -15,20 +15,25 @@ TimerCallbacks <- setRefClass(
|
||||
initialize = function() {
|
||||
.nextId <<- 0L
|
||||
},
|
||||
clear = function() {
|
||||
.nextId <<- 0L
|
||||
.funcs$clear()
|
||||
.times <<- data.frame()
|
||||
},
|
||||
schedule = function(millis, func) {
|
||||
id <- .nextId
|
||||
.nextId <<- .nextId + 1L
|
||||
|
||||
|
||||
t <- now()
|
||||
|
||||
|
||||
# TODO: Horribly inefficient, use a heap instead
|
||||
.times <<- rbind(.times, data.frame(time=t+millis,
|
||||
scheduled=t,
|
||||
id=id))
|
||||
.times <<- .times[order(.times$time),]
|
||||
|
||||
|
||||
.funcs$set(as.character(id), func)
|
||||
|
||||
|
||||
return(id)
|
||||
},
|
||||
timeToNextEvent = function() {
|
||||
@@ -41,25 +46,25 @@ TimerCallbacks <- setRefClass(
|
||||
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())
|
||||
|
||||
|
||||
return(result)
|
||||
},
|
||||
executeElapsed = function() {
|
||||
elapsed <- takeElapsed()
|
||||
if (length(elapsed) == 0)
|
||||
return(F)
|
||||
|
||||
return(FALSE)
|
||||
|
||||
for (id in elapsed$id) {
|
||||
thisFunc <- .funcs$remove(as.character(id))
|
||||
# TODO: Catch exception, and...?
|
||||
# TODO: Detect NULL, and...?
|
||||
thisFunc()
|
||||
}
|
||||
return(T)
|
||||
return(TRUE)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
466
R/update-input.R
Normal file
466
R/update-input.R
Normal file
@@ -0,0 +1,466 @@
|
||||
#' Change the value of a text input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#'
|
||||
#' @seealso \code{\link{textInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' # This will change the value of input$inText, based on x
|
||||
#' updateTextInput(session, "inText", value = paste("New text", x))
|
||||
#'
|
||||
#' # Can also set the label, this time for input$inText2
|
||||
#' updateTextInput(session, "inText2",
|
||||
#' label = paste("New label", x),
|
||||
#' value = paste("New text", x))
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateTextInput <- function(session, inputId, label = NULL, value = NULL) {
|
||||
message <- dropNulls(list(label=label, value=value))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a checkbox input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#'
|
||||
#' @seealso \code{\link{checkboxInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # TRUE if input$controller is even, FALSE otherwise.
|
||||
#' x_even <- input$controller %% 2 == 0
|
||||
#'
|
||||
#' updateCheckboxInput(session, "inCheckbox", value = x_even)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateCheckboxInput <- updateTextInput
|
||||
|
||||
|
||||
#' Change the value of a slider input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#'
|
||||
#' @seealso \code{\link{sliderInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' # Similar to number and text. only label and value can be set for slider
|
||||
#' updateSliderInput(session, "inSlider",
|
||||
#' label = paste("Slider label", x),
|
||||
#' value = x)
|
||||
#'
|
||||
#' # For sliders that pick out a range, pass in a vector of 2 values.
|
||||
#' updateSliderInput(session, "inSlider2", value = c(x-1, x+1))
|
||||
#'
|
||||
#' # An NA means to not change that value (the low or high one)
|
||||
#' updateSliderInput(session, "inSlider3", value = c(NA, x+2))
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateSliderInput <- updateTextInput
|
||||
|
||||
#' 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.
|
||||
#' @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.
|
||||
#'
|
||||
#' @seealso \code{\link{dateInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' updateDateInput(session, "inDate",
|
||||
#' label = paste("Date label", x),
|
||||
#' value = paste("2013-04-", x, sep=""),
|
||||
#' min = paste("2013-04-", x-1, sep=""),
|
||||
#' max = paste("2013-04-", x+1, sep="")
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL) {
|
||||
|
||||
# If value is a date object, convert it to a string with yyyy-mm-dd format
|
||||
# Same for min and max
|
||||
if (inherits(value, "Date")) value <- format(value, "%Y-%m-%d")
|
||||
if (inherits(min, "Date")) min <- format(min, "%Y-%m-%d")
|
||||
if (inherits(max, "Date")) max <- format(max, "%Y-%m-%d")
|
||||
|
||||
message <- dropNulls(list(label=label, value=value, min=min, max=max))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
#' 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.
|
||||
#' @param end The end date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @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.
|
||||
#'
|
||||
#' @seealso \code{\link{dateRangeInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' updateDateRangeInput(session, "inDateRange",
|
||||
#' label = paste("Date range label", x),
|
||||
#' start = paste("2013-01-", x, sep=""))
|
||||
#' end = paste("2013-12-", x, sep=""))
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
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')
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = label,
|
||||
value = c(start, end),
|
||||
min = min,
|
||||
max = max
|
||||
))
|
||||
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' 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.
|
||||
#'
|
||||
#' @seealso \code{\link{tabsetPanel}}, \code{\link{navlistPanel}},
|
||||
#' \code{\link{navbarPage}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # TRUE if input$controller is even, FALSE otherwise.
|
||||
#' x_even <- input$controller %% 2 == 0
|
||||
#'
|
||||
#' # Change the selected tab.
|
||||
#' # Note that the tabset container must have been created with an 'id' argument
|
||||
#' if (x_even) {
|
||||
#' updateTabsetPanel(session, "inTabset", selected = "panel2")
|
||||
#' } else {
|
||||
#' updateTabsetPanel(session, "inTabset", selected = "panel1")
|
||||
#' }
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateTabsetPanel <- function(session, inputId, selected = NULL) {
|
||||
message <- dropNulls(list(value = selected))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
#' 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.
|
||||
#'
|
||||
#' @seealso \code{\link{numericInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' updateNumericInput(session, "inNumber", value = x)
|
||||
#'
|
||||
#' updateNumericInput(session, "inNumber2",
|
||||
#' label = paste("Number label ", x),
|
||||
#' value = x, min = x-10, max = x+10, step = 5)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL, step = NULL) {
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = label, value = formatNoSci(value),
|
||||
min = formatNoSci(min), max = formatNoSci(max), step = formatNoSci(step)
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a checkbox group input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param choices A named vector or named list of options. For each item, the
|
||||
#' name will be used as the label, and the value will be used as the value.
|
||||
#' @param selected A vector or list of options (values) which will be selected.
|
||||
#'
|
||||
#' @seealso \code{\link{checkboxGroupInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' # Create a list of new options, where the name of the items is something
|
||||
#' # like 'option label x 1', and the values are 'option-x-1'.
|
||||
#' cb_options <- list()
|
||||
#' cb_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
|
||||
#' cb_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
|
||||
#'
|
||||
#' # Change values for input$inCheckboxGroup
|
||||
#' updateCheckboxGroupInput(session, "inCheckboxGroup", choices = cb_options)
|
||||
#'
|
||||
#' # Can also set the label and select items
|
||||
#' updateCheckboxGroupInput(session, "inCheckboxGroup2",
|
||||
#' label = paste("checkboxgroup label", x),
|
||||
#' choices = cb_options,
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
choices = NULL, selected = NULL) {
|
||||
|
||||
choices <- choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
|
||||
options <- if (length(choices))
|
||||
columnToRowData(list(value = choices, label = names(choices)))
|
||||
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a radio input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param choices A named vector or named list of options. For each item, the
|
||||
#' name will be used as the label, and the value will be used as the value.
|
||||
#' @param selected A vector or list of options (values) which will be selected.
|
||||
#'
|
||||
#' @seealso \code{\link{radioButtons}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' r_options <- list()
|
||||
#' r_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
|
||||
#' r_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
|
||||
#'
|
||||
#' # Change values for input$inRadio
|
||||
#' updateRadioButtons(session, "inRadio", choices = r_options)
|
||||
#'
|
||||
#' # Can also set the label and select an item
|
||||
#' updateRadioButtons(session, "inRadio2",
|
||||
#' label = paste("Radio label", x),
|
||||
#' choices = r_options,
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateRadioButtons <- updateCheckboxGroupInput
|
||||
|
||||
|
||||
#' Change the value of a select input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param choices A named vector or named list of options. For each item, the
|
||||
#' name will be used as the label, and the value will be used as the value.
|
||||
#' @param selected A vector or list of options (values) which will be selected.
|
||||
#'
|
||||
#' @seealso \code{\link{selectInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' # Create a list of new options, where the name of the items is something
|
||||
#' # like 'option label x 1', and the values are 'option-x-1'.
|
||||
#' s_options <- list()
|
||||
#' s_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
|
||||
#' s_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
|
||||
#'
|
||||
#' # Change values for input$inSelect
|
||||
#' updateSelectInput(session, "inSelect", choices = s_options)
|
||||
#'
|
||||
#' # Can also set the label and select an item (or more than one if it's a
|
||||
#' # multi-select)
|
||||
#' updateSelectInput(session, "inSelect2",
|
||||
#' label = paste("Select label", x),
|
||||
#' choices = s_options,
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateSelectInput <- updateCheckboxGroupInput
|
||||
|
||||
#' @rdname updateSelectInput
|
||||
#' @param options a list of options (see \code{\link{selectizeInput}})
|
||||
#' @param server whether to store \code{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
|
||||
#' version of \pkg{selectize.js})
|
||||
#' @export
|
||||
updateSelectizeInput <- function(
|
||||
session, inputId, label = NULL, choices = NULL, selected = NULL,
|
||||
options = list(), server = FALSE
|
||||
) {
|
||||
if (length(options)) {
|
||||
res <- checkAsIs(options)
|
||||
cfg <- tags$script(
|
||||
type = 'application/json',
|
||||
`data-for` = inputId,
|
||||
`data-eval` = if (length(res$eval)) HTML(toJSON(res$eval)),
|
||||
HTML(toJSON(res$options))
|
||||
)
|
||||
session$sendInputMessage(inputId, list(newOptions = as.character(cfg)))
|
||||
}
|
||||
if (!server) {
|
||||
return(updateSelectInput(session, inputId, label, choices, selected))
|
||||
}
|
||||
# in the server mode, the choices are not available before we type, so we
|
||||
# cannot really pre-select any options, but here we insert the `selected`
|
||||
# options into selectize forcibly
|
||||
value <- unname(selected)
|
||||
selected <- choicesWithNames(selected)
|
||||
message <- dropNulls(list(
|
||||
label = label,
|
||||
value = value,
|
||||
selected = if (length(selected)) {
|
||||
columnToRowData(list(label = names(selected), value = selected))
|
||||
},
|
||||
url = session$registerDataObj(inputId, choices, selectizeJSON)
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
selectizeJSON <- function(data, req) {
|
||||
query <- parseQueryString(req$QUERY_STRING)
|
||||
# extract the query variables, conjunction (and/or), search string, maximum options
|
||||
var <- fromJSON(query$field)
|
||||
cjn <- if (query$conju == 'and') all else any
|
||||
# all keywords in lower-case, for case-insensitive matching
|
||||
key <- unique(strsplit(tolower(query$query), '\\s+')[[1]])
|
||||
if (identical(key, '')) key <- character(0)
|
||||
mop <- query$maxop
|
||||
|
||||
# convert a single vector to a data frame so it returns {label: , value: }
|
||||
# later in JSON; other objects return arbitrary JSON {x: , y: , foo: , ...}
|
||||
data <- if (is.atomic(data)) {
|
||||
data <- choicesWithNames(data)
|
||||
data.frame(label = names(data), value = data, stringsAsFactors = FALSE)
|
||||
} else as.data.frame(data, stringsAsFactors = FALSE)
|
||||
|
||||
# start searching for keywords in all specified columns
|
||||
idx <- logical(nrow(data))
|
||||
if (length(key)) for (v in var) {
|
||||
matches <- do.call(
|
||||
cbind,
|
||||
lapply(key, function(k) {
|
||||
grepl(k, tolower(as.character(data[[v]])), fixed = TRUE)
|
||||
})
|
||||
)
|
||||
# merge column matches using OR, and match multiple keywords in one column
|
||||
# using the conjunction setting (AND or OR)
|
||||
idx <- idx | apply(matches, 1, cjn)
|
||||
}
|
||||
# only return the first n rows (n = maximum options in configuration)
|
||||
idx <- head(which(idx), mop)
|
||||
data <- data[idx, ]
|
||||
|
||||
httpResponse(200, 'application/json', toJSON(columnToRowData(data)))
|
||||
}
|
||||
899
R/utils.R
Normal file
899
R/utils.R
Normal file
@@ -0,0 +1,899 @@
|
||||
#' @include globals.R
|
||||
#' @include map.R
|
||||
NULL
|
||||
|
||||
#' Make a random number generator repeatable
|
||||
#'
|
||||
#' Given a function that generates random data, returns a wrapped version of
|
||||
#' that function that always uses the same seed when called. The seed to use can
|
||||
#' be passed in explicitly if desired; otherwise, a random number is used.
|
||||
#'
|
||||
#' @param rngfunc The function that is affected by the R session's seed.
|
||||
#' @param seed The seed to set every time the resulting function is called.
|
||||
#' @return A repeatable version of the function that was passed in.
|
||||
#'
|
||||
#' @note When called, the returned function attempts to preserve the R session's
|
||||
#' current seed by snapshotting and restoring
|
||||
#' \code{\link[base]{.Random.seed}}.
|
||||
#'
|
||||
#' @examples
|
||||
#' rnormA <- repeatable(rnorm)
|
||||
#' rnormB <- repeatable(rnorm)
|
||||
#' rnormA(3) # [1] 1.8285879 -0.7468041 -0.4639111
|
||||
#' rnormA(3) # [1] 1.8285879 -0.7468041 -0.4639111
|
||||
#' rnormA(5) # [1] 1.8285879 -0.7468041 -0.4639111 -1.6510126 -1.4686924
|
||||
#' rnormB(5) # [1] -0.7946034 0.2568374 -0.6567597 1.2451387 -0.8375699
|
||||
#'
|
||||
#' @export
|
||||
repeatable <- function(rngfunc, seed = runif(1, 0, .Machine$integer.max)) {
|
||||
force(seed)
|
||||
|
||||
function(...) {
|
||||
# When we exit, restore the seed to its original state
|
||||
if (exists('.Random.seed', where=globalenv())) {
|
||||
currentSeed <- get('.Random.seed', pos=globalenv())
|
||||
on.exit(assign('.Random.seed', currentSeed, pos=globalenv()))
|
||||
}
|
||||
else {
|
||||
on.exit(rm('.Random.seed', pos=globalenv()))
|
||||
}
|
||||
|
||||
set.seed(seed)
|
||||
|
||||
rngfunc(...)
|
||||
}
|
||||
}
|
||||
|
||||
# Temporarily set x in env to value, evaluate expr, and
|
||||
# then restore x to its original state
|
||||
withTemporary <- function(env, x, value, expr, unset = FALSE) {
|
||||
|
||||
if (exists(x, envir = env, inherits = FALSE)) {
|
||||
oldValue <- get(x, envir = env, inherits = FALSE)
|
||||
on.exit(
|
||||
assign(x, oldValue, envir = env, inherits = FALSE),
|
||||
add = TRUE)
|
||||
} else {
|
||||
on.exit(
|
||||
rm(list = x, envir = env, inherits = FALSE),
|
||||
add = TRUE
|
||||
)
|
||||
}
|
||||
|
||||
if (!missing(value) && !isTRUE(unset))
|
||||
assign(x, value, envir = env, inherits = FALSE)
|
||||
else {
|
||||
if (exists(x, envir = env, inherits = FALSE))
|
||||
rm(list = x, envir = env, inherits = FALSE)
|
||||
}
|
||||
force(expr)
|
||||
}
|
||||
|
||||
.globals$ownSeed <- NULL
|
||||
# Evaluate an expression using Shiny's own private stream of
|
||||
# randomness (not affected by set.seed).
|
||||
withPrivateSeed <- function(expr) {
|
||||
withTemporary(.GlobalEnv, ".Random.seed",
|
||||
.globals$ownSeed, unset=is.null(.globals$ownSeed), {
|
||||
tryCatch({
|
||||
expr
|
||||
}, finally = {.globals$ownSeed <- .Random.seed})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# Version of runif that runs with private seed
|
||||
p_runif <- function(...) {
|
||||
withPrivateSeed(runif(...))
|
||||
}
|
||||
|
||||
# Version of sample that runs with private seed
|
||||
p_sample <- function(...) {
|
||||
withPrivateSeed(sample(...))
|
||||
}
|
||||
|
||||
# Return a random integral value in the range [min, max).
|
||||
# If only one argument is passed, then min=0 and max=argument.
|
||||
randomInt <- function(min, max) {
|
||||
if (missing(max)) {
|
||||
max <- min
|
||||
min <- 0
|
||||
}
|
||||
if (min < 0 || max <= min)
|
||||
stop("Invalid min/max values")
|
||||
|
||||
min + sample(max-min, 1)-1
|
||||
}
|
||||
|
||||
p_randomInt <- function(...) {
|
||||
withPrivateSeed(randomInt(...))
|
||||
}
|
||||
|
||||
`%OR%` <- function(x, y) {
|
||||
if (is.null(x) || isTRUE(is.na(x)))
|
||||
y
|
||||
else
|
||||
x
|
||||
}
|
||||
|
||||
`%AND%` <- function(x, y) {
|
||||
if (!is.null(x) && !is.na(x))
|
||||
if (!is.null(y) && !is.na(y))
|
||||
return(y)
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
`%.%` <- function(x, y) {
|
||||
paste(x, y, sep='')
|
||||
}
|
||||
|
||||
# Given a vector or list, drop all the NULL items in it
|
||||
dropNulls <- function(x) {
|
||||
x[!vapply(x, is.null, FUN.VALUE=logical(1))]
|
||||
}
|
||||
|
||||
nullOrEmpty <- function(x) {
|
||||
is.null(x) || length(x) == 0
|
||||
}
|
||||
# Given a vector or list, drop all the NULL items in it
|
||||
dropNullsOrEmpty <- function(x) {
|
||||
x[!vapply(x, nullOrEmpty, FUN.VALUE=logical(1))]
|
||||
}
|
||||
|
||||
# Combine dir and (file)name into a file path. If a file already exists with a
|
||||
# name differing only by case, then use it instead.
|
||||
file.path.ci <- function(dir, name) {
|
||||
default <- file.path(dir, name)
|
||||
if (file.exists(default))
|
||||
return(default)
|
||||
if (!file.exists(dir))
|
||||
return(default)
|
||||
|
||||
matches <- list.files(dir, name, ignore.case=TRUE, full.names=TRUE,
|
||||
include.dirs=TRUE)
|
||||
if (length(matches) == 0)
|
||||
return(default)
|
||||
return(matches[[1]])
|
||||
}
|
||||
|
||||
# Attempt to join a path and relative path, and turn the result into a
|
||||
# (normalized) absolute path. The result will only be returned if it is an
|
||||
# existing file/directory and is a descendant of dir.
|
||||
#
|
||||
# Example:
|
||||
# resolve("/Users/jcheng", "shiny") # "/Users/jcheng/shiny"
|
||||
# resolve("/Users/jcheng", "./shiny") # "/Users/jcheng/shiny"
|
||||
# resolve("/Users/jcheng", "shiny/../shiny/") # "/Users/jcheng/shiny"
|
||||
# resolve("/Users/jcheng", ".") # NULL
|
||||
# resolve("/Users/jcheng", "..") # NULL
|
||||
# resolve("/Users/jcheng", "shiny/..") # NULL
|
||||
resolve <- function(dir, relpath) {
|
||||
abs.path <- file.path(dir, relpath)
|
||||
if (!file.exists(abs.path))
|
||||
return(NULL)
|
||||
abs.path <- normalizePath(abs.path, winslash='/', mustWork=TRUE)
|
||||
dir <- normalizePath(dir, winslash='/', mustWork=TRUE)
|
||||
# trim the possible trailing slash under Windows (#306)
|
||||
if (.Platform$OS.type == 'windows') dir <- sub('/$', '', dir)
|
||||
if (nchar(abs.path) <= nchar(dir) + 1)
|
||||
return(NULL)
|
||||
if (substr(abs.path, 1, nchar(dir)) != dir ||
|
||||
substr(abs.path, nchar(dir)+1, nchar(dir)+1) != '/') {
|
||||
return(NULL)
|
||||
}
|
||||
return(abs.path)
|
||||
}
|
||||
|
||||
# This is a wrapper for download.file and has the same interface.
|
||||
# The only difference is that, if the protocol is https, it changes the
|
||||
# download settings, depending on platform.
|
||||
download <- function(url, ...) {
|
||||
# First, check protocol. If http or https, check platform:
|
||||
if (grepl('^https?://', url)) {
|
||||
|
||||
# If Windows, call setInternet2, then use download.file with defaults.
|
||||
if (.Platform$OS.type == "windows") {
|
||||
# If we directly use setInternet2, R CMD CHECK gives a Note on Mac/Linux
|
||||
mySI2 <- `::`(utils, 'setInternet2')
|
||||
# Store initial settings
|
||||
internet2_start <- mySI2(NA)
|
||||
on.exit(mySI2(internet2_start))
|
||||
|
||||
# Needed for https
|
||||
mySI2(TRUE)
|
||||
download.file(url, ...)
|
||||
|
||||
} else {
|
||||
# If non-Windows, check for curl/wget/lynx, then call download.file with
|
||||
# appropriate method.
|
||||
|
||||
if (nzchar(Sys.which("wget")[1])) {
|
||||
method <- "wget"
|
||||
} else if (nzchar(Sys.which("curl")[1])) {
|
||||
method <- "curl"
|
||||
|
||||
# curl needs to add a -L option to follow redirects.
|
||||
# Save the original options and restore when we exit.
|
||||
orig_extra_options <- getOption("download.file.extra")
|
||||
on.exit(options(download.file.extra = orig_extra_options))
|
||||
|
||||
options(download.file.extra = paste("-L", orig_extra_options))
|
||||
|
||||
} else if (nzchar(Sys.which("lynx")[1])) {
|
||||
method <- "lynx"
|
||||
} else {
|
||||
stop("no download method found")
|
||||
}
|
||||
|
||||
download.file(url, method = method, ...)
|
||||
}
|
||||
|
||||
} else {
|
||||
download.file(url, ...)
|
||||
}
|
||||
}
|
||||
|
||||
knownContentTypes <- Map$new()
|
||||
knownContentTypes$mset(
|
||||
html='text/html; charset=UTF-8',
|
||||
htm='text/html; charset=UTF-8',
|
||||
js='text/javascript',
|
||||
css='text/css',
|
||||
png='image/png',
|
||||
jpg='image/jpeg',
|
||||
jpeg='image/jpeg',
|
||||
gif='image/gif',
|
||||
svg='image/svg+xml',
|
||||
txt='text/plain',
|
||||
pdf='application/pdf',
|
||||
ps='application/postscript',
|
||||
xml='application/xml',
|
||||
m3u='audio/x-mpegurl',
|
||||
m4a='audio/mp4a-latm',
|
||||
m4b='audio/mp4a-latm',
|
||||
m4p='audio/mp4a-latm',
|
||||
mp3='audio/mpeg',
|
||||
wav='audio/x-wav',
|
||||
m4u='video/vnd.mpegurl',
|
||||
m4v='video/x-m4v',
|
||||
mp4='video/mp4',
|
||||
mpeg='video/mpeg',
|
||||
mpg='video/mpeg',
|
||||
avi='video/x-msvideo',
|
||||
mov='video/quicktime',
|
||||
ogg='application/ogg',
|
||||
swf='application/x-shockwave-flash',
|
||||
doc='application/msword',
|
||||
xls='application/vnd.ms-excel',
|
||||
ppt='application/vnd.ms-powerpoint',
|
||||
xlsx='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
xltx='application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||
potx='application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||
ppsx='application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||
pptx='application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
sldx='application/vnd.openxmlformats-officedocument.presentationml.slide',
|
||||
docx='application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
dotx='application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
xlam='application/vnd.ms-excel.addin.macroEnabled.12',
|
||||
xlsb='application/vnd.ms-excel.sheet.binary.macroEnabled.12')
|
||||
|
||||
getContentType <- function(ext, defaultType='application/octet-stream') {
|
||||
knownContentTypes$get(tolower(ext)) %OR% defaultType
|
||||
}
|
||||
|
||||
# Create a zero-arg function from a quoted expression and environment
|
||||
# @examples
|
||||
# makeFunction(body=quote(print(3)))
|
||||
makeFunction <- function(args = pairlist(), body, env = parent.frame()) {
|
||||
eval(call("function", args, body), env)
|
||||
}
|
||||
|
||||
#' Convert an expression to a function
|
||||
#'
|
||||
#' This is to be called from another function, because it will attempt to get
|
||||
#' an unquoted expression from two calls back.
|
||||
#'
|
||||
#' If expr is a quoted expression, then this just converts it to a function.
|
||||
#' If expr is a function, then this simply returns expr (and prints a
|
||||
#' deprecation message).
|
||||
#' If expr was a non-quoted expression from two calls back, then this will
|
||||
#' quote the original expression and convert it to a function.
|
||||
#
|
||||
#' @param expr A quoted or unquoted expression, or a function.
|
||||
#' @param env The desired environment for the function. Defaults to the
|
||||
#' calling environment two steps back.
|
||||
#' @param quoted Is the expression quoted?
|
||||
#' @param caller_offset If specified, the offset in the callstack of the
|
||||
#' functiont to be treated as the caller.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Example of a new renderer, similar to renderText
|
||||
#' # This is something that toolkit authors will do
|
||||
#' renderTriple <- function(expr, env=parent.frame(), quoted=FALSE) {
|
||||
#' # Convert expr to a function
|
||||
#' func <- shiny::exprToFunction(expr, env, quoted)
|
||||
#'
|
||||
#' function() {
|
||||
#' value <- func()
|
||||
#' paste(rep(value, 3), collapse=", ")
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
#' # Example of using the renderer.
|
||||
#' # This is something that app authors will do.
|
||||
#' values <- reactiveValues(A="text")
|
||||
#'
|
||||
#' \dontrun{
|
||||
#' # Create an output object
|
||||
#' output$tripleA <- renderTriple({
|
||||
#' values$A
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' # At the R console, you can experiment with the renderer using isolate()
|
||||
#' tripleA <- renderTriple({
|
||||
#' values$A
|
||||
#' })
|
||||
#'
|
||||
#' isolate(tripleA())
|
||||
#' # "text, text, text"
|
||||
#'
|
||||
#' @export
|
||||
exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE,
|
||||
caller_offset=1) {
|
||||
# Get the quoted expr from two calls back
|
||||
expr_sub <- eval(substitute(substitute(expr)), parent.frame(caller_offset))
|
||||
|
||||
# Check if expr is a function, making sure not to evaluate expr, in case it
|
||||
# is actually an unquoted expression.
|
||||
# If expr is a single token, then indexing with [[ will error; if it has multiple
|
||||
# tokens, then [[ works. In the former case it will be a name object; in the
|
||||
# latter, it will be a language object.
|
||||
if (!is.null(expr_sub) && !is.name(expr_sub) && expr_sub[[1]] == as.name('function')) {
|
||||
# Get name of function that called this function
|
||||
called_fun <- sys.call(-1 * caller_offset)[[1]]
|
||||
|
||||
shinyDeprecated(msg = paste("Passing functions to '", called_fun,
|
||||
"' is deprecated. Please use expressions instead. See ?", called_fun,
|
||||
" for more information.", sep=""))
|
||||
return(expr)
|
||||
}
|
||||
|
||||
if (quoted) {
|
||||
# expr is a quoted expression
|
||||
makeFunction(body=expr, env=env)
|
||||
} else {
|
||||
# expr is an unquoted expression
|
||||
makeFunction(body=expr_sub, env=env)
|
||||
}
|
||||
}
|
||||
|
||||
#' Install an expression as a function
|
||||
#'
|
||||
#' 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.
|
||||
#'
|
||||
#' @seealso Wraps \code{\link{exprToFunction}}; see that method's documentation
|
||||
#' for more documentation and examples.
|
||||
#'
|
||||
#' @param expr A quoted or unquoted expression
|
||||
#' @param name The name the function should be given
|
||||
#' @param eval.env The desired environment for the function. Defaults to the
|
||||
#' calling environment two steps back.
|
||||
#' @param quoted Is the expression quoted?
|
||||
#' @param assign.env The environment in which the function should be assigned.
|
||||
#' @param label A label for the object to be shown in the debugger. Defaults to
|
||||
#' the name of the calling function.
|
||||
#'
|
||||
#' @export
|
||||
installExprFunction <- function(expr, name, eval.env = parent.frame(2),
|
||||
quoted = FALSE,
|
||||
assign.env = parent.frame(1),
|
||||
label = as.character(sys.call(-1)[[1]])) {
|
||||
func <- exprToFunction(expr, eval.env, quoted, 2)
|
||||
assign(name, func, envir = assign.env)
|
||||
registerDebugHook(name, assign.env, label)
|
||||
}
|
||||
|
||||
#' Parse a GET query string from a URL
|
||||
#'
|
||||
#' Returns a named character vector of key-value pairs.
|
||||
#'
|
||||
#' @param str The query string. It can have a leading \code{"?"} or not.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' parseQueryString("?foo=1&bar=b%20a%20r")
|
||||
#'
|
||||
#' \dontrun{
|
||||
#' # Example of usage within a Shiny app
|
||||
#' shinyServer(function(input, output, clientData) {
|
||||
#'
|
||||
#' output$queryText <- renderText({
|
||||
#' query <- parseQueryString(clientData$url_search)
|
||||
#'
|
||||
#' # Ways of accessing the values
|
||||
#' if (as.numeric(query$foo) == 1) {
|
||||
#' # Do something
|
||||
#' }
|
||||
#' if (query[["bar"]] == "targetstring") {
|
||||
#' # Do something else
|
||||
#' }
|
||||
#'
|
||||
#' # Return a string with key-value pairs
|
||||
#' paste(names(query), query, sep = "=", collapse=", ")
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
parseQueryString <- function(str) {
|
||||
if (is.null(str) || nchar(str) == 0)
|
||||
return(list())
|
||||
|
||||
# Remove leading ?
|
||||
if (substr(str, 1, 1) == '?')
|
||||
str <- substr(str, 2, nchar(str))
|
||||
|
||||
pairs <- strsplit(str, '&', fixed = TRUE)[[1]]
|
||||
pairs <- strsplit(pairs, '=', fixed = TRUE)
|
||||
|
||||
keys <- vapply(pairs, function(x) x[1], FUN.VALUE = character(1))
|
||||
values <- vapply(pairs, function(x) x[2], FUN.VALUE = character(1))
|
||||
# Replace NA with '', so they don't get converted to 'NA' by URLdecode
|
||||
values[is.na(values)] <- ''
|
||||
|
||||
# Convert "+" to " ", since URLdecode doesn't do it
|
||||
keys <- gsub('+', ' ', keys, fixed = TRUE)
|
||||
values <- gsub('+', ' ', values, fixed = TRUE)
|
||||
|
||||
keys <- vapply(keys, function(x) URLdecode(x), FUN.VALUE = character(1))
|
||||
values <- vapply(values, function(x) URLdecode(x), FUN.VALUE = character(1))
|
||||
|
||||
setNames(as.list(values), keys)
|
||||
}
|
||||
|
||||
# decide what to do in case of errors; it is customizable using the shiny.error
|
||||
# option (e.g. we can set options(shiny.error = recover))
|
||||
shinyCallingHandlers <- function(expr) {
|
||||
withCallingHandlers(expr, error = function(e) {
|
||||
handle <- getOption('shiny.error')
|
||||
if (is.function(handle)) handle()
|
||||
})
|
||||
}
|
||||
|
||||
#' Print message for deprecated functions in Shiny
|
||||
#'
|
||||
#' To disable these messages, use \code{options(shiny.deprecation.messages=FALSE)}.
|
||||
#'
|
||||
#' @param new Name of replacement function.
|
||||
#' @param msg Message to print. If used, this will override the default message.
|
||||
#' @param old Name of deprecated function.
|
||||
shinyDeprecated <- function(new=NULL, msg=NULL,
|
||||
old=as.character(sys.call(sys.parent()))[1L]) {
|
||||
|
||||
if (getOption("shiny.deprecation.messages", default=TRUE) == FALSE)
|
||||
return(invisible())
|
||||
|
||||
if (is.null(msg)) {
|
||||
msg <- paste(old, "is deprecated.")
|
||||
if (!is.null(new))
|
||||
msg <- paste(msg, "Please use", new, "instead.",
|
||||
"To disable this message, run options(shiny.deprecation.messages=FALSE)")
|
||||
}
|
||||
# Similar to .Deprecated(), but print a message instead of warning
|
||||
message(msg)
|
||||
}
|
||||
|
||||
#' Register a function with the debugger (if one is active).
|
||||
#'
|
||||
#' Call this function after exprToFunction to give any active debugger a hook
|
||||
#' to set and clear breakpoints in the function. A debugger may implement
|
||||
#' registerShinyDebugHook to receive callbacks when Shiny functions are
|
||||
#' instantiated at runtime.
|
||||
#'
|
||||
#' @param name Name of the field or object containing the function.
|
||||
#' @param where The reference object or environment containing the function.
|
||||
#' @param label A label to display on the function in the debugger.
|
||||
#' @noRd
|
||||
registerDebugHook <- function(name, where, label) {
|
||||
if (exists("registerShinyDebugHook", mode = "function")) {
|
||||
registerShinyDebugHook <- get("registerShinyDebugHook", mode = "function")
|
||||
params <- new.env(parent = emptyenv())
|
||||
params$name <- name
|
||||
params$where <- where
|
||||
params$label <- label
|
||||
registerShinyDebugHook(params)
|
||||
}
|
||||
}
|
||||
|
||||
Callbacks <- setRefClass(
|
||||
'Callbacks',
|
||||
fields = list(
|
||||
.nextId = 'integer',
|
||||
.callbacks = 'Map'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
.nextId <<- as.integer(.Machine$integer.max)
|
||||
},
|
||||
register = function(callback) {
|
||||
id <- as.character(.nextId)
|
||||
.nextId <<- .nextId - 1L
|
||||
.callbacks$set(id, callback)
|
||||
return(function() {
|
||||
.callbacks$remove(id)
|
||||
})
|
||||
},
|
||||
invoke = function(..., onError=NULL) {
|
||||
for (callback in .callbacks$values()) {
|
||||
if (is.null(onError)) {
|
||||
callback(...)
|
||||
} else {
|
||||
tryCatch(callback(...), error = onError)
|
||||
}
|
||||
}
|
||||
},
|
||||
count = function() {
|
||||
.callbacks$size()
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# convert a data frame to JSON as required by DataTables request
|
||||
dataTablesJSON <- function(data, req) {
|
||||
query <- req$QUERY_STRING
|
||||
n <- nrow(data)
|
||||
with(parseQueryString(query), {
|
||||
useRegex <- function(j, envir = parent.frame()) {
|
||||
# FIXME: bRegex is not part of the query string yet (DataTables 1.9.4)
|
||||
return(TRUE)
|
||||
ex <- getExists(
|
||||
if (missing(j)) 'bRegex' else sprintf('bRegex_%s', j), 'character', envir
|
||||
)
|
||||
is.null(ex) || ex == 'true'
|
||||
}
|
||||
# global searching
|
||||
i <- seq_len(n)
|
||||
sSearch <- getExists('sSearch', 'character')
|
||||
if (length(sSearch) && nzchar(sSearch)) {
|
||||
bRegex <- useRegex()
|
||||
i0 <- apply(data, 2, function(x) grep(sSearch, as.character(x), fixed = !bRegex))
|
||||
i <- intersect(i, unique(unlist(i0)))
|
||||
}
|
||||
# search by columns
|
||||
if (length(i)) for (j in seq_len(as.integer(iColumns)) - 1) {
|
||||
if (is.null(s <- getExists(sprintf('bSearchable_%d', j), 'character')) ||
|
||||
s == "0" || s == "false") next # the j-th column is not searchable
|
||||
if (is.null(k <- getExists(sprintf('sSearch_%d', j), 'character'))) next
|
||||
if (nzchar(k)) {
|
||||
dj <- data[, j + 1]
|
||||
r <- commaToRange(k)
|
||||
ij <- if (length(r) == 2 && is.numeric(dj)) {
|
||||
which(dj >= r[1] & dj <= r[2])
|
||||
} else {
|
||||
grep(k, as.character(dj), fixed = !useRegex(j))
|
||||
}
|
||||
i <- intersect(ij, i)
|
||||
}
|
||||
if (length(i) == 0) break
|
||||
}
|
||||
if (length(i) != n) data <- data[i, , drop = FALSE]
|
||||
# sorting
|
||||
oList <- list()
|
||||
for (j in seq_len(as.integer(iSortingCols)) - 1) {
|
||||
if (is.null(k <- getExists(sprintf('iSortCol_%d', j), 'character'))) break
|
||||
desc <- getExists(sprintf('sSortDir_%d', j), 'character')
|
||||
if (is.character(desc)) {
|
||||
col <- data[, as.integer(k) + 1]
|
||||
oList[[length(oList) + 1]] <- (if (desc == 'asc') identity else `-`)(
|
||||
if (is.numeric(col)) col else xtfrm(col)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (length(oList)) {
|
||||
i <- do.call(order, oList)
|
||||
data <- data[i, , drop = FALSE]
|
||||
}
|
||||
# paging
|
||||
if (iDisplayLength != '-1') {
|
||||
i <- seq(as.integer(iDisplayStart) + 1L, length.out = as.integer(iDisplayLength))
|
||||
i <- i[i <= nrow(data)]
|
||||
fdata <- data[i, , drop = FALSE] # filtered data
|
||||
} else fdata <- data
|
||||
fdata <- unname(as.matrix(fdata))
|
||||
# WAT: toJSON(list(x = matrix(nrow = 0, ncol = 1))) => {"x": } (#299)
|
||||
if (nrow(fdata) == 0) fdata <- list()
|
||||
# WAT: toJSON(list(x = matrix(1:2))) => {x: [ [1], [2] ]}, however,
|
||||
# toJSON(list(x = matrix(1))) => {x: [ 1 ]} (loss of dimension, #429)
|
||||
if (all(dim(fdata) == 1)) fdata <- list(list(fdata[1, 1]))
|
||||
|
||||
res <- toJSON(list(
|
||||
sEcho = as.integer(sEcho),
|
||||
iTotalRecords = n,
|
||||
iTotalDisplayRecords = nrow(data),
|
||||
aaData = fdata
|
||||
))
|
||||
httpResponse(200, 'application/json', res)
|
||||
})
|
||||
}
|
||||
|
||||
getExists <- function(x, mode, envir = parent.frame()) {
|
||||
if (exists(x, envir = envir, mode = mode, inherits = FALSE))
|
||||
get(x, envir = envir, mode = mode, inherits = FALSE)
|
||||
}
|
||||
|
||||
# convert a string of the form "lower,upper" to c(lower, upper)
|
||||
commaToRange <- function(string) {
|
||||
if (!grepl(',', string)) return()
|
||||
r <- strsplit(string, ',')[[1]]
|
||||
if (length(r) > 2) return()
|
||||
if (length(r) == 1) r <- c(r, '') # lower,
|
||||
r <- as.numeric(r)
|
||||
if (is.na(r[1])) r[1] <- -Inf
|
||||
if (is.na(r[2])) r[2] <- Inf
|
||||
r
|
||||
}
|
||||
|
||||
# for options passed to DataTables/Selectize/..., the options of the class AsIs
|
||||
# will be evaluated as literal JavaScript code
|
||||
checkAsIs <- function(options) {
|
||||
evalOptions <- if (length(options)) {
|
||||
nms <- names(options)
|
||||
i <- unlist(lapply(options, function(x) {
|
||||
is.character(x) && inherits(x, 'AsIs')
|
||||
}))
|
||||
if (any(i)) {
|
||||
# must convert to character, otherwise toJSON() turns it to an array []
|
||||
options[i] <- lapply(options[i], paste, collapse = '\n')
|
||||
nms[i] # options of these names will be evaluated in JS
|
||||
}
|
||||
}
|
||||
list(options = options, eval = evalOptions)
|
||||
}
|
||||
|
||||
srcrefFromShinyCall <- function(expr) {
|
||||
srcrefs <- attr(expr, "srcref")
|
||||
num_exprs <- length(srcrefs)
|
||||
if (num_exprs < 1)
|
||||
return(NULL)
|
||||
c(srcrefs[[1]][1], srcrefs[[1]][2],
|
||||
srcrefs[[num_exprs]][3], srcrefs[[num_exprs]][4],
|
||||
srcrefs[[1]][5], srcrefs[[num_exprs]][6])
|
||||
}
|
||||
|
||||
# Indicates whether the given querystring should cause the associated request
|
||||
# to be handled in showcase mode. Returns the showcase mode if set, or NULL
|
||||
# if no showcase mode is set.
|
||||
showcaseModeOfQuerystring <- function(querystring) {
|
||||
if (nchar(querystring) > 0) {
|
||||
qs <- parseQueryString(querystring)
|
||||
if (exists("showcase", where = qs)) {
|
||||
return(as.numeric(qs$showcase))
|
||||
}
|
||||
}
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
showcaseModeOfReq <- function(req) {
|
||||
showcaseModeOfQuerystring(req$QUERY_STRING)
|
||||
}
|
||||
|
||||
# Returns (just) the filename containing the given source reference, or an
|
||||
# empty string if the source reference doesn't include file information.
|
||||
srcFileOfRef <- function(srcref) {
|
||||
fileEnv <- attr(srcref, "srcfile")
|
||||
# The 'srcfile' attribute should be a non-null environment containing the
|
||||
# variable 'filename', which gives the full path to the source file.
|
||||
if (!is.null(fileEnv) &&
|
||||
is.environment(fileEnv) &&
|
||||
exists("filename", where = fileEnv))
|
||||
basename(fileEnv[["filename"]])
|
||||
else
|
||||
""
|
||||
}
|
||||
|
||||
# Format a number without sci notation, and keep as many digits as possible (do
|
||||
# we really need to go beyond 15 digits?)
|
||||
formatNoSci <- function(x) {
|
||||
if (is.null(x)) return(NULL)
|
||||
format(x, scientific = FALSE, digits = 15)
|
||||
}
|
||||
|
||||
# Returns a function that calls the given func and caches the result for
|
||||
# subsequent calls, unless the given file's mtime changes.
|
||||
cachedFuncWithFile <- function(dir, file, func, case.sensitive = FALSE) {
|
||||
dir <- normalizePath(dir, mustWork=TRUE)
|
||||
mtime <- NA
|
||||
value <- NULL
|
||||
function(...) {
|
||||
fname <- if (case.sensitive)
|
||||
file.path(dir, file)
|
||||
else
|
||||
file.path.ci(dir, file)
|
||||
|
||||
now <- file.info(fname)$mtime
|
||||
if (!identical(mtime, now)) {
|
||||
value <<- func(fname, ...)
|
||||
mtime <<- now
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
# Returns a function that sources the file and caches the result for subsequent
|
||||
# calls, unless the file's mtime changes.
|
||||
cachedSource <- function(dir, file, case.sensitive = FALSE) {
|
||||
dir <- normalizePath(dir, mustWork=TRUE)
|
||||
cachedFuncWithFile(dir, file, function(fname, ...) {
|
||||
if (file.exists(fname))
|
||||
return(source(fname, ...))
|
||||
else
|
||||
return(NULL)
|
||||
})
|
||||
}
|
||||
|
||||
# turn column-based data to row-based data (mainly for JSON), e.g. data.frame(x
|
||||
# = 1:10, y = 10:1) ==> list(list(x = 1, y = 10), list(x = 2, y = 9), ...)
|
||||
columnToRowData <- function(data) {
|
||||
do.call(
|
||||
mapply, c(
|
||||
list(FUN = function(...) list(...), SIMPLIFY = FALSE, USE.NAMES = FALSE),
|
||||
as.list(data)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#' Validate input values and other conditions
|
||||
#'
|
||||
#' For an output rendering function (e.g. \code{\link{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
|
||||
#' doing so.
|
||||
#'
|
||||
#' The \code{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}
|
||||
#' 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
|
||||
#' the following:
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \item{\code{FALSE}}
|
||||
#' \item{\code{NULL}}
|
||||
#' \item{\code{""}}
|
||||
#' \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}}}
|
||||
#' }
|
||||
#'
|
||||
#' 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}.
|
||||
#'
|
||||
#' If you need validation logic that differs significantly from \code{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
|
||||
#' single-element character vector, or if the failure should happen silently,
|
||||
#' \code{FALSE}.
|
||||
#'
|
||||
#' Because validation failure is signaled as an error, you can use
|
||||
#' \code{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.
|
||||
#'
|
||||
#' @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
|
||||
#' message.
|
||||
#' @param errorClass A CSS class to apply. The actual CSS string will have
|
||||
#' \code{shiny-output-error-} prepended to this value.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' # in ui.R
|
||||
#' fluidPage(
|
||||
#' checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
|
||||
#' selectizeInput('in2', 'Select a state', choices = state.name),
|
||||
#' plotOutput('plot')
|
||||
#' )
|
||||
#'
|
||||
#' # in server.R
|
||||
#' function(input, output) {
|
||||
#' output$plot <- renderPlot({
|
||||
#' validate(
|
||||
#' need(input$in1, 'Check at least one letter!'),
|
||||
#' need(input$in2 == '', 'Please choose a state.')
|
||||
#' )
|
||||
#' plot(1:10, main = paste(c(input$in1, input$in2), collapse = ', '))
|
||||
#' })
|
||||
#' }
|
||||
validate <- function(..., errorClass = character(0)) {
|
||||
results <- sapply(list(...), function(x) {
|
||||
# Detect NULL or NA
|
||||
if (is.null(x))
|
||||
return(NA_character_)
|
||||
else if (identical(x, FALSE))
|
||||
return("")
|
||||
else if (is.character(x))
|
||||
return(paste(as.character(x), collapse = "\n"))
|
||||
else
|
||||
stop("Unexpected validation result: ", as.character(x))
|
||||
})
|
||||
|
||||
results <- na.omit(results)
|
||||
if (length(results) == 0)
|
||||
return(invisible())
|
||||
|
||||
# There may be empty strings remaining; these are message-less failures that
|
||||
# started as FALSE
|
||||
results <- results[nzchar(results)]
|
||||
|
||||
stopWithCondition(c("validation", errorClass), paste(results, collapse="\n"))
|
||||
}
|
||||
|
||||
#' @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.
|
||||
#' @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
|
||||
#' otherwise.
|
||||
#' @export
|
||||
#' @rdname validate
|
||||
need <- function(expr, message = paste(label, "must be provided"), label) {
|
||||
|
||||
force(message) # Fail fast on message/label both being missing
|
||||
|
||||
if (!isTruthy(expr))
|
||||
return(message)
|
||||
else
|
||||
return(invisible(NULL))
|
||||
}
|
||||
|
||||
isTruthy <- function(x) {
|
||||
if (inherits(x, 'try-error'))
|
||||
return(FALSE)
|
||||
|
||||
if (!is.atomic(x))
|
||||
return(TRUE)
|
||||
|
||||
if (is.null(x))
|
||||
return(FALSE)
|
||||
if (length(x) == 0)
|
||||
return(FALSE)
|
||||
if (all(is.na(x)))
|
||||
return(FALSE)
|
||||
if (is.character(x) && !any(nzchar(na.omit(x))))
|
||||
return(FALSE)
|
||||
if (inherits(x, 'shinyActionButtonValue') && x == 0)
|
||||
return(FALSE)
|
||||
if (is.logical(x) && !any(na.omit(x)))
|
||||
return(FALSE)
|
||||
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
# add class(es) to the error condition, which will be used as names of CSS
|
||||
# classes, e.g. shiny-output-error shiny-output-error-validation
|
||||
stopWithCondition <- function(class, message) {
|
||||
cond <- structure(
|
||||
list(message = message),
|
||||
class = c(class, 'shiny.silent.error', 'error', 'condition')
|
||||
)
|
||||
stop(cond)
|
||||
}
|
||||
23
README.md
23
README.md
@@ -1,34 +1,45 @@
|
||||
# Shiny
|
||||
# Shiny
|
||||
|
||||
[](https://travis-ci.org/rstudio/shiny)
|
||||
|
||||
Shiny is a new package from RStudio that makes it incredibly easy to build interactive web applications with R.
|
||||
|
||||
For an introduction and examples, visit the [Shiny homepage](http://www.rstudio.com/shiny/).
|
||||
|
||||
## Features
|
||||
|
||||
* Build useful web applications with only a few lines of code—no JavaScript required.
|
||||
* Shiny applications are automatically "live" in the same way that spreadsheets are live. Outputs change instantly as users modify inputs, without requiring a reload of the browser.
|
||||
* Shiny user interfaces can be built entirely using R, or can be written directly in HTML, CSS, and JavaScript for more flexibility.
|
||||
* Works in any R environment (Console R, Rgui for Windows or Mac, ESS, StatET, RStudio, etc.)
|
||||
* Attractive default UI theme based on [Twitter Bootstrap](http://twitter.github.com/bootstrap).
|
||||
* Attractive default UI theme based on [Bootstrap](http://getbootstrap.com/2.3.2/).
|
||||
* A highly customizable slider widget with built-in support for animation.
|
||||
* Pre-built output widgets for displaying plots, tables, and printed output of R objects.
|
||||
* Fast bidirectional communication between the web browser and R using the [websockets](http://illposed.net/websockets.html) package.
|
||||
* Fast bidirectional communication between the web browser and R using the [httpuv](https://github.com/rstudio/httpuv) package.
|
||||
* Uses a [reactive](http://en.wikipedia.org/wiki/Reactive_programming) programming model that eliminates messy event handling code, so you can focus on the code that really matters.
|
||||
* Develop and redistribute your own Shiny widgets that other developers can easily drop into their own applications (coming soon!).
|
||||
|
||||
## Installation
|
||||
|
||||
From an R console:
|
||||
To install the stable version from CRAN, simply run the following from an R console:
|
||||
|
||||
```r
|
||||
options(repos=c(RStudio="http://rstudio.org/_packages", getOption("repos")))
|
||||
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("shiny", "rstudio")
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
To learn more we highly recommend you check out the [Shiny Tutorial](http://rstudio.github.com/shiny/tutorial). The tutorial explains the framework in-depth, walks you through building a simple application, and includes extensive annotated examples.
|
||||
|
||||
We hope you enjoy using Shiny. As you learn more and work with the package please [let us know](https://github.com/rstudio/shiny/issues) what problems you encounter and how you'd like to see Shiny evolve.
|
||||
We hope you enjoy using Shiny. If you have general questions about using Shiny, please use the Shiny [mailing list](https://groups.google.com/forum/#!forum/shiny-discuss). For bug reports, please use the [issue tracker](https://github.com/rstudio/shiny/issues).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
73
inst/NOTICE
73
inst/NOTICE
@@ -4,13 +4,16 @@ these components are included below):
|
||||
|
||||
- jQuery
|
||||
- Bootstrap
|
||||
- bootstrap-datepicker, from https://github.com/eternicode/bootstrap-datepicker
|
||||
- selectize, from https://github.com/brianreavis/selectize.js
|
||||
- es5-shim
|
||||
- jslider
|
||||
|
||||
- DataTables
|
||||
|
||||
jQuery License
|
||||
----------------------------------------------------------------------
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2012 jQuery Foundation and other contributors,
|
||||
Copyright (c) 2012 jQuery Foundation and other contributors,
|
||||
http://jquery.com/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
@@ -33,8 +36,8 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
Bootstrap License
|
||||
----------------------------------------------------------------------
|
||||
Bootstrap, bootstrap-datepicker, and selectize License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -239,8 +242,34 @@ Bootstrap License
|
||||
limitations under the License.
|
||||
|
||||
|
||||
es5-shim License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2009-2014 Kristopher Michael Kowal and contributors
|
||||
|
||||
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.
|
||||
|
||||
|
||||
jslider License
|
||||
----------------------------------------------------------------------
|
||||
----------------------------------------------------------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2012 Egor Khmelev
|
||||
@@ -262,3 +291,35 @@ 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.
|
||||
|
||||
|
||||
DataTables License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2008-2010, Allan Jardine
|
||||
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 Allan Jardine nor SpryMedia UK 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 "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 HOLDERS 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.
|
||||
|
||||
7
inst/examples/01_hello/DESCRIPTION
Normal file
7
inst/examples/01_hello/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Hello Shiny!
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/01_hello/Readme.md
Normal file
4
inst/examples/01_hello/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
This small Shiny application demonstrates Shiny's automatic UI updates. Move
|
||||
the *Number of bins* slider and notice how the `renderPlot` expression is
|
||||
automatically re-evaluated when its dependant, `input$bins`, changes,
|
||||
causing a histogram with a new number of bins to be rendered.
|
||||
@@ -1,20 +1,21 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic required to generate and plot a random distribution
|
||||
# Define server logic required to draw a histogram
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Function that generates a plot of the distribution. The function
|
||||
# is wrapped in a call to reactivePlot to indicate that:
|
||||
|
||||
# Expression that generates a histogram. The expression is
|
||||
# wrapped in a call to renderPlot to indicate that:
|
||||
#
|
||||
# 1) It is "reactive" and therefore should be automatically
|
||||
# 1) It is "reactive" and therefore should be automatically
|
||||
# re-executed when inputs change
|
||||
# 2) Its output type is a plot
|
||||
#
|
||||
output$distPlot <- reactivePlot(function() {
|
||||
|
||||
# generate an rnorm distribution and plot it
|
||||
dist <- rnorm(input$obs)
|
||||
hist(dist)
|
||||
# 2) Its output type is a plot
|
||||
|
||||
output$distPlot <- renderPlot({
|
||||
x <- faithful[, 2] # Old Faithful Geyser data
|
||||
bins <- seq(min(x), max(x), length.out = input$bins + 1)
|
||||
|
||||
# draw the histogram with the specified number of bins
|
||||
hist(x, breaks = bins, col = 'darkgray', border = 'white')
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for application that plots random distributions
|
||||
shinyUI(pageWithSidebar(
|
||||
|
||||
# Define UI for application that draws a histogram
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
headerPanel("Hello Shiny!"),
|
||||
|
||||
# Sidebar with a slider input for number of observations
|
||||
sidebarPanel(
|
||||
sliderInput("obs",
|
||||
"Number of observations:",
|
||||
min = 0,
|
||||
max = 1000,
|
||||
value = 500)
|
||||
),
|
||||
|
||||
# Show a plot of the generated distribution
|
||||
mainPanel(
|
||||
plotOutput("distPlot")
|
||||
titlePanel("Hello Shiny!"),
|
||||
|
||||
# Sidebar with a slider input for the number of bins
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
sliderInput("bins",
|
||||
"Number of bins:",
|
||||
min = 1,
|
||||
max = 50,
|
||||
value = 30)
|
||||
),
|
||||
|
||||
# Show a plot of the generated distribution
|
||||
mainPanel(
|
||||
plotOutput("distPlot")
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
8
inst/examples/02_text/DESCRIPTION
Normal file
8
inst/examples/02_text/DESCRIPTION
Normal file
@@ -0,0 +1,8 @@
|
||||
Title: Shiny Text
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
1
inst/examples/02_text/Readme.md
Normal file
1
inst/examples/02_text/Readme.md
Normal file
@@ -0,0 +1 @@
|
||||
This example demonstrates output of raw text from R using the `renderPrint` function in `server.R` and the `verbatimTextOutput` function in `ui.R`. In this case, a textual summary of the data is shown using R's built-in `summary` function.
|
||||
@@ -1,11 +1,12 @@
|
||||
library(shiny)
|
||||
library(datasets)
|
||||
|
||||
# Define server logic required to summarize and view the selected dataset
|
||||
# Define server logic required to summarize and view the selected
|
||||
# dataset
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Return the requested dataset
|
||||
datasetInput <- reactive(function() {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
@@ -13,13 +14,13 @@ shinyServer(function(input, output) {
|
||||
})
|
||||
|
||||
# Generate a summary of the dataset
|
||||
output$summary <- reactivePrint(function() {
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations
|
||||
output$view <- reactiveTable(function() {
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
shinyUI(pageWithSidebar(
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
headerPanel("Shiny Text"),
|
||||
titlePanel("Shiny Text"),
|
||||
|
||||
# Sidebar with controls to select a dataset and specify the number
|
||||
# of observations to view
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
# Sidebar with controls to select a dataset and specify the
|
||||
# number of observations to view
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
numericInput("obs", "Number of observations to view:", 10)
|
||||
),
|
||||
|
||||
numericInput("obs", "Number of observations to view:", 10)
|
||||
),
|
||||
|
||||
# Show a summary of the dataset and an HTML table with the requested
|
||||
# number of observations
|
||||
mainPanel(
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
tableOutput("view")
|
||||
# Show a summary of the dataset and an HTML table with the
|
||||
# requested number of observations
|
||||
mainPanel(
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
tableOutput("view")
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
7
inst/examples/03_reactivity/DESCRIPTION
Normal file
7
inst/examples/03_reactivity/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Reactivity
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
5
inst/examples/03_reactivity/Readme.md
Normal file
5
inst/examples/03_reactivity/Readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
This example demonstrates a core feature of Shiny: **reactivity**. In `server.R`, a reactive called `datasetInput` is declared.
|
||||
|
||||
Notice that the reactive expression depends on the input expression `input$dataset`, and that it's used by both the output expression `output$summary` and `output$view`. Try changing the dataset (using *Choose a dataset*) while looking at the reactive and then at the outputs; you will see first the reactive and then its dependencies flash.
|
||||
|
||||
Notice also that the reactive expression doesn't just update whenever anything changes--only the inputs it depends on will trigger an update. Change the "Caption" field and notice how only the `output$caption` expression is re-evaluated; the reactive and its dependents are left alone.
|
||||
@@ -1,50 +1,53 @@
|
||||
library(shiny)
|
||||
library(datasets)
|
||||
|
||||
# Define server logic required to summarize and view the selected dataset
|
||||
# Define server logic required to summarize and view the selected
|
||||
# dataset
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# By declaring databaseInput as a reactive function we ensure that:
|
||||
# By declaring datasetInput as a reactive expression we ensure
|
||||
# that:
|
||||
#
|
||||
# 1) It is only called when the inputs it depends on changes
|
||||
# 2) The computation and result are shared by all the callers (it
|
||||
# only executes a single time)
|
||||
# 3) When the inputs change and the function is re-executed, the
|
||||
# new result is compared to the previous result; if the two are
|
||||
# identical, then the callers are not notified
|
||||
# 2) The computation and result are shared by all the callers
|
||||
# (it only executes a single time)
|
||||
#
|
||||
datasetInput <- reactive(function() {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
# The output$caption is computed based on a reactive function that
|
||||
# returns input$caption. When the user changes the "caption" field:
|
||||
# The output$caption is computed based on a reactive expression
|
||||
# that returns input$caption. When the user changes the
|
||||
# "caption" field:
|
||||
#
|
||||
# 1) This function is automatically called to recompute the output
|
||||
# 2) The new caption is pushed back to the browser for re-display
|
||||
# 1) This function is automatically called to recompute the
|
||||
# output
|
||||
# 2) The new caption is pushed back to the browser for
|
||||
# re-display
|
||||
#
|
||||
# Note that because the data-oriented reactive functions below don't
|
||||
# depend on input$caption, those functions are NOT called when
|
||||
# input$caption changes.
|
||||
output$caption <- reactiveText(function() {
|
||||
# Note that because the data-oriented reactive expressions
|
||||
# below don't depend on input$caption, those expressions are
|
||||
# NOT called when input$caption changes.
|
||||
output$caption <- renderText({
|
||||
input$caption
|
||||
})
|
||||
|
||||
# The output$summary depends on the datasetInput reactive function,
|
||||
# so will be re-executed whenever datasetInput is re-executed
|
||||
# The output$summary depends on the datasetInput reactive
|
||||
# expression, so will be re-executed whenever datasetInput is
|
||||
# invalidated
|
||||
# (i.e. whenever the input$dataset changes)
|
||||
output$summary <- reactivePrint(function() {
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# The output$view depends on both the databaseInput reactive function
|
||||
# and input$obs, so will be re-executed whenever input$dataset or
|
||||
# input$obs is changed.
|
||||
output$view <- reactiveTable(function() {
|
||||
# The output$view depends on both the databaseInput reactive
|
||||
# expression and input$obs, so will be re-executed whenever
|
||||
# input$dataset or input$obs is changed.
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
shinyUI(pageWithSidebar(
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
headerPanel("Reactivity"),
|
||||
titlePanel("Reactivity"),
|
||||
|
||||
# Sidebar with controls to provide a caption, select a dataset, and
|
||||
# specify the number of observations to view. Note that changes made
|
||||
# to the caption in the textInput control are updated in the output
|
||||
# area immediately as you type
|
||||
sidebarPanel(
|
||||
textInput("caption", "Caption:", "Data Summary"),
|
||||
# Sidebar with controls to provide a caption, select a dataset,
|
||||
# and specify the number of observations to view. Note that
|
||||
# changes made to the caption in the textInput control are
|
||||
# updated in the output area immediately as you type
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
textInput("caption", "Caption:", "Data Summary"),
|
||||
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
numericInput("obs", "Number of observations to view:", 10)
|
||||
),
|
||||
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
numericInput("obs", "Number of observations to view:", 10)
|
||||
),
|
||||
|
||||
|
||||
# Show the caption, a summary of the dataset and an HTML table with
|
||||
# the requested number of observations
|
||||
mainPanel(
|
||||
h3(textOutput("caption")),
|
||||
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
tableOutput("view")
|
||||
# Show the caption, a summary of the dataset and an HTML
|
||||
# table with the requested number of observations
|
||||
mainPanel(
|
||||
h3(textOutput("caption", container = span)),
|
||||
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
tableOutput("view")
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
7
inst/examples/04_mpg/DESCRIPTION
Normal file
7
inst/examples/04_mpg/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Miles Per Gallon
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/04_mpg/Readme.md
Normal file
4
inst/examples/04_mpg/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
This example demonstrates the following concepts:
|
||||
|
||||
* **Global variables**: The `mpgData` variable is declared outside the `shinyServer` function. This makes it available anywhere inside `shinyServer`. The code in `server.R` outside `shinyServer` is only run once when the app starts up, so it can't contain user input.
|
||||
* **Reactive expressions**: `formulaText` is a reactive expression. Note how it re-evaluates when the Variable field is changed, but not when the Show Outliers box is ticked.
|
||||
@@ -1,30 +1,32 @@
|
||||
library(shiny)
|
||||
library(datasets)
|
||||
|
||||
# We tweak the "am" field to have nicer factor labels. Since this doesn't
|
||||
# rely on any user inputs we can do this once at startup and then use the
|
||||
# value throughout the lifetime of the application
|
||||
# We tweak the "am" field to have nicer factor labels. Since
|
||||
# this doesn't rely on any user inputs we can do this once at
|
||||
# startup and then use the value throughout the lifetime of the
|
||||
# application
|
||||
mpgData <- mtcars
|
||||
mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
|
||||
|
||||
|
||||
# Define server logic required to plot various variables against mpg
|
||||
# Define server logic required to plot various variables against
|
||||
# mpg
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Compute the forumla text in a reactive function since it is
|
||||
# Compute the forumla text in a reactive expression since it is
|
||||
# shared by the output$caption and output$mpgPlot functions
|
||||
formulaText <- reactive(function() {
|
||||
formulaText <- reactive({
|
||||
paste("mpg ~", input$variable)
|
||||
})
|
||||
|
||||
# Return the formula text for printing as a caption
|
||||
output$caption <- reactiveText(function() {
|
||||
output$caption <- renderText({
|
||||
formulaText()
|
||||
})
|
||||
|
||||
# Generate a plot of the requested variable against mpg and only
|
||||
# include outliers if requested
|
||||
output$mpgPlot <- reactivePlot(function() {
|
||||
# Generate a plot of the requested variable against mpg and
|
||||
# only include outliers if requested
|
||||
output$mpgPlot <- renderPlot({
|
||||
boxplot(as.formula(formulaText()),
|
||||
data = mpgData,
|
||||
outline = input$outliers)
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for miles per gallon application
|
||||
shinyUI(pageWithSidebar(
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
headerPanel("Miles Per Gallon"),
|
||||
titlePanel("Miles Per Gallon"),
|
||||
|
||||
# Sidebar with controls to select the variable to plot against mpg
|
||||
# and to specify whether outliers should be included
|
||||
sidebarPanel(
|
||||
selectInput("variable", "Variable:",
|
||||
list("Cylinders" = "cyl",
|
||||
"Transmission" = "am",
|
||||
"Gears" = "gear")),
|
||||
|
||||
checkboxInput("outliers", "Show outliers", FALSE)
|
||||
),
|
||||
# Sidebar with controls to select the variable to plot against
|
||||
# mpg and to specify whether outliers should be included
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
selectInput("variable", "Variable:",
|
||||
c("Cylinders" = "cyl",
|
||||
"Transmission" = "am",
|
||||
"Gears" = "gear")),
|
||||
|
||||
# Show the caption and plot of the requested variable against mpg
|
||||
mainPanel(
|
||||
h3(textOutput("caption")),
|
||||
checkboxInput("outliers", "Show outliers", FALSE)
|
||||
),
|
||||
|
||||
plotOutput("mpgPlot")
|
||||
# Show the caption and plot of the requested variable against
|
||||
# mpg
|
||||
mainPanel(
|
||||
h3(textOutput("caption")),
|
||||
|
||||
plotOutput("mpgPlot")
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
7
inst/examples/05_sliders/DESCRIPTION
Normal file
7
inst/examples/05_sliders/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Sliders
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
3
inst/examples/05_sliders/Readme.md
Normal file
3
inst/examples/05_sliders/Readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
This example demonstrates Shiny's versatile `sliderInput` widget.
|
||||
|
||||
Slider inputs can be used to select single values, to select a continuous range of values, and even to animate over a range.
|
||||
@@ -3,8 +3,9 @@ library(shiny)
|
||||
# Define server logic for slider examples
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Reactive function to compose a data frame containing all of the values
|
||||
sliderValues <- reactive(function() {
|
||||
# Reactive expression to compose a data frame containing all of
|
||||
# the values
|
||||
sliderValues <- reactive({
|
||||
|
||||
# Compose data frame
|
||||
data.frame(
|
||||
@@ -22,7 +23,7 @@ shinyServer(function(input, output) {
|
||||
})
|
||||
|
||||
# Show the values using an HTML table
|
||||
output$values <- reactiveTable(function() {
|
||||
output$values <- renderTable({
|
||||
sliderValues()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for slider demo application
|
||||
shinyUI(pageWithSidebar(
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
headerPanel("Sliders"),
|
||||
titlePanel("Sliders"),
|
||||
|
||||
# Sidebar with sliders that demonstrate various available options
|
||||
sidebarPanel(
|
||||
# Simple integer interval
|
||||
sliderInput("integer", "Integer:",
|
||||
min=0, max=1000, value=500),
|
||||
# Sidebar with sliders that demonstrate various available
|
||||
# options
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
# Simple integer interval
|
||||
sliderInput("integer", "Integer:",
|
||||
min=0, max=1000, value=500),
|
||||
|
||||
# Decimal interval with step value
|
||||
sliderInput("decimal", "Decimal:",
|
||||
min = 0, max = 1, value = 0.5, step= 0.1),
|
||||
|
||||
# Specification of range within an interval
|
||||
sliderInput("range", "Range:",
|
||||
min = 1, max = 1000, value = c(200,500)),
|
||||
|
||||
# Provide a custom currency format for value display,
|
||||
# with basic animation
|
||||
sliderInput("format", "Custom Format:",
|
||||
min = 0, max = 10000, value = 0, step = 2500,
|
||||
format="$#,##0", locale="us", animate=TRUE),
|
||||
|
||||
# Animation with custom interval (in ms) to control speed,
|
||||
# plus looping
|
||||
sliderInput("animation", "Looping Animation:", 1, 2000, 1,
|
||||
step = 10, animate=
|
||||
animationOptions(interval=300, loop=TRUE))
|
||||
),
|
||||
|
||||
# Decimal interval with step value
|
||||
sliderInput("decimal", "Decimal:",
|
||||
min = 0, max = 1, value = 0.5, step= 0.1),
|
||||
|
||||
# Specification of range within an interval
|
||||
sliderInput("range", "Range:",
|
||||
min = 1, max = 1000, value = c(200,500)),
|
||||
|
||||
# Provide a custom currency format for value display, with basic animation
|
||||
sliderInput("format", "Custom Format:",
|
||||
min = 0, max = 10000, value = 0, step = 2500,
|
||||
format="$#,##0", locale="us", animate=TRUE),
|
||||
|
||||
# Animation with custom interval (in ms) to control speed, plus looping
|
||||
sliderInput("animation", "Looping Animation:", 1, 2000, 1, step = 10,
|
||||
animate=animationOptions(interval=300, loop=T))
|
||||
),
|
||||
|
||||
# Show a table summarizing the values entered
|
||||
mainPanel(
|
||||
tableOutput("values")
|
||||
# Show a table summarizing the values entered
|
||||
mainPanel(
|
||||
tableOutput("values")
|
||||
)
|
||||
)
|
||||
))
|
||||
))
|
||||
|
||||
7
inst/examples/06_tabsets/DESCRIPTION
Normal file
7
inst/examples/06_tabsets/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Tabsets
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
9
inst/examples/06_tabsets/Readme.md
Normal file
9
inst/examples/06_tabsets/Readme.md
Normal file
@@ -0,0 +1,9 @@
|
||||
This example demonstrates the `tabsetPanel` and `tabPanel` widgets.
|
||||
|
||||
Notice that outputs that are not visible are not re-evaluated until they become visible. Try this:
|
||||
|
||||
1. Scroll to the bottom of `server.R`
|
||||
2. Change the number of observations, and observe that only `output$plot` is evaluated.
|
||||
3. Click the Summary tab, and observe that `output$summary` is evaluated.
|
||||
4. Change the number of observations again, and observe that now only `output$summary` is evaluated.
|
||||
|
||||
@@ -3,10 +3,11 @@ library(shiny)
|
||||
# Define server logic for random distribution application
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Reactive function to generate the requested distribution. This is
|
||||
# called whenever the inputs change. The output functions defined
|
||||
# below then all use the value computed from this function
|
||||
data <- reactive(function() {
|
||||
# Reactive expression to generate the requested distribution.
|
||||
# This is called whenever the inputs change. The output
|
||||
# functions defined below then all use the value computed from
|
||||
# this expression
|
||||
data <- reactive({
|
||||
dist <- switch(input$dist,
|
||||
norm = rnorm,
|
||||
unif = runif,
|
||||
@@ -17,11 +18,12 @@ shinyServer(function(input, output) {
|
||||
dist(input$n)
|
||||
})
|
||||
|
||||
# Generate a plot of the data. Also uses the inputs to build the
|
||||
# plot label. Note that the dependencies on both the inputs and
|
||||
# the data reactive function are both tracked, and all functions
|
||||
# are called in the sequence implied by the dependency graph
|
||||
output$plot <- reactivePlot(function() {
|
||||
# Generate a plot of the data. Also uses the inputs to build
|
||||
# the plot label. Note that the dependencies on both the inputs
|
||||
# and the data reactive expression are both tracked, and
|
||||
# all expressions are called in the sequence implied by the
|
||||
# dependency graph
|
||||
output$plot <- renderPlot({
|
||||
dist <- input$dist
|
||||
n <- input$n
|
||||
|
||||
@@ -30,12 +32,12 @@ shinyServer(function(input, output) {
|
||||
})
|
||||
|
||||
# Generate a summary of the data
|
||||
output$summary <- reactivePrint(function() {
|
||||
output$summary <- renderPrint({
|
||||
summary(data())
|
||||
})
|
||||
|
||||
# Generate an HTML table view of the data
|
||||
output$table <- reactiveTable(function() {
|
||||
output$table <- renderTable({
|
||||
data.frame(x=data())
|
||||
})
|
||||
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for random distribution application
|
||||
shinyUI(pageWithSidebar(
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
headerPanel("Tabsets"),
|
||||
titlePanel("Tabsets"),
|
||||
|
||||
# Sidebar with controls to select the random distribution type
|
||||
# and number of observations to generate. Note the use of the br()
|
||||
# element to introduce extra vertical spacing
|
||||
sidebarPanel(
|
||||
radioButtons("dist", "Distribution type:",
|
||||
list("Normal" = "norm",
|
||||
"Uniform" = "unif",
|
||||
"Log-normal" = "lnorm",
|
||||
"Exponential" = "exp")),
|
||||
br(),
|
||||
# and number of observations to generate. Note the use of the
|
||||
# br() element to introduce extra vertical spacing
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
radioButtons("dist", "Distribution type:",
|
||||
c("Normal" = "norm",
|
||||
"Uniform" = "unif",
|
||||
"Log-normal" = "lnorm",
|
||||
"Exponential" = "exp")),
|
||||
br(),
|
||||
|
||||
sliderInput("n",
|
||||
"Number of observations:",
|
||||
value = 500,
|
||||
min = 1,
|
||||
max = 1000)
|
||||
),
|
||||
|
||||
sliderInput("n",
|
||||
"Number of observations:",
|
||||
value = 500,
|
||||
min = 1,
|
||||
max = 1000)
|
||||
),
|
||||
|
||||
# Show a tabset that includes a plot, summary, and table view
|
||||
# of the generated distribution
|
||||
mainPanel(
|
||||
tabsetPanel(
|
||||
tabPanel("Plot", plotOutput("plot")),
|
||||
tabPanel("Summary", verbatimTextOutput("summary")),
|
||||
tabPanel("Table", tableOutput("table"))
|
||||
# Show a tabset that includes a plot, summary, and table view
|
||||
# of the generated distribution
|
||||
mainPanel(
|
||||
tabsetPanel(type = "tabs",
|
||||
tabPanel("Plot", plotOutput("plot")),
|
||||
tabPanel("Summary", verbatimTextOutput("summary")),
|
||||
tabPanel("Table", tableOutput("table"))
|
||||
)
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
7
inst/examples/07_widgets/DESCRIPTION
Normal file
7
inst/examples/07_widgets/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Widgets
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
1
inst/examples/07_widgets/Readme.md
Normal file
1
inst/examples/07_widgets/Readme.md
Normal file
@@ -0,0 +1 @@
|
||||
This example demonstrates some additional widgets included in Shiny, such as `helpText` and `submitButton`. The latter is used to delay rendering output until the user explicitly requests it.
|
||||
@@ -1,11 +1,12 @@
|
||||
library(shiny)
|
||||
library(datasets)
|
||||
|
||||
# Define server logic required to summarize and view the selected dataset
|
||||
# Define server logic required to summarize and view the
|
||||
# selected dataset
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Return the requested dataset
|
||||
datasetInput <- reactive(function() {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
@@ -13,13 +14,13 @@ shinyServer(function(input, output) {
|
||||
})
|
||||
|
||||
# Generate a summary of the dataset
|
||||
output$summary <- reactivePrint(function() {
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations
|
||||
output$view <- reactiveTable(function() {
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,39 +1,43 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
shinyUI(pageWithSidebar(
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title.
|
||||
headerPanel("More Widgets"),
|
||||
titlePanel("More Widgets"),
|
||||
|
||||
# Sidebar with controls to select a dataset and specify the number
|
||||
# of observations to view. The helpText function is also used to
|
||||
# include clarifying text. Most notably, the inclusion of a
|
||||
# submitButton defers the rendering of output until the user
|
||||
# explicitly clicks the button (rather than doing it immediately
|
||||
# when inputs change). This is useful if the computations required
|
||||
# to render output are inordinately time-consuming.
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
# Sidebar with controls to select a dataset and specify the
|
||||
# number of observations to view. The helpText function is
|
||||
# also used to include clarifying text. Most notably, the
|
||||
# inclusion of a submitButton defers the rendering of output
|
||||
# until the user explicitly clicks the button (rather than
|
||||
# doing it immediately when inputs change). This is useful if
|
||||
# the computations required to render output are inordinately
|
||||
# time-consuming.
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
numericInput("obs", "Number of observations to view:", 10),
|
||||
|
||||
helpText("Note: while the data view will show only the specified",
|
||||
"number of observations, the summary will still be based",
|
||||
"on the full dataset."),
|
||||
|
||||
submitButton("Update View")
|
||||
),
|
||||
|
||||
numericInput("obs", "Number of observations to view:", 10),
|
||||
|
||||
helpText("Note: while the data view will show only the specified",
|
||||
"number of observations, the summary will still be based",
|
||||
"on the full dataset."),
|
||||
|
||||
submitButton("Update View")
|
||||
),
|
||||
|
||||
# Show a summary of the dataset and an HTML table with the requested
|
||||
# number of observations. Note the use of the h4 function to provide
|
||||
# an additional header above each output section.
|
||||
mainPanel(
|
||||
h4("Summary"),
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
h4("Observations"),
|
||||
tableOutput("view")
|
||||
# Show a summary of the dataset and an HTML table with the
|
||||
# requested number of observations. Note the use of the h4
|
||||
# function to provide an additional header above each output
|
||||
# section.
|
||||
mainPanel(
|
||||
h4("Summary"),
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
h4("Observations"),
|
||||
tableOutput("view")
|
||||
)
|
||||
)
|
||||
))
|
||||
))
|
||||
|
||||
7
inst/examples/08_html/DESCRIPTION
Normal file
7
inst/examples/08_html/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Custom HTML UI
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/08_html/Readme.md
Normal file
4
inst/examples/08_html/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Normally we use the built-in functions, such as `textInput()`, to generate
|
||||
the HTML UI in the R script `ui.R`. Actually **shiny** also works with a
|
||||
custom HTML page `www/index.html`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#html-ui) for more details.
|
||||
@@ -3,10 +3,10 @@ library(shiny)
|
||||
# Define server logic for random distribution application
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Reactive function to generate the requested distribution. This is
|
||||
# called whenever the inputs change. The output functions defined
|
||||
# below then all used the value computed from this function
|
||||
data <- reactive(function() {
|
||||
# Reactive expression to generate the requested distribution. This is
|
||||
# called whenever the inputs change. The output expressions defined
|
||||
# below then all used the value computed from this expression
|
||||
data <- reactive({
|
||||
dist <- switch(input$dist,
|
||||
norm = rnorm,
|
||||
unif = runif,
|
||||
@@ -19,9 +19,9 @@ shinyServer(function(input, output) {
|
||||
|
||||
# Generate a plot of the data. Also uses the inputs to build the
|
||||
# plot label. Note that the dependencies on both the inputs and
|
||||
# the data reactive function are both tracked, and all functions
|
||||
# the data reactive expression are both tracked, and all expressions
|
||||
# are called in the sequence implied by the dependency graph
|
||||
output$plot <- reactivePlot(function() {
|
||||
output$plot <- renderPlot({
|
||||
dist <- input$dist
|
||||
n <- input$n
|
||||
|
||||
@@ -30,12 +30,12 @@ shinyServer(function(input, output) {
|
||||
})
|
||||
|
||||
# Generate a summary of the data
|
||||
output$summary <- reactivePrint(function() {
|
||||
output$summary <- renderPrint({
|
||||
summary(data())
|
||||
})
|
||||
|
||||
# Generate an HTML table view of the data
|
||||
output$table <- reactiveTable(function() {
|
||||
output$table <- renderTable({
|
||||
data.frame(x=data())
|
||||
})
|
||||
|
||||
|
||||
7
inst/examples/09_upload/DESCRIPTION
Normal file
7
inst/examples/09_upload/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: File Upload
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/09_upload/Readme.md
Normal file
4
inst/examples/09_upload/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
We can add a file upload input in the UI using the function `fileInput()`,
|
||||
e.g. `fileInput('foo')`. In `server.R`, we can access the uploaded files via
|
||||
`input$foo`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#uploads) for more details.
|
||||
20
inst/examples/09_upload/server.R
Normal file
20
inst/examples/09_upload/server.R
Normal file
@@ -0,0 +1,20 @@
|
||||
library(shiny)
|
||||
|
||||
shinyServer(function(input, output) {
|
||||
output$contents <- renderTable({
|
||||
|
||||
# input$file1 will be NULL initially. After the user selects
|
||||
# and uploads a file, it will be a data frame with 'name',
|
||||
# 'size', 'type', and 'datapath' columns. The 'datapath'
|
||||
# column will contain the local filenames where the data can
|
||||
# be found.
|
||||
|
||||
inFile <- input$file1
|
||||
|
||||
if (is.null(inFile))
|
||||
return(NULL)
|
||||
|
||||
read.csv(inFile$datapath, header=input$header, sep=input$sep,
|
||||
quote=input$quote)
|
||||
})
|
||||
})
|
||||
28
inst/examples/09_upload/ui.R
Normal file
28
inst/examples/09_upload/ui.R
Normal file
@@ -0,0 +1,28 @@
|
||||
library(shiny)
|
||||
|
||||
shinyUI(fluidPage(
|
||||
titlePanel("Uploading Files"),
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
fileInput('file1', 'Choose CSV File',
|
||||
accept=c('text/csv',
|
||||
'text/comma-separated-values,text/plain',
|
||||
'.csv')),
|
||||
tags$hr(),
|
||||
checkboxInput('header', 'Header', TRUE),
|
||||
radioButtons('sep', 'Separator',
|
||||
c(Comma=',',
|
||||
Semicolon=';',
|
||||
Tab='\t'),
|
||||
','),
|
||||
radioButtons('quote', 'Quote',
|
||||
c(None='',
|
||||
'Double Quote'='"',
|
||||
'Single Quote'="'"),
|
||||
'"')
|
||||
),
|
||||
mainPanel(
|
||||
tableOutput('contents')
|
||||
)
|
||||
)
|
||||
))
|
||||
7
inst/examples/10_download/DESCRIPTION
Normal file
7
inst/examples/10_download/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: File Download
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/10_download/Readme.md
Normal file
4
inst/examples/10_download/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
We can add a download button to the UI using `downloadButton()`, and write
|
||||
the content of the file in `downloadHandler()` in `server.R`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#downloads) for more
|
||||
details.
|
||||
21
inst/examples/10_download/server.R
Normal file
21
inst/examples/10_download/server.R
Normal file
@@ -0,0 +1,21 @@
|
||||
shinyServer(function(input, output) {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
output$table <- renderTable({
|
||||
datasetInput()
|
||||
})
|
||||
|
||||
output$downloadData <- downloadHandler(
|
||||
filename = function() {
|
||||
paste(input$dataset, '.csv', sep='')
|
||||
},
|
||||
content = function(file) {
|
||||
write.csv(datasetInput(), file)
|
||||
}
|
||||
)
|
||||
})
|
||||
13
inst/examples/10_download/ui.R
Normal file
13
inst/examples/10_download/ui.R
Normal file
@@ -0,0 +1,13 @@
|
||||
shinyUI(fluidPage(
|
||||
titlePanel('Downloading Data'),
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
downloadButton('downloadData', 'Download')
|
||||
),
|
||||
mainPanel(
|
||||
tableOutput('table')
|
||||
)
|
||||
)
|
||||
))
|
||||
7
inst/examples/11_timer/DESCRIPTION
Normal file
7
inst/examples/11_timer/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Timer
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/11_timer/Readme.md
Normal file
4
inst/examples/11_timer/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
The function `invalidateLater()` can be used to invalidate an observer or
|
||||
reactive expression in a given number of milliseconds. In this example, the
|
||||
output `currentTime` is updated every second, so it shows the current time
|
||||
on a second basis.
|
||||
6
inst/examples/11_timer/server.R
Normal file
6
inst/examples/11_timer/server.R
Normal file
@@ -0,0 +1,6 @@
|
||||
shinyServer(function(input, output, session) {
|
||||
output$currentTime <- renderText({
|
||||
invalidateLater(1000, session)
|
||||
paste("The current time is", Sys.time())
|
||||
})
|
||||
})
|
||||
3
inst/examples/11_timer/ui.R
Normal file
3
inst/examples/11_timer/ui.R
Normal file
@@ -0,0 +1,3 @@
|
||||
shinyUI(fluidPage(
|
||||
textOutput("currentTime")
|
||||
))
|
||||
164
inst/staticdocs/index.r
Normal file
164
inst/staticdocs/index.r
Normal file
@@ -0,0 +1,164 @@
|
||||
sd_section("UI Layout",
|
||||
"Functions for laying out the user interface for your application.",
|
||||
c(
|
||||
"absolutePanel",
|
||||
"bootstrapPage",
|
||||
"column",
|
||||
"conditionalPanel",
|
||||
"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",
|
||||
"sliderInput",
|
||||
"submitButton",
|
||||
"textInput",
|
||||
"updateCheckboxGroupInput",
|
||||
"updateCheckboxInput",
|
||||
"updateDateInput",
|
||||
"updateDateRangeInput",
|
||||
"updateNumericInput",
|
||||
"updateRadioButtons",
|
||||
"updateSelectInput",
|
||||
"updateSliderInput",
|
||||
"updateTabsetPanel",
|
||||
"updateTextInput"
|
||||
)
|
||||
)
|
||||
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",
|
||||
"imageOutput",
|
||||
"plotOutput",
|
||||
"outputOptions",
|
||||
"tableOutput",
|
||||
"textOutput",
|
||||
"verbatimTextOutput",
|
||||
"downloadButton"
|
||||
)
|
||||
)
|
||||
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"
|
||||
)
|
||||
)
|
||||
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",
|
||||
"renderText",
|
||||
"renderPrint",
|
||||
"renderDataTable",
|
||||
"renderImage",
|
||||
"renderTable",
|
||||
"renderUI",
|
||||
"downloadHandler",
|
||||
"reactivePlot",
|
||||
"reactivePrint",
|
||||
"reactiveTable",
|
||||
"reactiveText",
|
||||
"reactiveUI"
|
||||
)
|
||||
)
|
||||
sd_section("Reactive constructs",
|
||||
"A sub-library that provides reactive programming facilities for R.",
|
||||
c(
|
||||
"invalidateLater",
|
||||
"is.reactivevalues",
|
||||
"isolate",
|
||||
"makeReactiveBinding",
|
||||
"observe",
|
||||
"reactive",
|
||||
"reactiveFileReader",
|
||||
"reactivePoll",
|
||||
"reactiveTimer",
|
||||
"reactiveValues",
|
||||
"reactiveValuesToList",
|
||||
"domains",
|
||||
"showReactLog"
|
||||
)
|
||||
)
|
||||
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",
|
||||
"runExample",
|
||||
"runGist",
|
||||
"runGitHub",
|
||||
"runUrl",
|
||||
"stopApp"
|
||||
)
|
||||
)
|
||||
sd_section("Extending Shiny",
|
||||
"Functions that are intended to be called by third-party packages that extend Shiny.",
|
||||
c(
|
||||
"addResourcePath",
|
||||
"registerInputHandler",
|
||||
"removeInputHandler",
|
||||
"markRenderFunction"
|
||||
)
|
||||
)
|
||||
sd_section("Utility functions",
|
||||
"Miscellaneous utilities that may be useful to advanced users or when extending Shiny.",
|
||||
c(
|
||||
"validate",
|
||||
"session",
|
||||
"exprToFunction",
|
||||
"installExprFunction",
|
||||
"parseQueryString",
|
||||
"plotPNG",
|
||||
"repeatable",
|
||||
"shinyDeprecated"
|
||||
)
|
||||
)
|
||||
sd_section("Embedding",
|
||||
"Functions that are intended for third-party packages that embed Shiny applications.",
|
||||
c(
|
||||
"shinyApp",
|
||||
"maskReactiveContext"
|
||||
)
|
||||
)
|
||||
95
inst/tests-js/SpecRunner.html
Normal file
95
inst/tests-js/SpecRunner.html
Normal file
@@ -0,0 +1,95 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Jasmine Spec Runner</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png">
|
||||
<link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css">
|
||||
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script>
|
||||
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script>
|
||||
|
||||
<!-- include source files here... -->
|
||||
|
||||
<!-- All of these includes are copied out of the HTML file generated by
|
||||
shinyUI() -->
|
||||
<script src="../www/shared/jquery.js" type="text/javascript"></script>
|
||||
<script src="../www/shared/shiny.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/shiny.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/slider/css/jquery.slider.min.css"/>
|
||||
<script src="../www/shared/slider/js/jquery.slider.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/bootstrap/css/bootstrap.min.css"/>
|
||||
<script src="../www/shared/bootstrap/js/bootstrap.min.js"></script>
|
||||
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"/> -->
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/bootstrap/css/bootstrap-responsive.min.css"/>
|
||||
|
||||
<script src="../www/shared/datepicker/js/bootstrap-datepicker.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/datepicker/css/datepicker.css"/>
|
||||
|
||||
<script src="../www/shared/bootstrap-daterangepicker/date.js"></script>
|
||||
<script src="../www/shared/bootstrap-daterangepicker/daterangepicker.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/bootstrap-daterangepicker/daterangepicker.css"/>
|
||||
|
||||
<!-- include spec files here... -->
|
||||
<!-- <script type="text/javascript" src="spec/SpecHelper.js"></script>
|
||||
<script type="text/javascript" src="spec/PlayerSpec.js"></script>
|
||||
-->
|
||||
<script type="text/javascript" src="spec/inputBindingSpec.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 1000;
|
||||
|
||||
var htmlReporter = new jasmine.HtmlReporter();
|
||||
|
||||
jasmineEnv.addReporter(htmlReporter);
|
||||
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return htmlReporter.specFilter(spec);
|
||||
};
|
||||
|
||||
// var currentWindowOnload = window.onload;
|
||||
|
||||
// window.onload = function() {
|
||||
// if (currentWindowOnload) {
|
||||
// currentWindowOnload();
|
||||
// }
|
||||
// execJasmine();
|
||||
// };
|
||||
|
||||
// Add a slight delay before running tests, so that Shiny has time to
|
||||
// do setup stuff.
|
||||
$(document).ready(function() {
|
||||
setTimeout(function() {
|
||||
execJasmine();
|
||||
},
|
||||
50
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
function execJasmine() {
|
||||
jasmineEnv.execute();
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
|
||||
// Clear the Shiny disconnected gray screen shortly after loading
|
||||
$(document).ready(function() {
|
||||
setTimeout(function() {
|
||||
$('body').removeClass('disconnected');
|
||||
},
|
||||
100
|
||||
)
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
2
inst/tests-js/fixtures/textInputBinding.html
Normal file
2
inst/tests-js/fixtures/textInputBinding.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<label>Text input:</label>
|
||||
<input id="in_text" type="text" value="starting value"/>
|
||||
20
inst/tests-js/lib/jasmine-1.3.1/MIT.LICENSE
Normal file
20
inst/tests-js/lib/jasmine-1.3.1/MIT.LICENSE
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2008-2011 Pivotal Labs
|
||||
|
||||
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.
|
||||
681
inst/tests-js/lib/jasmine-1.3.1/jasmine-html.js
Normal file
681
inst/tests-js/lib/jasmine-1.3.1/jasmine-html.js
Normal file
@@ -0,0 +1,681 @@
|
||||
jasmine.HtmlReporterHelpers = {};
|
||||
|
||||
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
|
||||
var el = document.createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
if (child) {
|
||||
el.appendChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == "className") {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
|
||||
var results = child.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
|
||||
var parentDiv = this.dom.summary;
|
||||
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
|
||||
var parent = child[parentSuite];
|
||||
|
||||
if (parent) {
|
||||
if (typeof this.views.suites[parent.id] == 'undefined') {
|
||||
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
|
||||
}
|
||||
parentDiv = this.views.suites[parent.id].element;
|
||||
}
|
||||
|
||||
parentDiv.appendChild(childElement);
|
||||
};
|
||||
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
|
||||
for(var fn in jasmine.HtmlReporterHelpers) {
|
||||
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter = function(_doc) {
|
||||
var self = this;
|
||||
var doc = _doc || window.document;
|
||||
|
||||
var reporterView;
|
||||
|
||||
var dom = {};
|
||||
|
||||
// Jasmine Reporter Public Interface
|
||||
self.logRunningSpecs = false;
|
||||
|
||||
self.reportRunnerStarting = function(runner) {
|
||||
var specs = runner.specs() || [];
|
||||
|
||||
if (specs.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
createReporterDom(runner.env.versionString());
|
||||
doc.body.appendChild(dom.reporter);
|
||||
setExceptionHandling();
|
||||
|
||||
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
|
||||
reporterView.addSpecs(specs, self.specFilter);
|
||||
};
|
||||
|
||||
self.reportRunnerResults = function(runner) {
|
||||
reporterView && reporterView.complete();
|
||||
};
|
||||
|
||||
self.reportSuiteResults = function(suite) {
|
||||
reporterView.suiteComplete(suite);
|
||||
};
|
||||
|
||||
self.reportSpecStarting = function(spec) {
|
||||
if (self.logRunningSpecs) {
|
||||
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
self.reportSpecResults = function(spec) {
|
||||
reporterView.specComplete(spec);
|
||||
};
|
||||
|
||||
self.log = function() {
|
||||
var console = jasmine.getGlobal().console;
|
||||
if (console && console.log) {
|
||||
if (console.log.apply) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.specFilter = function(spec) {
|
||||
if (!focusedSpecName()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return spec.getFullName().indexOf(focusedSpecName()) === 0;
|
||||
};
|
||||
|
||||
return self;
|
||||
|
||||
function focusedSpecName() {
|
||||
var specName;
|
||||
|
||||
(function memoizeFocusedSpec() {
|
||||
if (specName) {
|
||||
return;
|
||||
}
|
||||
|
||||
var paramMap = [];
|
||||
var params = jasmine.HtmlReporter.parameters(doc);
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
specName = paramMap.spec;
|
||||
})();
|
||||
|
||||
return specName;
|
||||
}
|
||||
|
||||
function createReporterDom(version) {
|
||||
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
|
||||
dom.banner = self.createDom('div', { className: 'banner' },
|
||||
self.createDom('span', { className: 'title' }, "Jasmine "),
|
||||
self.createDom('span', { className: 'version' }, version)),
|
||||
|
||||
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
|
||||
dom.alert = self.createDom('div', {className: 'alert'},
|
||||
self.createDom('span', { className: 'exceptions' },
|
||||
self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
|
||||
self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
|
||||
dom.results = self.createDom('div', {className: 'results'},
|
||||
dom.summary = self.createDom('div', { className: 'summary' }),
|
||||
dom.details = self.createDom('div', { id: 'details' }))
|
||||
);
|
||||
}
|
||||
|
||||
function noTryCatch() {
|
||||
return window.location.search.match(/catch=false/);
|
||||
}
|
||||
|
||||
function searchWithCatch() {
|
||||
var params = jasmine.HtmlReporter.parameters(window.document);
|
||||
var removed = false;
|
||||
var i = 0;
|
||||
|
||||
while (!removed && i < params.length) {
|
||||
if (params[i].match(/catch=/)) {
|
||||
params.splice(i, 1);
|
||||
removed = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (jasmine.CATCH_EXCEPTIONS) {
|
||||
params.push("catch=false");
|
||||
}
|
||||
|
||||
return params.join("&");
|
||||
}
|
||||
|
||||
function setExceptionHandling() {
|
||||
var chxCatch = document.getElementById('no_try_catch');
|
||||
|
||||
if (noTryCatch()) {
|
||||
chxCatch.setAttribute('checked', true);
|
||||
jasmine.CATCH_EXCEPTIONS = false;
|
||||
}
|
||||
chxCatch.onclick = function() {
|
||||
window.location.search = searchWithCatch();
|
||||
};
|
||||
}
|
||||
};
|
||||
jasmine.HtmlReporter.parameters = function(doc) {
|
||||
var paramStr = doc.location.search.substring(1);
|
||||
var params = [];
|
||||
|
||||
if (paramStr.length > 0) {
|
||||
params = paramStr.split('&');
|
||||
}
|
||||
return params;
|
||||
}
|
||||
jasmine.HtmlReporter.sectionLink = function(sectionName) {
|
||||
var link = '?';
|
||||
var params = [];
|
||||
|
||||
if (sectionName) {
|
||||
params.push('spec=' + encodeURIComponent(sectionName));
|
||||
}
|
||||
if (!jasmine.CATCH_EXCEPTIONS) {
|
||||
params.push("catch=false");
|
||||
}
|
||||
if (params.length > 0) {
|
||||
link += params.join("&");
|
||||
}
|
||||
|
||||
return link;
|
||||
};
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
|
||||
jasmine.HtmlReporter.ReporterView = function(dom) {
|
||||
this.startedAt = new Date();
|
||||
this.runningSpecCount = 0;
|
||||
this.completeSpecCount = 0;
|
||||
this.passedCount = 0;
|
||||
this.failedCount = 0;
|
||||
this.skippedCount = 0;
|
||||
|
||||
this.createResultsMenu = function() {
|
||||
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
|
||||
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
|
||||
' | ',
|
||||
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
|
||||
|
||||
this.summaryMenuItem.onclick = function() {
|
||||
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
|
||||
};
|
||||
|
||||
this.detailsMenuItem.onclick = function() {
|
||||
showDetails();
|
||||
};
|
||||
};
|
||||
|
||||
this.addSpecs = function(specs, specFilter) {
|
||||
this.totalSpecCount = specs.length;
|
||||
|
||||
this.views = {
|
||||
specs: {},
|
||||
suites: {}
|
||||
};
|
||||
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
var spec = specs[i];
|
||||
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
|
||||
if (specFilter(spec)) {
|
||||
this.runningSpecCount++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.specComplete = function(spec) {
|
||||
this.completeSpecCount++;
|
||||
|
||||
if (isUndefined(this.views.specs[spec.id])) {
|
||||
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
|
||||
}
|
||||
|
||||
var specView = this.views.specs[spec.id];
|
||||
|
||||
switch (specView.status()) {
|
||||
case 'passed':
|
||||
this.passedCount++;
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
this.failedCount++;
|
||||
break;
|
||||
|
||||
case 'skipped':
|
||||
this.skippedCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
specView.refresh();
|
||||
this.refresh();
|
||||
};
|
||||
|
||||
this.suiteComplete = function(suite) {
|
||||
var suiteView = this.views.suites[suite.id];
|
||||
if (isUndefined(suiteView)) {
|
||||
return;
|
||||
}
|
||||
suiteView.refresh();
|
||||
};
|
||||
|
||||
this.refresh = function() {
|
||||
|
||||
if (isUndefined(this.resultsMenu)) {
|
||||
this.createResultsMenu();
|
||||
}
|
||||
|
||||
// currently running UI
|
||||
if (isUndefined(this.runningAlert)) {
|
||||
this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
|
||||
dom.alert.appendChild(this.runningAlert);
|
||||
}
|
||||
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
|
||||
|
||||
// skipped specs UI
|
||||
if (isUndefined(this.skippedAlert)) {
|
||||
this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
|
||||
}
|
||||
|
||||
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
|
||||
|
||||
if (this.skippedCount === 1 && isDefined(dom.alert)) {
|
||||
dom.alert.appendChild(this.skippedAlert);
|
||||
}
|
||||
|
||||
// passing specs UI
|
||||
if (isUndefined(this.passedAlert)) {
|
||||
this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
|
||||
}
|
||||
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
|
||||
|
||||
// failing specs UI
|
||||
if (isUndefined(this.failedAlert)) {
|
||||
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
|
||||
}
|
||||
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
|
||||
|
||||
if (this.failedCount === 1 && isDefined(dom.alert)) {
|
||||
dom.alert.appendChild(this.failedAlert);
|
||||
dom.alert.appendChild(this.resultsMenu);
|
||||
}
|
||||
|
||||
// summary info
|
||||
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
|
||||
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
|
||||
};
|
||||
|
||||
this.complete = function() {
|
||||
dom.alert.removeChild(this.runningAlert);
|
||||
|
||||
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
|
||||
|
||||
if (this.failedCount === 0) {
|
||||
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
|
||||
} else {
|
||||
showDetails();
|
||||
}
|
||||
|
||||
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function showDetails() {
|
||||
if (dom.reporter.className.search(/showDetails/) === -1) {
|
||||
dom.reporter.className += " showDetails";
|
||||
}
|
||||
}
|
||||
|
||||
function isUndefined(obj) {
|
||||
return typeof obj === 'undefined';
|
||||
}
|
||||
|
||||
function isDefined(obj) {
|
||||
return !isUndefined(obj);
|
||||
}
|
||||
|
||||
function specPluralizedFor(count) {
|
||||
var str = count + " spec";
|
||||
if (count > 1) {
|
||||
str += "s"
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
|
||||
|
||||
|
||||
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
|
||||
this.spec = spec;
|
||||
this.dom = dom;
|
||||
this.views = views;
|
||||
|
||||
this.symbol = this.createDom('li', { className: 'pending' });
|
||||
this.dom.symbolSummary.appendChild(this.symbol);
|
||||
|
||||
this.summary = this.createDom('div', { className: 'specSummary' },
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
|
||||
title: this.spec.getFullName()
|
||||
}, this.spec.description)
|
||||
);
|
||||
|
||||
this.detail = this.createDom('div', { className: 'specDetail' },
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
|
||||
title: this.spec.getFullName()
|
||||
}, this.spec.getFullName())
|
||||
);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.status = function() {
|
||||
return this.getSpecStatus(this.spec);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
|
||||
this.symbol.className = this.status();
|
||||
|
||||
switch (this.status()) {
|
||||
case 'skipped':
|
||||
break;
|
||||
|
||||
case 'passed':
|
||||
this.appendSummaryToSuiteDiv();
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
this.appendSummaryToSuiteDiv();
|
||||
this.appendFailureDetail();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
|
||||
this.summary.className += ' ' + this.status();
|
||||
this.appendToSummary(this.spec, this.summary);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
|
||||
this.detail.className += ' ' + this.status();
|
||||
|
||||
var resultItems = this.spec.results().getItems();
|
||||
var messagesDiv = this.createDom('div', { className: 'messages' });
|
||||
|
||||
for (var i = 0; i < resultItems.length; i++) {
|
||||
var result = resultItems[i];
|
||||
|
||||
if (result.type == 'log') {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
|
||||
} else if (result.type == 'expect' && result.passed && !result.passed()) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
||||
|
||||
if (result.trace.stack) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesDiv.childNodes.length > 0) {
|
||||
this.detail.appendChild(messagesDiv);
|
||||
this.dom.details.appendChild(this.detail);
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
|
||||
this.suite = suite;
|
||||
this.dom = dom;
|
||||
this.views = views;
|
||||
|
||||
this.element = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
|
||||
);
|
||||
|
||||
this.appendToSummary(this.suite, this.element);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
|
||||
return this.getSpecStatus(this.suite);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
|
||||
this.element.className += " " + this.status();
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
|
||||
|
||||
/* @deprecated Use jasmine.HtmlReporter instead
|
||||
*/
|
||||
jasmine.TrivialReporter = function(doc) {
|
||||
this.document = doc || document;
|
||||
this.suiteDivs = {};
|
||||
this.logRunningSpecs = false;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
|
||||
var el = document.createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
if (child) { el.appendChild(child); }
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == "className") {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
|
||||
var showPassed, showSkipped;
|
||||
|
||||
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
|
||||
this.createDom('div', { className: 'banner' },
|
||||
this.createDom('div', { className: 'logo' },
|
||||
this.createDom('span', { className: 'title' }, "Jasmine"),
|
||||
this.createDom('span', { className: 'version' }, runner.env.versionString())),
|
||||
this.createDom('div', { className: 'options' },
|
||||
"Show ",
|
||||
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
|
||||
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
|
||||
)
|
||||
),
|
||||
|
||||
this.runnerDiv = this.createDom('div', { className: 'runner running' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
|
||||
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
|
||||
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
|
||||
);
|
||||
|
||||
this.document.body.appendChild(this.outerDiv);
|
||||
|
||||
var suites = runner.suites();
|
||||
for (var i = 0; i < suites.length; i++) {
|
||||
var suite = suites[i];
|
||||
var suiteDiv = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
|
||||
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
|
||||
this.suiteDivs[suite.id] = suiteDiv;
|
||||
var parentDiv = this.outerDiv;
|
||||
if (suite.parentSuite) {
|
||||
parentDiv = this.suiteDivs[suite.parentSuite.id];
|
||||
}
|
||||
parentDiv.appendChild(suiteDiv);
|
||||
}
|
||||
|
||||
this.startedAt = new Date();
|
||||
|
||||
var self = this;
|
||||
showPassed.onclick = function(evt) {
|
||||
if (showPassed.checked) {
|
||||
self.outerDiv.className += ' show-passed';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
|
||||
}
|
||||
};
|
||||
|
||||
showSkipped.onclick = function(evt) {
|
||||
if (showSkipped.checked) {
|
||||
self.outerDiv.className += ' show-skipped';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
|
||||
var results = runner.results();
|
||||
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
|
||||
this.runnerDiv.setAttribute("class", className);
|
||||
//do it twice for IE
|
||||
this.runnerDiv.setAttribute("className", className);
|
||||
var specs = runner.specs();
|
||||
var specCount = 0;
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
if (this.specFilter(specs[i])) {
|
||||
specCount++;
|
||||
}
|
||||
}
|
||||
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
|
||||
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
|
||||
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
|
||||
|
||||
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
|
||||
var results = suite.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.totalCount === 0) { // todo: change this to check results.skipped
|
||||
status = 'skipped';
|
||||
}
|
||||
this.suiteDivs[suite.id].className += " " + status;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
|
||||
if (this.logRunningSpecs) {
|
||||
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
|
||||
var results = spec.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
var specDiv = this.createDom('div', { className: 'spec ' + status },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(spec.getFullName()),
|
||||
title: spec.getFullName()
|
||||
}, spec.description));
|
||||
|
||||
|
||||
var resultItems = results.getItems();
|
||||
var messagesDiv = this.createDom('div', { className: 'messages' });
|
||||
for (var i = 0; i < resultItems.length; i++) {
|
||||
var result = resultItems[i];
|
||||
|
||||
if (result.type == 'log') {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
|
||||
} else if (result.type == 'expect' && result.passed && !result.passed()) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
||||
|
||||
if (result.trace.stack) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesDiv.childNodes.length > 0) {
|
||||
specDiv.appendChild(messagesDiv);
|
||||
}
|
||||
|
||||
this.suiteDivs[spec.suite.id].appendChild(specDiv);
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.log = function() {
|
||||
var console = jasmine.getGlobal().console;
|
||||
if (console && console.log) {
|
||||
if (console.log.apply) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.getLocation = function() {
|
||||
return this.document.location;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
|
||||
var paramMap = {};
|
||||
var params = this.getLocation().search.substring(1).split('&');
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
if (!paramMap.spec) {
|
||||
return true;
|
||||
}
|
||||
return spec.getFullName().indexOf(paramMap.spec) === 0;
|
||||
};
|
||||
82
inst/tests-js/lib/jasmine-1.3.1/jasmine.css
Normal file
82
inst/tests-js/lib/jasmine-1.3.1/jasmine.css
Normal file
@@ -0,0 +1,82 @@
|
||||
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
|
||||
|
||||
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
|
||||
#HTMLReporter a { text-decoration: none; }
|
||||
#HTMLReporter a:hover { text-decoration: underline; }
|
||||
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
|
||||
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
|
||||
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
|
||||
#HTMLReporter .version { color: #aaaaaa; }
|
||||
#HTMLReporter .banner { margin-top: 14px; }
|
||||
#HTMLReporter .duration { color: #aaaaaa; float: right; }
|
||||
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
|
||||
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
|
||||
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
|
||||
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
|
||||
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
|
||||
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
|
||||
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
|
||||
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
|
||||
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
|
||||
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
|
||||
#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
|
||||
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
|
||||
#HTMLReporter .runningAlert { background-color: #666666; }
|
||||
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
|
||||
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
|
||||
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
|
||||
#HTMLReporter .passingAlert { background-color: #a6b779; }
|
||||
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
|
||||
#HTMLReporter .failingAlert { background-color: #cf867e; }
|
||||
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
|
||||
#HTMLReporter .results { margin-top: 14px; }
|
||||
#HTMLReporter #details { display: none; }
|
||||
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
|
||||
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
|
||||
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
|
||||
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
|
||||
#HTMLReporter.showDetails .summary { display: none; }
|
||||
#HTMLReporter.showDetails #details { display: block; }
|
||||
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
|
||||
#HTMLReporter .summary { margin-top: 14px; }
|
||||
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
|
||||
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
|
||||
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
|
||||
#HTMLReporter .description + .suite { margin-top: 0; }
|
||||
#HTMLReporter .suite { margin-top: 14px; }
|
||||
#HTMLReporter .suite a { color: #333333; }
|
||||
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
|
||||
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
|
||||
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
|
||||
#HTMLReporter .resultMessage span.result { display: block; }
|
||||
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
|
||||
|
||||
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
|
||||
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
|
||||
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
|
||||
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
|
||||
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
|
||||
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
|
||||
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
|
||||
#TrivialReporter .runner.running { background-color: yellow; }
|
||||
#TrivialReporter .options { text-align: right; font-size: .8em; }
|
||||
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
|
||||
#TrivialReporter .suite .suite { margin: 5px; }
|
||||
#TrivialReporter .suite.passed { background-color: #dfd; }
|
||||
#TrivialReporter .suite.failed { background-color: #fdd; }
|
||||
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
|
||||
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
|
||||
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
|
||||
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
|
||||
#TrivialReporter .spec.skipped { background-color: #bbb; }
|
||||
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
|
||||
#TrivialReporter .passed { background-color: #cfc; display: none; }
|
||||
#TrivialReporter .failed { background-color: #fbb; }
|
||||
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
|
||||
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
|
||||
#TrivialReporter .resultMessage .mismatch { color: black; }
|
||||
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
|
||||
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
|
||||
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
|
||||
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
|
||||
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
|
||||
2600
inst/tests-js/lib/jasmine-1.3.1/jasmine.js
Normal file
2600
inst/tests-js/lib/jasmine-1.3.1/jasmine.js
Normal file
File diff suppressed because it is too large
Load Diff
1610
inst/tests-js/spec/inputBindingSpec.js
Normal file
1610
inst/tests-js/spec/inputBindingSpec.js
Normal file
File diff suppressed because it is too large
Load Diff
45
inst/tests/test-bootstrap.r
Normal file
45
inst/tests/test-bootstrap.r
Normal file
@@ -0,0 +1,45 @@
|
||||
context("bootstrap")
|
||||
|
||||
test_that("CSS unit validation", {
|
||||
# On error, return NA; on success, return result
|
||||
validateCssUnit_wrap <- function(x) {
|
||||
tryCatch(validateCssUnit(x), error = function(e) { NA_character_ })
|
||||
}
|
||||
|
||||
# Test strings and expected results
|
||||
strings <- c("100x", "10px", "10.4px", ".4px", "1px0", "px", "5", "%", "5%", "auto", "1auto", "")
|
||||
expected <- c(NA, "10px", "10.4px", ".4px", NA, NA, "5px", NA, "5%", "auto", NA, NA)
|
||||
results <- vapply(strings, validateCssUnit_wrap, character(1), USE.NAMES = FALSE)
|
||||
expect_equal(results, expected)
|
||||
|
||||
# Numbers should return string with "px"
|
||||
expect_equal(validateCssUnit(100), "100px")
|
||||
})
|
||||
|
||||
|
||||
test_that("Repeated names for selectInput and radioButtons choices", {
|
||||
# These test might be a bit too closely tied to the exact structure of the
|
||||
# tag object, but they get the job done for now.
|
||||
|
||||
# Select input
|
||||
x <- selectInput('id','label', choices = c(a='x1', a='x2', b='x3'), selectize = FALSE)
|
||||
expect_equal(format(x), '<label class="control-label" for="id">label</label>
|
||||
<select id="id"><option value="x1" selected>a</option>\n<option value="x2">a</option>\n<option value="x3">b</option></select>')
|
||||
|
||||
|
||||
# Radio buttons
|
||||
x <- radioButtons('id','label', choices = c(a='x1', a='x2', b='x3'))
|
||||
choices <- x$children
|
||||
|
||||
expect_equal(choices[[2]][[1]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]][[1]]$children[[1]]$attribs$value, 'x1')
|
||||
expect_equal(choices[[2]][[1]]$children[[1]]$attribs$checked, 'checked')
|
||||
|
||||
expect_equal(choices[[2]][[2]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]][[2]]$children[[1]]$attribs$value, 'x2')
|
||||
expect_equal(choices[[2]][[2]]$children[[1]]$attribs$checked, NULL)
|
||||
|
||||
expect_equal(choices[[2]][[3]]$children[[2]]$children[[1]], 'b')
|
||||
expect_equal(choices[[2]][[3]]$children[[1]]$attribs$value, 'x3')
|
||||
expect_equal(choices[[2]][[3]]$children[[1]]$attribs$checked, NULL)
|
||||
})
|
||||
73
inst/tests/test-gc.r
Normal file
73
inst/tests/test-gc.r
Normal file
@@ -0,0 +1,73 @@
|
||||
context("garbage collection")
|
||||
|
||||
test_that("unreferenced observers are garbage collected", {
|
||||
vals_removed <- FALSE
|
||||
obs_removed <- FALSE
|
||||
vals <- reactiveValues(A=1)
|
||||
obs <- observe({ vals$A })
|
||||
|
||||
# These are called when the objects are garbage-collected
|
||||
reg.finalizer(attr(.subset2(vals,'impl'), ".xData"),
|
||||
function(e) vals_removed <<- TRUE)
|
||||
reg.finalizer(attr(obs, ".xData"),
|
||||
function(e) obs_removed <<- TRUE)
|
||||
|
||||
flushReact()
|
||||
|
||||
# Removing this reference to obs doesn't delete it because vals still has a
|
||||
# reference to it
|
||||
rm(obs)
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
|
||||
|
||||
# Updating vals$A and flushing won't make obs go away because it creates a new
|
||||
# context, and vals$A's context tracks obs's context as a dependent
|
||||
vals$A <- 2
|
||||
flushReact()
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
|
||||
|
||||
# Removing vals will result in vals and obs being garbage collected since
|
||||
# there are no other references to them
|
||||
rm(vals)
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(TRUE, TRUE))
|
||||
})
|
||||
|
||||
|
||||
test_that("suspended observers are garbage collected", {
|
||||
vals_removed <- FALSE
|
||||
obs_removed <- FALSE
|
||||
vals <- reactiveValues(A=1)
|
||||
obs <- observe({ vals$A })
|
||||
|
||||
# These are called when the objects are garbage-collected
|
||||
reg.finalizer(attr(.subset2(vals,'impl'), ".xData"),
|
||||
function(e) vals_removed <<- TRUE)
|
||||
reg.finalizer(attr(obs, ".xData"),
|
||||
function(e) obs_removed <<- TRUE)
|
||||
|
||||
flushReact()
|
||||
|
||||
vals$A <- 2
|
||||
flushReact()
|
||||
invisible(gc())
|
||||
|
||||
# Simply suspending and removing our reference to obs doesn't result in GC,
|
||||
# because vals's context still has a reference to obs's context, as a dependent
|
||||
obs$suspend()
|
||||
rm(obs)
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
|
||||
|
||||
# Next time we update vals$A and flush, there's no more reference to obs
|
||||
vals$A <- 3
|
||||
flushReact()
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, TRUE))
|
||||
|
||||
# Deleting vals should work immediately now
|
||||
rm(vals)
|
||||
invisible(gc()) # Removes vals object
|
||||
expect_equal(c(vals_removed, obs_removed), c(TRUE, TRUE))
|
||||
})
|
||||
47
inst/tests/test-input-handler.R
Normal file
47
inst/tests/test-input-handler.R
Normal file
@@ -0,0 +1,47 @@
|
||||
context("Parse Shiny Input")
|
||||
|
||||
test_that("A new type can be registered successfully", {
|
||||
registerInputHandler("shiny.someType", function(){})
|
||||
})
|
||||
|
||||
test_that("A duplicated type throws", {
|
||||
expect_error({
|
||||
registerInputHandler("shiny.dupType", function(){})
|
||||
registerInputHandler("shiny.dupType", function(){})
|
||||
})
|
||||
})
|
||||
|
||||
test_that("Date converts to date", {
|
||||
x <- "2013/01/01"
|
||||
class(x) <- "shiny.date"
|
||||
handler <- inputHandlers$get('shiny.date')
|
||||
expect_identical(
|
||||
handler(x), as.Date(unclass(x))
|
||||
)
|
||||
})
|
||||
|
||||
test_that("List of dates converts to vector", {
|
||||
x <- list("2013/01/01", "2014/01/01")
|
||||
class(x) <- "shiny.date"
|
||||
handler <- inputHandlers$get('shiny.date')
|
||||
expect_identical(
|
||||
handler(x), as.Date(unlist(x))
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Matrix converts list of lists to matrix", {
|
||||
x <- list(a=1:3,b=4:6)
|
||||
class(x) <- "shiny.matrix"
|
||||
handler <- inputHandlers$get('shiny.matrix')
|
||||
expect_identical(
|
||||
handler(x), matrix(c(1:3,4:6), byrow=FALSE, ncol=2)
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Nulls are not converted to NAs in parsing", {
|
||||
msg <- charToRaw("{\"method\":\"init\",\"data\":{\"obs\":500,\"nullObs\":null}}")
|
||||
expect_identical(
|
||||
decodeMessage(msg),
|
||||
list(method="init", data=list(obs=500, nullObs=NULL))
|
||||
)
|
||||
})
|
||||
823
inst/tests/test-reactivity.r
Normal file
823
inst/tests/test-reactivity.r
Normal file
@@ -0,0 +1,823 @@
|
||||
context("reactivity")
|
||||
|
||||
|
||||
# Test for correct behavior of ReactiveValues
|
||||
test_that("ReactiveValues", {
|
||||
# Creation and indexing into ReactiveValues -------------------------------
|
||||
values <- reactiveValues()
|
||||
|
||||
# $ indexing
|
||||
values$a <- 3
|
||||
expect_equal(isolate(values$a), 3)
|
||||
|
||||
# [[ indexing
|
||||
values[['a']] <- 4
|
||||
expect_equal(isolate(values[['a']]), 4)
|
||||
|
||||
# Create with initialized values
|
||||
values <- reactiveValues(a=1, b=2)
|
||||
expect_equal(isolate(values$a), 1)
|
||||
expect_equal(isolate(values[['b']]), 2)
|
||||
|
||||
# NULL values -------------------------------------------------------------
|
||||
# Initializing with NULL value
|
||||
values <- reactiveValues(a=NULL, b=2)
|
||||
# a should exist and be NULL
|
||||
expect_equal(isolate(names(values)), c("a", "b"))
|
||||
expect_true(is.null(isolate(values$a)))
|
||||
|
||||
# Assigning NULL should keep object (not delete it), and set value to NULL
|
||||
values$b <- NULL
|
||||
expect_equal(isolate(names(values)), c("a", "b"))
|
||||
expect_true(is.null(isolate(values$b)))
|
||||
|
||||
|
||||
# Errors -----------------------------------------------------------------
|
||||
# Error: indexing with non-string
|
||||
expect_error(isolate(values[[1]]))
|
||||
expect_error(isolate(values[[NULL]]))
|
||||
expect_error(isolate(values[[list('a')]]))
|
||||
|
||||
# Error: [ indexing shouldn't work
|
||||
expect_error(isolate(values['a']))
|
||||
expect_error(isolate(values['a'] <- 1))
|
||||
|
||||
# Error: unnamed arguments
|
||||
expect_error(reactiveValues(1))
|
||||
expect_error(reactiveValues(1, b=2))
|
||||
|
||||
# Error: assignment to readonly values
|
||||
values <- .createReactiveValues(ReactiveValues$new(), readonly = TRUE)
|
||||
expect_error(values$a <- 1)
|
||||
})
|
||||
|
||||
|
||||
# Test for overreactivity. funcB has an indirect dependency on valueA (via
|
||||
# funcA) and also a direct dependency on valueA. When valueA changes, funcB
|
||||
# should only execute once.
|
||||
test_that("Functions are not over-reactive", {
|
||||
|
||||
values <- reactiveValues(A=10)
|
||||
|
||||
funcA <- reactive({
|
||||
values$A
|
||||
})
|
||||
|
||||
funcB <- reactive({
|
||||
funcA()
|
||||
values$A
|
||||
})
|
||||
|
||||
obsC <- observe({
|
||||
funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
|
||||
values$A <- 11
|
||||
flushReact()
|
||||
expect_equal(execCount(funcB), 2)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
})
|
||||
|
||||
## "foo => bar" is defined as "foo is a dependency of bar"
|
||||
##
|
||||
## vA => fB
|
||||
## (fB, vA) => obsE
|
||||
## (fB, vA) => obsF
|
||||
##
|
||||
## obsE and obsF should each execute once when vA changes.
|
||||
test_that("overreactivity2", {
|
||||
# ----------------------------------------------
|
||||
# Test 1
|
||||
# B depends on A, and observer depends on A and B. The observer uses A and
|
||||
# B, in that order.
|
||||
|
||||
# This is to store the value from observe()
|
||||
observed_value1 <- NA
|
||||
observed_value2 <- NA
|
||||
|
||||
values <- reactiveValues(A=1)
|
||||
funcB <- reactive({
|
||||
values$A + 5
|
||||
})
|
||||
obsC <- observe({
|
||||
observed_value1 <<- funcB() * values$A
|
||||
})
|
||||
obsD <- observe({
|
||||
observed_value2 <<- funcB() * values$A
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(observed_value1, 6) # Should be 1 * (1 + 5) = 6
|
||||
expect_equal(observed_value2, 6) # Should be 1 * (1 + 5) = 6
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
expect_equal(execCount(obsD), 1)
|
||||
|
||||
values$A <- 2
|
||||
flushReact()
|
||||
expect_equal(observed_value1, 14) # Should be 2 * (2 + 5) = 14
|
||||
expect_equal(observed_value2, 14) # Should be 2 * (2 + 5) = 14
|
||||
expect_equal(execCount(funcB), 2)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
expect_equal(execCount(obsD), 2)
|
||||
})
|
||||
|
||||
## Test for isolation. funcB depends on funcA depends on valueA. When funcA
|
||||
## is invalidated, if its new result is not different than its old result,
|
||||
## then it doesn't invalidate its dependents. This is done by adding an observer
|
||||
## (valueB) between obsA and funcC.
|
||||
##
|
||||
## valueA => obsB => valueC => funcD => obsE
|
||||
test_that("isolation", {
|
||||
values <- reactiveValues(A=10, C=NULL)
|
||||
|
||||
obsB <- observe({
|
||||
values$C <- values$A > 0
|
||||
})
|
||||
|
||||
funcD <- reactive({
|
||||
values$C
|
||||
})
|
||||
|
||||
obsE <- observe({
|
||||
funcD()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
countD <- execCount(funcD)
|
||||
|
||||
values$A <- 11
|
||||
flushReact()
|
||||
expect_equal(execCount(funcD), countD)
|
||||
})
|
||||
|
||||
|
||||
## Test for laziness. With lazy evaluation, the observers should "pull" values
|
||||
## from their dependent functions. In contrast, eager evaluation would have
|
||||
## reactive values and functions "push" their changes down to their descendents.
|
||||
test_that("laziness", {
|
||||
|
||||
values <- reactiveValues(A=10)
|
||||
|
||||
funcA <- reactive({
|
||||
values$A > 0
|
||||
})
|
||||
|
||||
funcB <- reactive({
|
||||
funcA()
|
||||
})
|
||||
|
||||
obsC <- observe({
|
||||
if (values$A > 10)
|
||||
return()
|
||||
funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
|
||||
values$A <- 11
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
})
|
||||
|
||||
|
||||
## Suppose B depends on A and C depends on A and B. Then when A is changed,
|
||||
## the evaluation order should be A, B, C. Also, each time A is changed, B and
|
||||
## C should be run once, if we want to be maximally efficient.
|
||||
test_that("order of evaluation", {
|
||||
# ----------------------------------------------
|
||||
# Test 1
|
||||
# B depends on A, and observer depends on A and B. The observer uses A and
|
||||
# B, in that order.
|
||||
|
||||
# This is to store the value from observe()
|
||||
observed_value <- NA
|
||||
|
||||
values <- reactiveValues(A=1)
|
||||
funcB <- reactive({
|
||||
values$A + 5
|
||||
})
|
||||
obsC <- observe({
|
||||
observed_value <<- values$A * funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(observed_value, 6) # Should be 1 * (1 + 5) = 6
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
|
||||
values$A <- 2
|
||||
flushReact()
|
||||
expect_equal(observed_value, 14) # Should be 2 * (2 + 5) = 14
|
||||
expect_equal(execCount(funcB), 2)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
|
||||
|
||||
# ----------------------------------------------
|
||||
# Test 2:
|
||||
# Same as Test 1, except the observer uses A and B in reversed order.
|
||||
# Resulting values should be the same.
|
||||
|
||||
observed_value <- NA
|
||||
|
||||
values <- reactiveValues(A=1)
|
||||
funcB <- reactive({
|
||||
values$A + 5
|
||||
})
|
||||
obsC <- observe({
|
||||
observed_value <<- funcB() * values$A
|
||||
})
|
||||
|
||||
flushReact()
|
||||
# Should be 1 * (1 + 5) = 6
|
||||
expect_equal(observed_value, 6)
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
|
||||
values$A <- 2
|
||||
flushReact()
|
||||
# Should be 2 * (2 + 5) = 14
|
||||
expect_equal(observed_value, 14)
|
||||
expect_equal(execCount(funcB), 2)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
})
|
||||
|
||||
|
||||
## Expressions in isolate() should not invalidate the parent context.
|
||||
test_that("isolate() blocks invalidations from propagating", {
|
||||
|
||||
obsC_value <- NA
|
||||
obsD_value <- NA
|
||||
|
||||
values <- reactiveValues(A=1, B=10)
|
||||
funcB <- reactive({
|
||||
values$B + 100
|
||||
})
|
||||
|
||||
# References to valueB and funcB are isolated
|
||||
obsC <- observe({
|
||||
obsC_value <<-
|
||||
values$A + isolate(values$B) + isolate(funcB())
|
||||
})
|
||||
|
||||
# In contrast with obsC, this has a non-isolated reference to funcB
|
||||
obsD <- observe({
|
||||
obsD_value <<-
|
||||
values$A + isolate(values$B) + funcB()
|
||||
})
|
||||
|
||||
|
||||
flushReact()
|
||||
expect_equal(obsC_value, 121)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
expect_equal(obsD_value, 121)
|
||||
expect_equal(execCount(obsD), 1)
|
||||
|
||||
# Changing A should invalidate obsC and obsD
|
||||
values$A <- 2
|
||||
flushReact()
|
||||
expect_equal(obsC_value, 122)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
expect_equal(obsD_value, 122)
|
||||
expect_equal(execCount(obsD), 2)
|
||||
|
||||
# Changing B shouldn't invalidate obsC becuause references to B are in isolate()
|
||||
# But it should invalidate obsD.
|
||||
values$B <- 20
|
||||
flushReact()
|
||||
expect_equal(obsC_value, 122)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
expect_equal(obsD_value, 142)
|
||||
expect_equal(execCount(obsD), 3)
|
||||
|
||||
# Changing A should invalidate obsC and obsD, and they should see updated
|
||||
# values for valueA, valueB, and funcB
|
||||
values$A <- 3
|
||||
flushReact()
|
||||
expect_equal(obsC_value, 143)
|
||||
expect_equal(execCount(obsC), 3)
|
||||
expect_equal(obsD_value, 143)
|
||||
expect_equal(execCount(obsD), 4)
|
||||
})
|
||||
|
||||
|
||||
test_that("isolate() evaluates expressions in calling environment", {
|
||||
outside <- 1
|
||||
inside <- 1
|
||||
loc <- 1
|
||||
|
||||
outside <- isolate(2) # Assignment outside isolate
|
||||
isolate(inside <- 2) # Assignment inside isolate
|
||||
# Should affect vars in the calling environment
|
||||
expect_equal(outside, 2)
|
||||
expect_equal(inside, 2)
|
||||
|
||||
isolate(local(loc <<- 2)) # <<- inside isolate(local)
|
||||
isolate(local(loc <- 3)) # <- inside isolate(local) - should have no effect
|
||||
expect_equal(loc, 2)
|
||||
})
|
||||
|
||||
|
||||
test_that("Circular refs/reentrancy in reactive functions work", {
|
||||
|
||||
values <- reactiveValues(A=3)
|
||||
|
||||
funcB <- reactive({
|
||||
# Each time fB executes, it reads and then writes valueA,
|
||||
# effectively invalidating itself--until valueA becomes 0.
|
||||
if (values$A == 0)
|
||||
return()
|
||||
values$A <- values$A - 1
|
||||
return(values$A)
|
||||
})
|
||||
|
||||
obsC <- observe({
|
||||
funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsC), 4)
|
||||
|
||||
values$A <- 3
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsC), 8)
|
||||
|
||||
})
|
||||
|
||||
test_that("Simple recursion", {
|
||||
|
||||
values <- reactiveValues(A=5)
|
||||
funcB <- reactive({
|
||||
if (values$A == 0)
|
||||
return(0)
|
||||
values$A <- values$A - 1
|
||||
funcB()
|
||||
})
|
||||
|
||||
obsC <- observe({
|
||||
funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsC), 2)
|
||||
expect_equal(execCount(funcB), 6)
|
||||
})
|
||||
|
||||
test_that("Non-reactive recursion", {
|
||||
nonreactiveA <- 3
|
||||
outputD <- NULL
|
||||
|
||||
funcB <- reactive({
|
||||
if (nonreactiveA == 0)
|
||||
return(0)
|
||||
nonreactiveA <<- nonreactiveA - 1
|
||||
return(funcB())
|
||||
})
|
||||
obsC <- observe({
|
||||
outputD <<- funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(funcB), 4)
|
||||
expect_equal(outputD, 0)
|
||||
})
|
||||
|
||||
test_that("Circular dep with observer only", {
|
||||
|
||||
values <- reactiveValues(A=3)
|
||||
obsB <- observe({
|
||||
if (values$A == 0)
|
||||
return()
|
||||
values$A <- values$A - 1
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsB), 4)
|
||||
})
|
||||
|
||||
test_that("Writing then reading value is not circular", {
|
||||
|
||||
values <- reactiveValues(A=3)
|
||||
funcB <- reactive({
|
||||
values$A <- isolate(values$A) - 1
|
||||
values$A
|
||||
})
|
||||
|
||||
obsC <- observe({
|
||||
funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsC), 1)
|
||||
|
||||
values$A <- 10
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsC), 2)
|
||||
})
|
||||
|
||||
test_that("names() and reactiveValuesToList()", {
|
||||
|
||||
values <- reactiveValues(A=1, .B=2)
|
||||
|
||||
# Dependent on names
|
||||
depNames <- observe({
|
||||
names(values)
|
||||
})
|
||||
|
||||
# Dependent on all non-hidden objects
|
||||
depValues <- observe({
|
||||
reactiveValuesToList(values)
|
||||
})
|
||||
|
||||
# Dependent on all objects, including hidden
|
||||
depAllValues <- observe({
|
||||
reactiveValuesToList(values, all.names = TRUE)
|
||||
})
|
||||
|
||||
# names() returns all names
|
||||
expect_equal(sort(isolate(names(values))), sort(c(".B", "A")))
|
||||
# Assigning names fails
|
||||
expect_error(isolate(names(v) <- c('x', 'y')))
|
||||
|
||||
expect_equal(isolate(reactiveValuesToList(values)), list(A=1))
|
||||
expect_equal(isolate(reactiveValuesToList(values, all.names=TRUE)), list(A=1, .B=2))
|
||||
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(depNames), 1)
|
||||
expect_equal(execCount(depValues), 1)
|
||||
expect_equal(execCount(depAllValues), 1)
|
||||
|
||||
# Update existing variable
|
||||
values$A <- 2
|
||||
flushReact()
|
||||
expect_equal(execCount(depNames), 1)
|
||||
expect_equal(execCount(depValues), 2)
|
||||
expect_equal(execCount(depAllValues), 2)
|
||||
|
||||
# Update existing hidden variable
|
||||
values$.B <- 3
|
||||
flushReact()
|
||||
expect_equal(execCount(depNames), 1)
|
||||
expect_equal(execCount(depValues), 2)
|
||||
expect_equal(execCount(depAllValues), 3)
|
||||
|
||||
# Add new variable
|
||||
values$C <- 1
|
||||
flushReact()
|
||||
expect_equal(execCount(depNames), 2)
|
||||
expect_equal(execCount(depValues), 3)
|
||||
expect_equal(execCount(depAllValues), 4)
|
||||
|
||||
# Add new hidden variable
|
||||
values$.D <- 1
|
||||
flushReact()
|
||||
expect_equal(execCount(depNames), 3)
|
||||
expect_equal(execCount(depValues), 3)
|
||||
expect_equal(execCount(depAllValues), 5)
|
||||
})
|
||||
|
||||
test_that("Observer pausing works", {
|
||||
values <- reactiveValues(a=1)
|
||||
|
||||
funcA <- reactive({
|
||||
values$a
|
||||
})
|
||||
|
||||
obsB <- observe({
|
||||
funcA()
|
||||
})
|
||||
|
||||
# Important: suspend() only affects observer at invalidation time
|
||||
|
||||
# Observers are invalidated at creation time, so it will run once regardless
|
||||
# of being suspended
|
||||
obsB$suspend()
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(obsB), 1)
|
||||
|
||||
# When resuming, if nothing changed, don't do anything
|
||||
obsB$resume()
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(obsB), 1)
|
||||
|
||||
# Make sure suspended observers do not flush, but do invalidate
|
||||
obsB_invalidated <- FALSE
|
||||
obsB$onInvalidate(function() {obsB_invalidated <<- TRUE})
|
||||
obsB$suspend()
|
||||
values$a <- 2
|
||||
flushReact()
|
||||
expect_equal(obsB_invalidated, TRUE)
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(obsB), 1)
|
||||
|
||||
obsB$resume()
|
||||
values$a <- 2.5
|
||||
obsB$suspend()
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 2)
|
||||
expect_equal(execCount(obsB), 2)
|
||||
|
||||
values$a <- 3
|
||||
flushReact()
|
||||
|
||||
expect_equal(execCount(funcA), 2)
|
||||
expect_equal(execCount(obsB), 2)
|
||||
|
||||
# If onInvalidate() is added _after_ obsB is suspended and the values$a
|
||||
# changes, then it shouldn't get run (onInvalidate runs on invalidation, not
|
||||
# on flush)
|
||||
values$a <- 4
|
||||
obsB_invalidated2 <- FALSE
|
||||
obsB$onInvalidate(function() {obsB_invalidated2 <<- TRUE})
|
||||
obsB$resume()
|
||||
flushReact()
|
||||
|
||||
expect_equal(execCount(funcA), 3)
|
||||
expect_equal(execCount(obsB), 3)
|
||||
expect_equal(obsB_invalidated2, FALSE)
|
||||
})
|
||||
|
||||
test_that("suspended/resumed observers run at most once", {
|
||||
|
||||
values <- reactiveValues(A=1)
|
||||
obs <- observe(function() {
|
||||
values$A
|
||||
})
|
||||
expect_equal(execCount(obs), 0)
|
||||
|
||||
# First flush should run obs once
|
||||
flushReact()
|
||||
expect_equal(execCount(obs), 1)
|
||||
|
||||
# Modify the dependency at each stage of suspend/resume/flush should still
|
||||
# only result in one run of obs()
|
||||
values$A <- 2
|
||||
obs$suspend()
|
||||
values$A <- 3
|
||||
obs$resume()
|
||||
values$A <- 4
|
||||
flushReact()
|
||||
expect_equal(execCount(obs), 2)
|
||||
|
||||
})
|
||||
|
||||
|
||||
test_that("reactive() accepts quoted and unquoted expressions", {
|
||||
vals <- reactiveValues(A=1)
|
||||
|
||||
# Unquoted expression, with curly braces
|
||||
fun <- reactive({ vals$A + 1 })
|
||||
expect_equal(isolate(fun()), 2)
|
||||
|
||||
# Unquoted expression, no curly braces
|
||||
fun <- reactive(vals$A + 1)
|
||||
expect_equal(isolate(fun()), 2)
|
||||
|
||||
# Quoted expression
|
||||
fun <- reactive(quote(vals$A + 1), quoted = TRUE)
|
||||
expect_equal(isolate(fun()), 2)
|
||||
|
||||
# Quoted expression, saved in a variable
|
||||
q_expr <- quote(vals$A + 1)
|
||||
fun <- reactive(q_expr, quoted = TRUE)
|
||||
expect_equal(isolate(fun()), 2)
|
||||
|
||||
# If function is used, work, but print message
|
||||
expect_message(fun <- reactive(function() { vals$A + 1 }))
|
||||
expect_equal(isolate(fun()), 2)
|
||||
|
||||
|
||||
# Check that environment is correct - parent environment should be this one
|
||||
this_env <- environment()
|
||||
fun <- reactive(environment())
|
||||
expect_identical(isolate(parent.env(fun())), this_env)
|
||||
|
||||
# Sanity check: environment structure for a reactive() should be the same as for
|
||||
# a normal function
|
||||
fun <- function() environment()
|
||||
expect_identical(parent.env(fun()), this_env)
|
||||
})
|
||||
|
||||
test_that("observe() accepts quoted and unquoted expressions", {
|
||||
vals <- reactiveValues(A=0)
|
||||
valB <- 0
|
||||
|
||||
# Unquoted expression, with curly braces
|
||||
observe({ valB <<- vals$A + 1})
|
||||
flushReact()
|
||||
expect_equal(valB, 1)
|
||||
|
||||
# Unquoted expression, no curly braces
|
||||
observe({ valB <<- vals$A + 2})
|
||||
flushReact()
|
||||
expect_equal(valB, 2)
|
||||
|
||||
# Quoted expression
|
||||
observe(quote(valB <<- vals$A + 3), quoted = TRUE)
|
||||
flushReact()
|
||||
expect_equal(valB, 3)
|
||||
|
||||
# Quoted expression, saved in a variable
|
||||
q_expr <- quote(valB <<- vals$A + 4)
|
||||
fun <- observe(q_expr, quoted = TRUE)
|
||||
flushReact()
|
||||
expect_equal(valB, 4)
|
||||
|
||||
# If function is used, work, but print message
|
||||
expect_message(observe(function() { valB <<- vals$A + 5 }))
|
||||
flushReact()
|
||||
expect_equal(valB, 5)
|
||||
|
||||
|
||||
# Check that environment is correct - parent environment should be this one
|
||||
this_env <- environment()
|
||||
inside_env <- NULL
|
||||
fun <- observe(inside_env <<- environment())
|
||||
flushReact()
|
||||
expect_identical(parent.env(inside_env), this_env)
|
||||
})
|
||||
|
||||
test_that("Observer priorities are respected", {
|
||||
results <- c()
|
||||
observe(results <<- c(results, 10), priority=10)
|
||||
observe(results <<- c(results, 30), priority=30)
|
||||
observe(results <<- c(results, 20), priority=20L)
|
||||
observe(results <<- c(results, 21), priority=20)
|
||||
observe(results <<- c(results, 22), priority=20L)
|
||||
|
||||
flushReact()
|
||||
|
||||
expect_identical(results, c(30, 20, 21, 22, 10))
|
||||
})
|
||||
|
||||
test_that("reactivePoll and reactiveFileReader", {
|
||||
path <- tempfile('file')
|
||||
on.exit(unlink(path))
|
||||
write.csv(cars, file=path, row.names=FALSE)
|
||||
rfr <- reactiveFileReader(100, NULL, path, read.csv)
|
||||
expect_equal(isolate(rfr()), cars)
|
||||
|
||||
write.csv(rbind(cars, cars), file=path, row.names=FALSE)
|
||||
Sys.sleep(0.15)
|
||||
timerCallbacks$executeElapsed()
|
||||
expect_equal(isolate(rfr()), cars)
|
||||
flushReact()
|
||||
expect_equal(isolate(rfr()), rbind(cars, cars))
|
||||
})
|
||||
|
||||
|
||||
test_that("classes of reactive object", {
|
||||
v <- reactiveValues(a = 1)
|
||||
r <- reactive({ v$a + 1 })
|
||||
o <- observe({ print(r()) })
|
||||
|
||||
expect_false(is.reactivevalues(12))
|
||||
expect_true(is.reactivevalues(v))
|
||||
expect_false(is.reactivevalues(r))
|
||||
expect_false(is.reactivevalues(o))
|
||||
|
||||
expect_false(is.reactive(12))
|
||||
expect_false(is.reactive(v))
|
||||
expect_true(is.reactive(r))
|
||||
expect_false(is.reactive(o))
|
||||
|
||||
o$destroy()
|
||||
})
|
||||
|
||||
test_that("{} and NULL also work in reactive()", {
|
||||
reactive({})
|
||||
reactive(NULL)
|
||||
})
|
||||
|
||||
test_that("shiny.suppressMissingContextError option works", {
|
||||
options(shiny.suppressMissingContextError=TRUE)
|
||||
on.exit(options(shiny.suppressMissingContextError=FALSE), add = TRUE)
|
||||
|
||||
expect_true(reactive(TRUE)())
|
||||
})
|
||||
|
||||
test_that("reactive domains are inherited", {
|
||||
|
||||
domainA <- createMockDomain()
|
||||
domainB <- createMockDomain()
|
||||
|
||||
local({
|
||||
domainY <- NULL
|
||||
domainZ <- NULL
|
||||
x <- observe({
|
||||
|
||||
y <- observe({
|
||||
# Should be domainA (inherited from observer x)
|
||||
domainY <<- getDefaultReactiveDomain()
|
||||
})
|
||||
|
||||
z <- observe({
|
||||
# Should be domainB (explicitly passed in)
|
||||
domainZ <<- getDefaultReactiveDomain()
|
||||
}, domain = domainB)
|
||||
|
||||
}, domain = domainA)
|
||||
|
||||
flushReact()
|
||||
flushReact()
|
||||
|
||||
expect_identical(domainY, domainA)
|
||||
expect_identical(domainZ, domainB)
|
||||
})
|
||||
|
||||
local({
|
||||
domainY <- 1
|
||||
x <- NULL
|
||||
y <- NULL
|
||||
z <- NULL
|
||||
r3 <- NULL
|
||||
domainR3 <- NULL
|
||||
|
||||
r1 <- reactive({
|
||||
y <<- observe({
|
||||
# Should be NULL (r1 has no domain)
|
||||
domainY <<- getDefaultReactiveDomain()
|
||||
})
|
||||
})
|
||||
r2 <- reactive({
|
||||
z <<- observe({
|
||||
# Should be domainB (r2 has explicit domainB)
|
||||
domainZ <<- getDefaultReactiveDomain()
|
||||
})
|
||||
}, domain = domainB)
|
||||
|
||||
observe({
|
||||
r3 <<- reactive({
|
||||
# This should be domainA. Doesn't matter where r3 is invoked, it only
|
||||
# matters where it was created.
|
||||
domainR3 <<- getDefaultReactiveDomain()
|
||||
})
|
||||
r1()
|
||||
r2()
|
||||
}, domain = domainA)
|
||||
|
||||
flushReact()
|
||||
flushReact()
|
||||
isolate(r3())
|
||||
|
||||
expect_identical(execCount(y), 1L)
|
||||
expect_identical(execCount(z), 1L)
|
||||
expect_identical(domainY, NULL)
|
||||
expect_identical(domainZ, domainB)
|
||||
expect_identical(domainR3, domainA)
|
||||
})
|
||||
})
|
||||
|
||||
test_that("observers autodestroy (or not)", {
|
||||
|
||||
domainA <- createMockDomain()
|
||||
local({
|
||||
a <- observe(NULL, domain = domainA)
|
||||
|
||||
b <- observe(NULL, domain = domainA, autoDestroy = FALSE)
|
||||
|
||||
c <- observe(NULL, domain = domainA)
|
||||
c$setAutoDestroy(FALSE)
|
||||
|
||||
d <- observe(NULL, domain = domainA, autoDestroy = FALSE)
|
||||
d$setAutoDestroy(TRUE)
|
||||
|
||||
e <- observe(NULL)
|
||||
|
||||
domainA$end()
|
||||
|
||||
flushReact()
|
||||
|
||||
expect_identical(execCount(a), 0L)
|
||||
expect_identical(execCount(b), 1L)
|
||||
expect_identical(execCount(c), 1L)
|
||||
expect_identical(execCount(d), 0L)
|
||||
expect_identical(execCount(e), 1L)
|
||||
})
|
||||
})
|
||||
|
||||
test_that("maskReactiveContext blocks use of reactives", {
|
||||
vals <- reactiveValues(x = 123)
|
||||
|
||||
# Block reactive contexts (created by isolate)
|
||||
expect_error(isolate(maskReactiveContext(vals$x)))
|
||||
expect_error(isolate(isolate(maskReactiveContext(vals$x))))
|
||||
|
||||
# Reactive contexts within maskReactiveContext shouldn't be blocked
|
||||
expect_identical(maskReactiveContext(isolate(vals$x)), 123)
|
||||
expect_identical(isolate(maskReactiveContext(isolate(vals$x))), 123)
|
||||
})
|
||||
35
inst/tests/test-staticdocs.R
Normal file
35
inst/tests/test-staticdocs.R
Normal file
@@ -0,0 +1,35 @@
|
||||
context("staticdocs")
|
||||
|
||||
test_that("All man pages have an entry in staticdocs/index.r", {
|
||||
if (!all(file.exists(c('../../inst/staticdocs', '../../man')))) {
|
||||
# This test works only when run against a package directory
|
||||
return()
|
||||
}
|
||||
# Known not to be indexed
|
||||
known_unindexed <- c("shiny-package", "knitr_methods")
|
||||
|
||||
indexed_topics <- local({
|
||||
result <- character(0)
|
||||
sd_section <- function(dummy1, dummy2, section_topics) {
|
||||
result <<- c(result, section_topics)
|
||||
}
|
||||
source("../../inst/staticdocs/index.r", local = TRUE)
|
||||
result
|
||||
})
|
||||
|
||||
all_topics <- sub("\\.Rd", "", list.files("../../man", pattern = "*.Rd"))
|
||||
|
||||
# This test ensures that every documented topic is included in
|
||||
# staticdocs/index.r, unless explicitly waived by specifying it
|
||||
# in the known_unindexed variable above.
|
||||
missing <- setdiff(sort(all_topics), sort(c(known_unindexed, indexed_topics)))
|
||||
unknown <- setdiff(sort(c(known_unindexed, indexed_topics)), sort(all_topics))
|
||||
expect_equal(length(missing), 0,
|
||||
info = paste("Functions missing from index:\n",
|
||||
paste(" ", missing, sep = "", collapse = "\n"),
|
||||
sep = ""))
|
||||
expect_equal(length(unknown), 0,
|
||||
info = paste("Unrecognized functions in index.r:\n",
|
||||
paste(" ", unknown, sep = "", collapse = "\n"),
|
||||
sep = ""))
|
||||
})
|
||||
56
inst/tests/test-text.R
Normal file
56
inst/tests/test-text.R
Normal file
@@ -0,0 +1,56 @@
|
||||
context("text")
|
||||
|
||||
test_that("renderPrint and renderText behavior is correct", {
|
||||
expect_equal(isolate(renderPrint({ "foo" })()),
|
||||
'[1] "foo"')
|
||||
expect_equal(isolate(renderPrint({ invisible("foo") })()),
|
||||
'')
|
||||
expect_equal(isolate(renderPrint({ print("foo"); "bar"})()),
|
||||
'[1] "foo"\n[1] "bar"')
|
||||
expect_equal(isolate(renderPrint({ NULL })()),
|
||||
'NULL')
|
||||
expect_equal(isolate(renderPrint({ invisible() })()),
|
||||
'')
|
||||
expect_equal(isolate(renderPrint({ 1:5 })()),
|
||||
'[1] 1 2 3 4 5')
|
||||
|
||||
expect_equal(isolate(renderText({ "foo" })()),
|
||||
'foo')
|
||||
expect_equal(isolate(renderText({ invisible("foo") })()),
|
||||
'foo')
|
||||
# Capture the print output so it's not shown on console during test, and
|
||||
# also check that it is correct
|
||||
print_out <- capture.output(ret <- isolate(renderText({ print("foo"); "bar"})()))
|
||||
expect_equal(ret, 'bar')
|
||||
expect_equal(print_out, '[1] "foo"')
|
||||
expect_equal(isolate(renderText({ NULL })()),
|
||||
'')
|
||||
expect_equal(isolate(renderText({ invisible() })()),
|
||||
'')
|
||||
expect_equal(isolate(renderText({ 1:5 })()),
|
||||
'1 2 3 4 5')
|
||||
})
|
||||
|
||||
test_that("reactive functions save visibility state", {
|
||||
# Call each function twice - should be no change in state with second call
|
||||
|
||||
# invisible NULL
|
||||
f <- reactive({ invisible() })
|
||||
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=FALSE))
|
||||
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=FALSE))
|
||||
|
||||
# visible NULL
|
||||
f <- reactive({ NULL })
|
||||
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=TRUE))
|
||||
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=TRUE))
|
||||
|
||||
# invisible non-NULL value
|
||||
f <- reactive({ invisible(10)})
|
||||
expect_identical(withVisible(isolate(f())), list(value=10, visible=FALSE))
|
||||
expect_identical(withVisible(isolate(f())), list(value=10, visible=FALSE))
|
||||
|
||||
# visible non-NULL value
|
||||
f <- reactive({ 10 })
|
||||
expect_identical(withVisible(isolate(f())), list(value=10, visible=TRUE))
|
||||
expect_identical(withVisible(isolate(f())), list(value=10, visible=TRUE))
|
||||
})
|
||||
20
inst/tests/test-url.R
Normal file
20
inst/tests/test-url.R
Normal file
@@ -0,0 +1,20 @@
|
||||
context("URL")
|
||||
|
||||
test_that("Query string parsing", {
|
||||
expect_identical(
|
||||
parseQueryString("?foo=1&bar=b+a%20r&b+a%20z=baz&=nokey&novalue=&=&noequal&end=end"),
|
||||
list(
|
||||
foo = '1',
|
||||
bar = 'b a r',
|
||||
`b a z` = 'baz',
|
||||
'nokey',
|
||||
novalue = '',
|
||||
'',
|
||||
noequal = '',
|
||||
end = 'end'
|
||||
)
|
||||
)
|
||||
|
||||
# Should be the same with or without leading question mark
|
||||
expect_identical(parseQueryString("?foo=1&bar=b"), parseQueryString("foo=1&bar=b"))
|
||||
})
|
||||
86
inst/tests/test-utils.R
Normal file
86
inst/tests/test-utils.R
Normal file
@@ -0,0 +1,86 @@
|
||||
context("utils")
|
||||
|
||||
test_that("Private randomness works at startup", {
|
||||
|
||||
if (exists(".Random.seed", envir = .GlobalEnv))
|
||||
rm(".Random.seed", envir = .GlobalEnv)
|
||||
.globals$ownSeed <- NULL
|
||||
# Just make sure this doesn't blow up
|
||||
createUniqueId(4)
|
||||
})
|
||||
|
||||
test_that("Setting process-wide seed doesn't affect private randomness", {
|
||||
set.seed(0)
|
||||
id1 <- createUniqueId(4)
|
||||
set.seed(0)
|
||||
id2 <- createUniqueId(4)
|
||||
|
||||
expect_false(identical(id1, id2))
|
||||
})
|
||||
|
||||
test_that("Resetting private seed doesn't result in dupes", {
|
||||
.globals$ownSeed <- NULL
|
||||
id3 <- createUniqueId(4)
|
||||
set.seed(0)
|
||||
.globals$ownSeed <- NULL
|
||||
id4 <- createUniqueId(4)
|
||||
|
||||
expect_false(identical(id3, id4))
|
||||
})
|
||||
|
||||
test_that("Clearing process-wide seed doesn't affect private randomness", {
|
||||
set.seed(NULL)
|
||||
id5 <- createUniqueId(4)
|
||||
set.seed(NULL)
|
||||
id6 <- createUniqueId(4)
|
||||
|
||||
expect_false(identical(id5, id6))
|
||||
})
|
||||
|
||||
test_that("Setting the private seed explicitly results in identical values", {
|
||||
set.seed(0)
|
||||
.globals$ownSeed <- .Random.seed
|
||||
id7 <- createUniqueId(4)
|
||||
set.seed(0)
|
||||
.globals$ownSeed <- .Random.seed
|
||||
id8 <- createUniqueId(4)
|
||||
|
||||
expect_identical(id7, id8)
|
||||
})
|
||||
|
||||
test_that("need() works as expected", {
|
||||
|
||||
# These are all falsy
|
||||
|
||||
expect_false(need(FALSE, FALSE))
|
||||
expect_false(need(NULL, FALSE))
|
||||
expect_false(need("", FALSE))
|
||||
|
||||
expect_false(need(character(0), FALSE))
|
||||
expect_false(need(logical(0), FALSE))
|
||||
expect_false(need(numeric(0), FALSE))
|
||||
expect_false(need(integer(0), FALSE))
|
||||
expect_false(need(complex(0), FALSE))
|
||||
expect_false(need(matrix(), FALSE))
|
||||
|
||||
expect_false(need(NA, FALSE))
|
||||
expect_false(need(NA_integer_, FALSE))
|
||||
expect_false(need(NA_real_, FALSE))
|
||||
expect_false(need(NA_complex_, FALSE))
|
||||
expect_false(need(NA_character_, FALSE))
|
||||
|
||||
expect_false(need(c(NA, NA, FALSE), FALSE))
|
||||
expect_false(need(c(FALSE), FALSE))
|
||||
|
||||
expect_false(need(try(stop("boom"), silent = TRUE), FALSE))
|
||||
|
||||
# These are all truthy
|
||||
|
||||
expect_null(need(0, FALSE))
|
||||
expect_null(need(1:10, FALSE))
|
||||
expect_null(need(LETTERS, FALSE))
|
||||
expect_null(need("NA", FALSE))
|
||||
expect_null(need(TRUE, FALSE))
|
||||
expect_null(need(c(NA, NA, TRUE), FALSE))
|
||||
expect_null(need(c(FALSE, FALSE, TRUE), FALSE))
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user