mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 23:48:01 -05:00
Compare commits
926 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8b09adda1 | ||
|
|
07c8f0c4b7 | ||
|
|
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 |
@@ -1,9 +1,13 @@
|
||||
^\.Rproj\.user$
|
||||
^\.git$
|
||||
^examples$
|
||||
^README\.md$
|
||||
^shiny\.Rproj$
|
||||
^shiny\.sh$
|
||||
^shiny\.cmd$
|
||||
^run\.R$
|
||||
^\.gitignore$
|
||||
^res$
|
||||
^tools$
|
||||
^man-roxygen$
|
||||
^\.travis\.yml$
|
||||
^staticdocs$
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,5 @@
|
||||
*.so
|
||||
/src-i386/
|
||||
/src-x86_64/
|
||||
shinyapps/
|
||||
README.html
|
||||
|
||||
26
.travis.yml
Normal file
26
.travis.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
# 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('xtable', repos = '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
|
||||
46
DESCRIPTION
46
DESCRIPTION
@@ -1,44 +1,56 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 0.1.12
|
||||
Date: 2012-11-21
|
||||
Version: 0.9.0
|
||||
Date: 2014-03-18
|
||||
Author: RStudio, Inc.
|
||||
Maintainer: Joe Cheng <joe@rstudio.org>
|
||||
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)
|
||||
R (>= 2.14.1),
|
||||
methods
|
||||
Imports:
|
||||
stats,
|
||||
tools,
|
||||
utils,
|
||||
datasets,
|
||||
methods,
|
||||
websockets (>= 1.1.6),
|
||||
httpuv (>= 1.2.0),
|
||||
caTools,
|
||||
RJSONIO,
|
||||
xtable,
|
||||
digest
|
||||
Suggests:
|
||||
markdown
|
||||
URL: https://github.com/rstudio/shiny, http://rstudio.github.com/shiny/tutorial
|
||||
datasets,
|
||||
markdown,
|
||||
Cairo (>= 1.5-5),
|
||||
testthat
|
||||
URL: http://www.rstudio.com/shiny/
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Collate:
|
||||
'map.R'
|
||||
'utils.R'
|
||||
'tar.R'
|
||||
'timer.R'
|
||||
'tags.R'
|
||||
'bootstrap-layout.R'
|
||||
'bootstrap.R'
|
||||
'cache.R'
|
||||
'map.R'
|
||||
'fileupload.R'
|
||||
'graph.R'
|
||||
'hooks.R'
|
||||
'imageutils.R'
|
||||
'jqueryui.R'
|
||||
'priorityqueue.R'
|
||||
'react.R'
|
||||
'reactives.R'
|
||||
'fileupload.R'
|
||||
'run-url.R'
|
||||
'sessioncontext.R'
|
||||
'utils.R'
|
||||
'shiny.R'
|
||||
'shinywrappers.R'
|
||||
'shinyui.R'
|
||||
'shinywrappers.R'
|
||||
'showcase.R'
|
||||
'slider.R'
|
||||
'bootstrap.R'
|
||||
'tags.R'
|
||||
'tar.R'
|
||||
'timer.R'
|
||||
'update-input.R'
|
||||
|
||||
113
NAMESPACE
113
NAMESPACE
@@ -1,18 +1,58 @@
|
||||
# Generated by roxygen2 (4.0.0): 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.character,shiny.tag)
|
||||
S3method(as.character,shiny.tag.list)
|
||||
S3method(as.list,reactivevalues)
|
||||
S3method(format,html)
|
||||
S3method(format,shiny.tag)
|
||||
S3method(format,shiny.tag.list)
|
||||
S3method(names,reactivevalues)
|
||||
S3method(print,html)
|
||||
S3method(print,reactive)
|
||||
S3method(print,shiny.tag)
|
||||
S3method(print,shiny.tag.list)
|
||||
export(HTML)
|
||||
export(a)
|
||||
export(absolutePanel)
|
||||
export(actionButton)
|
||||
export(addResourcePath)
|
||||
export(animationOptions)
|
||||
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(fluidPage)
|
||||
export(fluidRow)
|
||||
export(h1)
|
||||
export(h2)
|
||||
export(h3)
|
||||
@@ -21,67 +61,108 @@ 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(installExprFunction)
|
||||
export(invalidateLater)
|
||||
export(is.reactive)
|
||||
export(is.reactivevalues)
|
||||
export(isolate)
|
||||
export(mainPanel)
|
||||
export(makeReactiveBinding)
|
||||
export(navbarMenu)
|
||||
export(navbarPage)
|
||||
export(navlistPanel)
|
||||
export(numericInput)
|
||||
export(observe)
|
||||
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(shinyServer)
|
||||
export(shinyUI)
|
||||
export(showReactLog)
|
||||
export(sidebarLayout)
|
||||
export(sidebarPanel)
|
||||
export(singleton)
|
||||
export(sliderInput)
|
||||
export(span)
|
||||
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(updateSliderInput)
|
||||
export(updateTabsetPanel)
|
||||
export(updateTextInput)
|
||||
export(validateCssUnit)
|
||||
export(verbatimTextOutput)
|
||||
export(verticalLayout)
|
||||
export(wellPanel)
|
||||
export(withMathJax)
|
||||
export(withTags)
|
||||
import(RJSONIO)
|
||||
import(caTools)
|
||||
import(digest)
|
||||
import(RJSONIO)
|
||||
import(websockets)
|
||||
import(httpuv)
|
||||
import(methods)
|
||||
import(xtable)
|
||||
S3method(as.character,shiny.tag)
|
||||
S3method(as.character,shiny.tag.list)
|
||||
S3method(as.list,reactvaluesreader)
|
||||
S3method(format,shiny.tag)
|
||||
S3method(format,shiny.tag.list)
|
||||
S3method(names,reactvaluesreader)
|
||||
S3method(print,shiny.tag)
|
||||
S3method(print,shiny.tag.list)
|
||||
S3method(reactive,default)
|
||||
S3method(reactive,"function")
|
||||
S3method("$",reactvaluesreader)
|
||||
S3method("$<-",shinyoutput)
|
||||
|
||||
421
NEWS
421
NEWS
@@ -1,3 +1,418 @@
|
||||
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
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -42,7 +457,7 @@ shiny 0.1.8
|
||||
* 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 Twitter Bootstrap based
|
||||
* Add `bootstrapPage` function for creating new Bootstrap based
|
||||
layouts from scratch.
|
||||
|
||||
|
||||
@@ -104,11 +519,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!
|
||||
|
||||
310
R/bootstrap-layout.R
Normal file
310
R/bootstrap-layout.R
Normal file
@@ -0,0 +1,310 @@
|
||||
|
||||
#' 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)
|
||||
}
|
||||
|
||||
#' Layout 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}}
|
||||
#'
|
||||
#' @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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
1584
R/bootstrap.R
1584
R/bootstrap.R
File diff suppressed because it is too large
Load Diff
20
R/cache.R
20
R/cache.R
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
# 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(
|
||||
@@ -17,7 +17,7 @@ CacheContext <- setRefClass(
|
||||
addDependencyFile = function(file) {
|
||||
if (.dirty)
|
||||
return()
|
||||
|
||||
|
||||
file <- normalizePath(file)
|
||||
|
||||
mtime <- file.info(file)$mtime
|
||||
@@ -37,14 +37,14 @@ CacheContext <- setRefClass(
|
||||
isDirty = function() {
|
||||
if (.dirty)
|
||||
return(TRUE)
|
||||
|
||||
|
||||
for (test in .tests) {
|
||||
if (test()) {
|
||||
forceDirty()
|
||||
return(TRUE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return(FALSE)
|
||||
},
|
||||
reset = function() {
|
||||
@@ -55,7 +55,7 @@ CacheContext <- setRefClass(
|
||||
oldCC <- .currentCacheContext$cc
|
||||
.currentCacheContext$cc <- .self
|
||||
on.exit(.currentCacheContext$cc <- oldCC)
|
||||
|
||||
|
||||
return(func())
|
||||
}
|
||||
)
|
||||
@@ -63,16 +63,16 @@ CacheContext <- setRefClass(
|
||||
|
||||
.currentCacheContext <- new.env()
|
||||
|
||||
# Indicates to Shiny that the given file path is part of the dependency graph
|
||||
# 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))
|
||||
stop("addFileDependency was called at an unexpected time (no cache context found)")
|
||||
|
||||
return()
|
||||
|
||||
if (is.null(filepath) || is.na(filepath))
|
||||
.currentCacheContext$cc$forceDirty()
|
||||
else
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# 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
|
||||
@@ -28,26 +28,38 @@ FileUploadOperation <- setRefClass(
|
||||
.files = 'data.frame',
|
||||
.dir = 'character',
|
||||
.currentFileInfo = 'list',
|
||||
.currentFileData = 'ANY'
|
||||
.currentFileData = 'ANY',
|
||||
.pendingFileInfos = 'list'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function(parent, id, dir) {
|
||||
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(file) {
|
||||
.currentFileInfo <<- file
|
||||
fileBegin = function() {
|
||||
if (length(.pendingFileInfos) < 1)
|
||||
stop("fileBegin called too many times")
|
||||
|
||||
filename <- file.path(.dir, as.character(length(.files)))
|
||||
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) == 0)
|
||||
|
||||
if (length(.files$name) == 0)
|
||||
.files <<- row
|
||||
else
|
||||
.files <<- rbind(.files, row)
|
||||
|
||||
|
||||
.currentFileData <<- file(filename, open='wb')
|
||||
},
|
||||
fileChunk = function(rawdata) {
|
||||
@@ -57,12 +69,15 @@ FileUploadOperation <- setRefClass(
|
||||
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(
|
||||
@@ -73,14 +88,14 @@ FileUploadContext <- setRefClass(
|
||||
initialize = function(dir=tempdir()) {
|
||||
.basedir <<- dir
|
||||
},
|
||||
createUploadOperation = function() {
|
||||
createUploadOperation = function(fileInfos) {
|
||||
while (TRUE) {
|
||||
id <- paste(as.raw(runif(12, min=0, max=0xFF)), collapse='')
|
||||
dir <- file.path(.basedir, id)
|
||||
if (!dir.create(dir))
|
||||
next
|
||||
|
||||
op <- FileUploadOperation$new(.self, id, dir)
|
||||
|
||||
op <- FileUploadOperation$new(.self, id, dir, fileInfos)
|
||||
.operations$set(id, op)
|
||||
return(id)
|
||||
}
|
||||
|
||||
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) {
|
||||
if (isTRUE(getOption('shiny.reactlog', FALSE)))
|
||||
.graphEnv$log <- c(.graphEnv$log, list(logEntry))
|
||||
session <- .getShowcaseSessionContext()
|
||||
if (!is.null(session)) {
|
||||
session$.sendCustomMessage("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) {
|
||||
.graphAppend(list(
|
||||
action='ctx', id=id, label=paste(label, collapse='\n'),
|
||||
srcref=attr(label, "srcref"), srcfile=attr(label, "srcfile"),
|
||||
type=type, prevId=prevId
|
||||
))
|
||||
}
|
||||
|
||||
.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) {
|
||||
.graphAppend(list(action='invalidate', id=id))
|
||||
}
|
||||
|
||||
.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
|
||||
}
|
||||
49
R/imageutils.R
Normal file
49
R/imageutils.R
Normal file
@@ -0,0 +1,49 @@
|
||||
#' 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, ...)
|
||||
dv <- dev.cur()
|
||||
tryCatch(shinyCallingHandlers(func()), finally = dev.off(dv))
|
||||
|
||||
filename
|
||||
}
|
||||
102
R/jqueryui.R
Normal file
102
R/jqueryui.R
Normal file
@@ -0,0 +1,102 @@
|
||||
#' 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(
|
||||
singleton(tags$head(tags$script(src='shared/jqueryui/1.10.3/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)
|
||||
}
|
||||
9
R/map.R
9
R/map.R
@@ -61,15 +61,6 @@ 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,
|
||||
|
||||
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
|
||||
102
R/react.R
102
R/react.R
@@ -2,38 +2,38 @@ Context <- setRefClass(
|
||||
'Context',
|
||||
fields = list(
|
||||
id = 'character',
|
||||
.label = 'character', # For debug purposes
|
||||
.invalidated = 'logical',
|
||||
.callbacks = 'list',
|
||||
.hintCallbacks = 'list'
|
||||
.invalidateCallbacks = 'list',
|
||||
.flushCallbacks = 'list'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
initialize = function(label='', type='other', prevId='') {
|
||||
id <<- .getReactiveEnvironment()$nextId()
|
||||
.invalidated <<- FALSE
|
||||
.callbacks <<- list()
|
||||
.hintCallbacks <<- list()
|
||||
.invalidateCallbacks <<- list()
|
||||
.flushCallbacks <<- list()
|
||||
.label <<- label
|
||||
.graphCreateContext(id, label, type, prevId)
|
||||
},
|
||||
run = function(func) {
|
||||
"Run the provided function under this context."
|
||||
env <- .getReactiveEnvironment()
|
||||
.graphEnterContext(id)
|
||||
on.exit(.graphExitContext(id))
|
||||
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()
|
||||
})
|
||||
},
|
||||
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 <<- TRUE
|
||||
.getReactiveEnvironment()$addPendingInvalidate(.self)
|
||||
|
||||
.graphInvalidate(id)
|
||||
lapply(.invalidateCallbacks, function(func) {
|
||||
func()
|
||||
})
|
||||
NULL
|
||||
},
|
||||
onInvalidate = function(func) {
|
||||
@@ -43,15 +43,21 @@ 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) {
|
||||
lapply(.flushCallbacks, function(func) {
|
||||
withCallingHandlers({
|
||||
func()
|
||||
}, warning = function(e) {
|
||||
@@ -66,51 +72,61 @@ 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(dummyContext)
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
.reactiveEnvironment <- ReactiveEnvironment$new()
|
||||
.getReactiveEnvironment <- function() {
|
||||
if (!exists('.ReactiveEnvironment', envir=.GlobalEnv, inherits=FALSE)) {
|
||||
assign('.ReactiveEnvironment', ReactiveEnvironment$new(), envir=.GlobalEnv)
|
||||
}
|
||||
get('.ReactiveEnvironment', envir=.GlobalEnv, inherits=FALSE)
|
||||
.reactiveEnvironment
|
||||
}
|
||||
|
||||
# Causes any pending invalidations to run.
|
||||
@@ -123,3 +139,5 @@ flushReact <- function() {
|
||||
getCurrentContext <- function() {
|
||||
.getReactiveEnvironment()$currentContext()
|
||||
}
|
||||
|
||||
delayedAssign("dummyContext", Context$new('[none]', type='isolate'))
|
||||
|
||||
1062
R/reactives.R
1062
R/reactives.R
File diff suppressed because it is too large
Load Diff
164
R/run-url.R
Normal file
164
R/run-url.R
Normal file
@@ -0,0 +1,164 @@
|
||||
#' 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)
|
||||
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.
|
||||
dirname <- untar2(filePath, list=TRUE)[1]
|
||||
untar2(filePath, exdir = dirname(filePath))
|
||||
|
||||
} else if (fileext == ".zip") {
|
||||
dirname <- as.character(unzip(filePath, list=TRUE)$Name[1])
|
||||
unzip(filePath, exdir = dirname(filePath))
|
||||
}
|
||||
|
||||
appdir <- file.path(dirname(filePath), dirname)
|
||||
on.exit(unlink(appdir, recursive = TRUE), add = TRUE)
|
||||
|
||||
appsubdir <- ifelse(is.null(subdir), appdir, file.path(appdir, subdir))
|
||||
runApp(appsubdir, port=port, launch.browser=launch.browser)
|
||||
}
|
||||
20
R/sessioncontext.R
Normal file
20
R/sessioncontext.R
Normal file
@@ -0,0 +1,20 @@
|
||||
# Keeps the context associated with a ShinySession reference object for the
|
||||
# duration of a request. Used to emit reactive evaluation information to the
|
||||
# appropriate session when showcase mode is enabled.
|
||||
|
||||
.sessionContext <- new.env(parent=emptyenv())
|
||||
.beginShowcaseSessionContext <- function(session) {
|
||||
assign("session", session, envir = .sessionContext)
|
||||
}
|
||||
|
||||
.endShowcaseSessionContext <- function() {
|
||||
if (exists("session", where = .sessionContext))
|
||||
remove("session", envir = .sessionContext)
|
||||
}
|
||||
|
||||
.getShowcaseSessionContext <- function() {
|
||||
if (exists("session", where = .sessionContext))
|
||||
.sessionContext$session
|
||||
else
|
||||
NULL
|
||||
}
|
||||
228
R/shinyui.R
228
R/shinyui.R
@@ -1,52 +1,95 @@
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
p <- function(...) tags$p(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h1 <- function(...) tags$h1(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h2 <- function(...) tags$h2(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h3 <- function(...) tags$h3(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h4 <- function(...) tags$h4(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h5 <- function(...) tags$h5(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h6 <- function(...) tags$h6(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
a <- function(...) tags$a(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
br <- function(...) tags$br(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
div <- function(...) tags$div(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
span <- function(...) tags$span(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
pre <- function(...) tags$pre(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
code <- function(...) tags$code(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
img <- function(...) tags$img(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
strong <- function(...) tags$strong(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
em <- function(...) tags$em(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
hr <- function(...) tags$hr(...)
|
||||
|
||||
#' Include Content From a File
|
||||
#'
|
||||
#' Include HTML, text, or rendered Markdown into a \link[=shinyUI]{Shiny UI}.
|
||||
#'
|
||||
#' These functions provide a convenient way to include an extensive amount of
|
||||
#' HTML, textual, Markdown, CSS, or JavaScript content, rather than using a
|
||||
#' large literal R string.
|
||||
#'
|
||||
#' @note \code{includeText} escapes its contents, but does no other processing.
|
||||
#' This means that hard breaks and multiple spaces will be rendered as they
|
||||
#' usually are in HTML: as a single space character. If you are looking for
|
||||
#' preformatted text, wrap the call with \code{\link{pre}}, or consider using
|
||||
#' \code{includeMarkdown} instead.
|
||||
#'
|
||||
#' @note The \code{includeMarkdown} function requires the \code{markdown}
|
||||
#' package.
|
||||
#'
|
||||
#' @param path The path of the file to be included. It is highly recommended to
|
||||
#' use a relative path (the base path being the Shiny application directory),
|
||||
#' not an absolute path.
|
||||
#'
|
||||
#' @rdname include
|
||||
#' @name include
|
||||
#' @aliases includeHTML
|
||||
#' @export
|
||||
includeHTML <- function(path) {
|
||||
dependsOnFile(path)
|
||||
@@ -54,33 +97,81 @@ includeHTML <- function(path) {
|
||||
return(HTML(paste(lines, collapse='\r\n')))
|
||||
}
|
||||
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeText <- function(path) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
return(HTML(paste(lines, collapse='\r\n')))
|
||||
return(paste(lines, collapse='\r\n'))
|
||||
}
|
||||
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeMarkdown <- function(path) {
|
||||
if (!require(markdown))
|
||||
stop("Markdown package is not installed")
|
||||
|
||||
|
||||
dependsOnFile(path)
|
||||
html <- markdown::markdownToHTML(path, fragment.only=TRUE)
|
||||
Encoding(html) <- 'UTF-8'
|
||||
return(HTML(html))
|
||||
}
|
||||
|
||||
#' @param ... Any additional attributes to be applied to the generated tag.
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeCSS <- function(path, ...) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
args <- list(...)
|
||||
if (is.null(args$type))
|
||||
args$type <- 'text/css'
|
||||
return(do.call(tags$style,
|
||||
c(list(HTML(paste(lines, collapse='\r\n'))), args)))
|
||||
}
|
||||
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeScript <- function(path, ...) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
return(tags$script(HTML(paste(lines, collapse='\r\n')), ...))
|
||||
}
|
||||
|
||||
#' 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
|
||||
#' @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(HTML('window.MathJax = {skipStartupTypeset: true};'))),
|
||||
singleton(tags$script(src = path, type = 'text/javascript'))
|
||||
),
|
||||
...,
|
||||
tags$script(HTML('$(function() {
|
||||
setTimeout(function() {MathJax.Hub.Typeset();}, 200);
|
||||
});'))
|
||||
)
|
||||
}
|
||||
|
||||
#' 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
|
||||
#' 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.
|
||||
#'
|
||||
#' @export
|
||||
@@ -89,56 +180,40 @@ singleton <- function(x) {
|
||||
return(x)
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
result <- renderTags(ui)
|
||||
|
||||
# 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"/>',
|
||||
' <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"/>',
|
||||
sprintf(' <script type="application/shiny-singletons">%s</script>',
|
||||
paste(result$singletons, collapse = ',')
|
||||
)),
|
||||
con = connection)
|
||||
if (showcase > 0) {
|
||||
writeLines(as.character(showcaseHead()), 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>'),
|
||||
@@ -146,35 +221,35 @@ renderPage <- function(ui, connection) {
|
||||
}
|
||||
|
||||
#' Create a Shiny UI handler
|
||||
#'
|
||||
#' Register a UI handler by providing a UI definition (created with e.g.
|
||||
#'
|
||||
#' 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,
|
||||
#' sliderInput("obs",
|
||||
#' "Number of observations:",
|
||||
#' min = 0,
|
||||
#' max = 1000,
|
||||
#' value = 500)
|
||||
#' ),
|
||||
#'
|
||||
#'
|
||||
#' # Show a plot of the generated distribution
|
||||
#' mainPanel(
|
||||
#' plotOutput("distPlot")
|
||||
@@ -183,19 +258,28 @@ renderPage <- function(ui, connection) {
|
||||
#'
|
||||
#' @export
|
||||
shinyUI <- function(ui, path='/') {
|
||||
|
||||
|
||||
force(ui)
|
||||
|
||||
|
||||
registerClient({
|
||||
|
||||
function(ws, header) {
|
||||
if (header$RESOURCE != path)
|
||||
|
||||
function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
|
||||
if (req$PATH_INFO != path)
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
on.exit(close(textConn))
|
||||
|
||||
renderPage(ui, textConn)
|
||||
|
||||
showcaseMode <- .globals$showcaseDefault
|
||||
if (.globals$showcaseOverride) {
|
||||
mode <- showcaseModeOfReq(req)
|
||||
if (!is.null(mode))
|
||||
showcaseMode <- mode
|
||||
}
|
||||
renderPage(ui, textConn, showcaseMode)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(httpResponse(200, content=html))
|
||||
}
|
||||
|
||||
@@ -1,214 +1,453 @@
|
||||
suppressPackageStartupMessages({
|
||||
library(caTools)
|
||||
library(xtable)
|
||||
})
|
||||
globalVariables('func')
|
||||
|
||||
#' Plot Output
|
||||
#'
|
||||
#' Creates a reactive plot that is suitable for assigning to an \code{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}.
|
||||
#'
|
||||
#' @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.
|
||||
#' 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
|
||||
#'
|
||||
#' @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
|
||||
#' 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 ... Arguments to be passed through to \code{\link[grDevices]{png}}.
|
||||
#' @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
|
||||
reactivePlot <- function(func, width='auto', height='auto', ...) {
|
||||
args <- list(...)
|
||||
|
||||
if (is.function(width))
|
||||
width <- reactive(width)
|
||||
if (is.function(height))
|
||||
height <- reactive(height)
|
||||
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
|
||||
|
||||
return(function(shinysession, name, ...) {
|
||||
if (!is.null(widthWrapper))
|
||||
width <- widthWrapper()
|
||||
if (!is.null(heightWrapper))
|
||||
height <- heightWrapper()
|
||||
|
||||
return(function(shinyapp, name, ...) {
|
||||
png.file <- tempfile(fileext='.png')
|
||||
|
||||
if (is.function(width))
|
||||
width <- width()
|
||||
if (is.function(height))
|
||||
height <- height()
|
||||
|
||||
# Note that these are reactive calls. A change to the width and height
|
||||
# will inherently cause a reactive plot to redraw (unless width and
|
||||
# will inherently cause a reactive plot to redraw (unless width and
|
||||
# height were explicitly specified).
|
||||
prefix <- '.shinyout_'
|
||||
prefix <- 'output_'
|
||||
if (width == 'auto')
|
||||
width <- shinyapp$session$get(paste(prefix, name, '_width', sep=''));
|
||||
width <- shinysession$clientData[[paste(prefix, name, '_width', sep='')]];
|
||||
if (height == 'auto')
|
||||
height <- shinyapp$session$get(paste(prefix, name, '_height', sep=''));
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
height <- shinysession$clientData[[paste(prefix, name, '_height', sep='')]];
|
||||
|
||||
if (is.null(width) || is.null(height) || 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)
|
||||
|
||||
pngData <- readBin(png.file, 'raw', n=bytes)
|
||||
if (shinyapp$allowDataUriScheme) {
|
||||
b64 <- base64encode(pngData)
|
||||
return(paste("data:image/png;base64,", b64, sep=''))
|
||||
}
|
||||
else {
|
||||
imageUrl <- shinyapp$savePlot(name, pngData, 'image/png')
|
||||
return(imageUrl)
|
||||
|
||||
# Resolution multiplier
|
||||
pixelratio <- shinysession$clientData$pixelratio
|
||||
if (is.null(pixelratio))
|
||||
pixelratio <- 1
|
||||
|
||||
coordmap <- NULL
|
||||
plotFunc <- function() {
|
||||
# Actually perform the plotting
|
||||
func()
|
||||
|
||||
# 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
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#' Table Output
|
||||
#'
|
||||
#' Creates a reactive table that is suitable for assigning to an \code{output}
|
||||
#' 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(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}} 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)
|
||||
}
|
||||
|
||||
function() {
|
||||
classNames <- getOption('shiny.table.class', 'data table table-bordered table-condensed')
|
||||
data <- func()
|
||||
|
||||
if (is.null(data) || is.na(data))
|
||||
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, TRUE),
|
||||
'"',
|
||||
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).
|
||||
#'
|
||||
#' @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) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPrint: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
function() {
|
||||
return(paste(capture.output({
|
||||
result <- withVisible(func())
|
||||
if (result$visible)
|
||||
print(result$value)
|
||||
}), 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
|
||||
reactiveText <- function(func) {
|
||||
reactive(function() {
|
||||
return(paste(capture.output(cat(func())), collapse="\n"))
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
||||
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 func A function that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#'
|
||||
#' @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 <- reactiveUI(function() {
|
||||
#' output$moreControls <- renderUI({
|
||||
#' list(
|
||||
#'
|
||||
#'
|
||||
#' )
|
||||
#' })
|
||||
#' }
|
||||
reactiveUI <- function(func) {
|
||||
reactive(function() {
|
||||
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)
|
||||
}
|
||||
|
||||
function(shinysession, name, ...) {
|
||||
result <- func()
|
||||
if (is.null(result) || length(result) == 0)
|
||||
return(NULL)
|
||||
return(as.character(result))
|
||||
})
|
||||
|
||||
result <- takeSingletons(result, shinysession$singletons, desingleton=FALSE)$ui
|
||||
result <- surroundSingletons(result)
|
||||
|
||||
# renderTags returns a list with head, singletons, and html
|
||||
output <- doRenderTags(result)
|
||||
|
||||
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
|
||||
#' 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
|
||||
#'
|
||||
#' @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{con} that is a
|
||||
#' file connection opened in mode \code{wb}, and writes the content of the
|
||||
#' download into the connection. (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
|
||||
#' @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:
|
||||
@@ -216,18 +455,123 @@ reactiveUI <- function(func) {
|
||||
#' filename = function() {
|
||||
#' paste('data-', Sys.Date(), '.csv', sep='')
|
||||
#' },
|
||||
#' content = function(con) {
|
||||
#' write.csv(data, con)
|
||||
#' content = function(file) {
|
||||
#' write.csv(data, file)
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#'
|
||||
#' # In ui.R:
|
||||
#' downloadLink('downloadData', 'Download')
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
downloadHandler <- function(filename, content, contentType=NA) {
|
||||
return(function(shinyapp, name, ...) {
|
||||
shinyapp$registerDownload(name, filename, contentType, content)
|
||||
return(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)
|
||||
|
||||
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$registerDataTable(name, data)
|
||||
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) {
|
||||
shinyDeprecated(new="renderText")
|
||||
renderText({ func() })
|
||||
}
|
||||
|
||||
151
R/showcase.R
Normal file
151
R/showcase.R
Normal file
@@ -0,0 +1,151 @@
|
||||
|
||||
# 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() {
|
||||
mdfile <- file.path.ci(getwd(), 'Readme.md')
|
||||
with(tags, tagList(
|
||||
script(src="shared/highlight/highlight.pack.js"),
|
||||
script(src="shared/showdown/compressed/showdown.js"),
|
||||
script(src="shared/jqueryui/1.10.3/jquery-ui.min.js"),
|
||||
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"),
|
||||
link(rel="stylesheet", type="text/css",
|
||||
href="shared/font-awesome/css/font-awesome.min.css"),
|
||||
if (file.exists(mdfile))
|
||||
script(type="text/markdown", id="showcase-markdown-content",
|
||||
paste(readLines(mdfile), collapse="\n"))
|
||||
else ""
|
||||
))
|
||||
}
|
||||
|
||||
# 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)),
|
||||
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)
|
||||
}
|
||||
76
R/slider.R
76
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,9 +28,9 @@ 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) {
|
||||
@@ -40,25 +38,25 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
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,7 +66,7 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
else
|
||||
step = 1
|
||||
}
|
||||
|
||||
|
||||
# Default state is to not have ticks
|
||||
if (identical(ticks, TRUE)) {
|
||||
# Automatic ticks
|
||||
@@ -99,25 +97,35 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
else {
|
||||
ticks <- NULL
|
||||
}
|
||||
|
||||
|
||||
# build slider
|
||||
sliderFragment <- 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)
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
if (identical(animate, TRUE))
|
||||
animate <- animationOptions()
|
||||
|
||||
|
||||
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='#',
|
||||
@@ -128,6 +136,6 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
tags$span(class='play', animate$playButton),
|
||||
tags$span(class='pause', animate$pauseButton)))
|
||||
}
|
||||
|
||||
|
||||
return(sliderFragment)
|
||||
}
|
||||
|
||||
710
R/tags.R
710
R/tags.R
@@ -15,26 +15,26 @@ htmlEscape <- local({
|
||||
`\n` = ' '
|
||||
)
|
||||
.htmlSpecialsPatternAttrib <- paste(names(.htmlSpecialsAttrib), collapse='|')
|
||||
|
||||
|
||||
function(text, attribute=TRUE) {
|
||||
pattern <- if(attribute)
|
||||
.htmlSpecialsPatternAttrib
|
||||
.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=TRUE)
|
||||
}
|
||||
|
||||
|
||||
return(text)
|
||||
}
|
||||
})
|
||||
@@ -46,22 +46,19 @@ isTag <- function(x) {
|
||||
#' @S3method print shiny.tag
|
||||
print.shiny.tag <- function(x, ...) {
|
||||
print(as.character(x), ...)
|
||||
invisible(x)
|
||||
}
|
||||
|
||||
# indent can be numeric to indicate an initial indent level,
|
||||
# or FALSE to suppress
|
||||
#' @S3method format shiny.tag
|
||||
format.shiny.tag <- function(x, ...) {
|
||||
as.character.shiny.tag(x)
|
||||
format.shiny.tag <- function(x, ..., singletons = character(0), indent = 0) {
|
||||
as.character(renderTags(x, singletons = singletons, indent = indent)$html)
|
||||
}
|
||||
|
||||
#' @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')))
|
||||
renderTags(x)$html
|
||||
}
|
||||
|
||||
#' @S3method print shiny.tag.list
|
||||
@@ -73,14 +70,26 @@ format.shiny.tag.list <- format.shiny.tag
|
||||
#' @S3method as.character shiny.tag.list
|
||||
as.character.shiny.tag.list <- as.character.shiny.tag
|
||||
|
||||
#' @S3method print html
|
||||
print.html <- function(x, ...) {
|
||||
cat(x, "\n")
|
||||
invisible(x)
|
||||
}
|
||||
|
||||
#' @S3method format html
|
||||
format.html <- function(x, ...) {
|
||||
as.character(x)
|
||||
}
|
||||
|
||||
normalizeText <- function(text) {
|
||||
if (!is.null(attr(text, "html")))
|
||||
text
|
||||
else
|
||||
htmlEscape(text, attribute=FALSE)
|
||||
|
||||
|
||||
}
|
||||
|
||||
#' @rdname tag
|
||||
#' @export
|
||||
tagList <- function(...) {
|
||||
lst <- list(...)
|
||||
@@ -88,293 +97,520 @@ tagList <- function(...) {
|
||||
return(lst)
|
||||
}
|
||||
|
||||
#' @rdname tag
|
||||
#' @export
|
||||
tagAppendAttributes <- function(tag, ...) {
|
||||
tag$attribs <- c(tag$attribs, list(...))
|
||||
tag
|
||||
}
|
||||
|
||||
#' @rdname tag
|
||||
#' @export
|
||||
tagAppendChild <- function(tag, child) {
|
||||
tag$children[[length(tag$children)+1]] <- child
|
||||
tag
|
||||
}
|
||||
|
||||
#' @rdname tag
|
||||
#' @export
|
||||
tagAppendChildren <- function(tag, ..., list = NULL) {
|
||||
tag$children <- c(tag$children, c(list(...), list))
|
||||
tag
|
||||
}
|
||||
|
||||
#' @rdname tag
|
||||
#' @export
|
||||
tagSetChildren <- function(tag, ..., list = NULL) {
|
||||
tag$children <- c(list(...), list)
|
||||
tag
|
||||
}
|
||||
|
||||
#' HTML Tag Object
|
||||
#'
|
||||
#' \code{tag()} creates an HTML tag definition. Note that all of the valid HTML5
|
||||
#' tags are already defined in the \code{\link{tags}} environment so these
|
||||
#' functions should only be used to generate additional tags.
|
||||
#' \code{tagAppendChild()} and \code{tagList()} are for supporting package
|
||||
#' authors who wish to create their own sets of tags; see the contents of
|
||||
#' bootstrap.R for examples.
|
||||
#' @param _tag_name HTML tag name
|
||||
#' @param varArgs List of attributes and children of the element. Named list
|
||||
#' items become attributes, and unnamed list items become children. Valid
|
||||
#' children are tags, single-character character vectors (which become text
|
||||
#' nodes), and raw HTML (see \code{\link{HTML}}). You can also pass lists that
|
||||
#' contain tags, text nodes, and HTML.
|
||||
#' @param tag A tag to append child elements to.
|
||||
#' @param child A child element to append to a parent tag.
|
||||
#' @param ... Unnamed items that comprise this list of tags.
|
||||
#' @param list An optional list of elements. Can be used with or instead of the
|
||||
#' \code{...} items.
|
||||
#' @return An HTML tag object that can be rendered as HTML using
|
||||
#' \code{\link{as.character}()}.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' tagList(tags$h1("Title"),
|
||||
#' tags$h2("Header text"),
|
||||
#' tags$p("Text here"))
|
||||
#'
|
||||
#' # Can also convert a regular list to a tagList (internal data structure isn't
|
||||
#' # exactly the same, but when rendered to HTML, the output is the same).
|
||||
#' x <- list(tags$h1("Title"),
|
||||
#' tags$h2("Header text"),
|
||||
#' tags$p("Text here"))
|
||||
#' tagList(x)
|
||||
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
|
||||
# Get arg names; if not a named list, use vector of empty strings
|
||||
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)
|
||||
|
||||
# Named arguments become attribs, dropping NULL values
|
||||
named_idx <- nzchar(varArgsNames)
|
||||
attribs <- dropNulls(varArgs[named_idx])
|
||||
|
||||
# Unnamed arguments are flattened and added as children.
|
||||
# Use unname() to remove the names attribute from the list, which would
|
||||
# consist of empty strings anyway.
|
||||
children <- unname(varArgs[!named_idx])
|
||||
|
||||
# Return tag data structure
|
||||
structure(
|
||||
list(name = `_tag_name`,
|
||||
attribs = attribs,
|
||||
children = children),
|
||||
class = "shiny.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, eol = "\n") {
|
||||
|
||||
if (length(tag) == 0)
|
||||
return (NULL)
|
||||
|
||||
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))
|
||||
tag <- dropNullsOrEmpty(flattenTags(tag))
|
||||
lapply(tag, tagWrite, textWriter, indent)
|
||||
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)
|
||||
|
||||
|
||||
nextIndent <- if (is.numeric(indent)) indent + 1 else indent
|
||||
indent <- if (is.numeric(indent)) indent else 0
|
||||
|
||||
# compute indent text
|
||||
indentText <- paste(rep(" ", indent*3), collapse="")
|
||||
|
||||
indentText <- paste(rep(" ", indent*2), collapse="")
|
||||
|
||||
# Check if it's just text (may either be plain-text or HTML)
|
||||
if (is.character(tag)) {
|
||||
textWriter(paste(indentText, normalizeText(tag), eol, sep=""))
|
||||
return (NULL)
|
||||
}
|
||||
|
||||
# write tag name
|
||||
textWriter(paste(indentText, "<", tag$name, sep=""))
|
||||
|
||||
|
||||
# Convert all attribs to chars explicitly; prevents us from messing up factors
|
||||
attribs <- lapply(tag$attribs, as.character)
|
||||
# concatenate attributes
|
||||
# split() is very slow, so avoid it if possible
|
||||
if (anyDuplicated(names(attribs)))
|
||||
attribs <- lapply(split(attribs, names(attribs)), paste, collapse = " ")
|
||||
|
||||
# write attributes
|
||||
for (attrib in names(tag$attribs)) {
|
||||
attribValue <- tag$attribs[[attrib]]
|
||||
for (attrib in names(attribs)) {
|
||||
attribValue <- attribs[[attrib]]
|
||||
if (!is.na(attribValue)) {
|
||||
if (is.logical(attribValue))
|
||||
attribValue <- tolower(attribValue)
|
||||
text <- htmlEscape(attribValue, attribute=TRUE)
|
||||
text <- htmlEscape(attribValue, attribute=TRUE)
|
||||
textWriter(paste(" ", attrib,"=\"", text, "\"", sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter(paste(" ", attrib, sep=""))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# write any children
|
||||
if (length(tag$children) > 0) {
|
||||
|
||||
children <- dropNullsOrEmpty(flattenTags(tag$children))
|
||||
if (length(children) > 0) {
|
||||
textWriter(">")
|
||||
|
||||
# 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=""))
|
||||
}
|
||||
if ((length(children) == 1) && is.character(children[[1]]) ) {
|
||||
textWriter(paste(normalizeText(children[[1]]), "</", tag$name, ">", eol,
|
||||
sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter(">\n")
|
||||
tagWriteChildren(tag, textWriter, indent+1, context)
|
||||
textWriter(paste(indentText, "</", tag$name, ">\n", sep=""))
|
||||
textWriter("\n")
|
||||
for (child in children)
|
||||
tagWrite(child, textWriter, nextIndent)
|
||||
textWriter(paste(indentText, "</", tag$name, ">", eol, sep=""))
|
||||
}
|
||||
}
|
||||
else {
|
||||
# only self-close void elements
|
||||
# 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",
|
||||
if (tag$name %in% c("area", "base", "br", "col", "command", "embed", "hr",
|
||||
"img", "input", "keygen", "link", "meta", "param",
|
||||
"source", "track", "wbr")) {
|
||||
textWriter("/>\n")
|
||||
textWriter(paste("/>", eol, sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter(paste("></", tag$name, ">\n", sep=""))
|
||||
textWriter(paste("></", tag$name, ">", eol, sep=""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doRenderTags <- function(ui, indent = 0) {
|
||||
# Render the body--the bodyHtml variable will be created
|
||||
conn <- file(open="w+")
|
||||
connWriter <- function(text) writeChar(text, conn, eos = NULL)
|
||||
htmlResult <- tryCatch({
|
||||
tagWrite(ui, connWriter, indent)
|
||||
flush(conn)
|
||||
readLines(conn)
|
||||
},
|
||||
finally = close(conn)
|
||||
)
|
||||
return(HTML(paste(htmlResult, collapse = "\n")))
|
||||
}
|
||||
|
||||
renderTags <- function(ui, singletons = character(0), indent = 0) {
|
||||
# Do singleton and head processing before rendering
|
||||
singletonInfo <- takeSingletons(ui, singletons)
|
||||
headInfo <- takeHeads(singletonInfo$ui)
|
||||
|
||||
# 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(...))
|
||||
headIndent <- if (is.numeric(indent)) indent + 1 else indent
|
||||
headHtml <- doRenderTags(headInfo$head, indent = headIndent)
|
||||
bodyHtml <- doRenderTags(headInfo$ui, indent = indent)
|
||||
|
||||
return(list(head = headHtml,
|
||||
singletons = singletonInfo$singletons,
|
||||
html = bodyHtml))
|
||||
}
|
||||
|
||||
# Walk a tree of tag objects, rewriting objects according to func.
|
||||
# preorder=TRUE means preorder tree traversal, that is, an object
|
||||
# should be rewritten before its children.
|
||||
rewriteTags <- function(ui, func, preorder) {
|
||||
if (preorder)
|
||||
ui <- func(ui)
|
||||
|
||||
if (isTag(ui)) {
|
||||
ui$children[] <- lapply(ui$children, rewriteTags, func, preorder)
|
||||
} else if (is.list(ui)) {
|
||||
ui[] <- lapply(ui, rewriteTags, func, preorder)
|
||||
}
|
||||
|
||||
if (!preorder)
|
||||
ui <- func(ui)
|
||||
|
||||
return(ui)
|
||||
}
|
||||
|
||||
# Preprocess a tag object by changing any singleton X into
|
||||
# <!--SHINY.SINGLETON[sig]-->X'<!--/SHINY.SINGLETON[sig]-->
|
||||
# where sig is the sha1 of X, and X' is X minus the singleton
|
||||
# attribute.
|
||||
#
|
||||
# In the case of nested singletons, outer singletons are processed
|
||||
# before inner singletons (otherwise the processing of inner
|
||||
# singletons would cause the sha1 of the outer singletons to be
|
||||
# different).
|
||||
surroundSingletons <- local({
|
||||
surroundSingleton <- function(uiObj) {
|
||||
if (inherits(uiObj, "shiny.singleton")) {
|
||||
sig <- digest(uiObj, "sha1")
|
||||
class(uiObj) <- class(uiObj)[class(uiObj) != "shiny.singleton"]
|
||||
return(tagList(
|
||||
HTML(sprintf("<!--SHINY.SINGLETON[%s]-->", sig)),
|
||||
uiObj,
|
||||
HTML(sprintf("<!--/SHINY.SINGLETON[%s]-->", sig))
|
||||
))
|
||||
} else {
|
||||
uiObj
|
||||
}
|
||||
}
|
||||
|
||||
function(ui) {
|
||||
rewriteTags(ui, surroundSingleton, TRUE)
|
||||
}
|
||||
})
|
||||
|
||||
# Given a tag object, apply singleton logic (allow singleton objects
|
||||
# to appear no more than once per signature) and return the processed
|
||||
# HTML objects and also the list of known singletons.
|
||||
takeSingletons <- function(ui, singletons=character(0), desingleton=TRUE) {
|
||||
result <- rewriteTags(ui, function(uiObj) {
|
||||
if (inherits(uiObj, "shiny.singleton")) {
|
||||
sig <- digest(uiObj, "sha1")
|
||||
if (sig %in% singletons)
|
||||
return(NULL)
|
||||
singletons <<- append(singletons, sig)
|
||||
if (desingleton)
|
||||
class(uiObj) <- class(uiObj)[class(uiObj) != "shiny.singleton"]
|
||||
return(uiObj)
|
||||
} else {
|
||||
return(uiObj)
|
||||
}
|
||||
}, TRUE)
|
||||
|
||||
return(list(ui=result, singletons=singletons))
|
||||
}
|
||||
|
||||
# Given a tag object, extract out any children of tags$head
|
||||
# and return them separate from the body.
|
||||
takeHeads <- function(ui) {
|
||||
headItems <- list()
|
||||
result <- rewriteTags(ui, function(uiObj) {
|
||||
if (isTag(uiObj) && tolower(uiObj$name) == "head") {
|
||||
headItems <<- append(headItems, uiObj$children)
|
||||
return(NULL)
|
||||
}
|
||||
return(uiObj)
|
||||
}, FALSE)
|
||||
|
||||
return(list(ui=result, head=headItems))
|
||||
}
|
||||
|
||||
#' HTML Builder Functions
|
||||
#'
|
||||
#' Simple functions for constructing HTML documents.
|
||||
#'
|
||||
#' The \code{tags} environment contains convenience functions for all valid
|
||||
#' HTML5 tags. To generate tags that are not part of the HTML5 specification,
|
||||
#' you can use the \code{\link{tag}()} function.
|
||||
#'
|
||||
#' Dedicated functions are available for the most common HTML tags that do not
|
||||
#' conflict with common R functions.
|
||||
#'
|
||||
#' The result from these functions is a tag object, which can be converted using
|
||||
#' \code{\link{as.character}()}.
|
||||
#'
|
||||
#' @name builder
|
||||
#' @param ... Attributes and children of the element. Named arguments become
|
||||
#' attributes, and positional arguments become children. Valid children are
|
||||
#' tags, single-character character vectors (which become text nodes), and raw
|
||||
#' HTML (see \code{\link{HTML}}). You can also pass lists that contain tags,
|
||||
#' text nodes, and HTML.
|
||||
#' @export tags
|
||||
#' @examples
|
||||
#' doc <- tags$html(
|
||||
#' tags$head(
|
||||
#' tags$title('My first page')
|
||||
#' ),
|
||||
#' tags$body(
|
||||
#' h1('My first heading'),
|
||||
#' p('My first paragraph, with some ',
|
||||
#' strong('bold'),
|
||||
#' ' text.'),
|
||||
#' div(id='myDiv', class='simpleDiv',
|
||||
#' 'Here is a div with some attributes.')
|
||||
#' )
|
||||
#' )
|
||||
#' cat(as.character(doc))
|
||||
NULL
|
||||
|
||||
#' @rdname builder
|
||||
#' @format NULL
|
||||
#' @docType NULL
|
||||
#' @keywords NULL
|
||||
tags <- list(
|
||||
a = function(...) tag("a", list(...)),
|
||||
abbr = function(...) tag("abbr", list(...)),
|
||||
address = function(...) tag("address", list(...)),
|
||||
area = function(...) tag("area", list(...)),
|
||||
article = function(...) tag("article", list(...)),
|
||||
aside = function(...) tag("aside", list(...)),
|
||||
audio = function(...) tag("audio", list(...)),
|
||||
b = function(...) tag("b", list(...)),
|
||||
base = function(...) tag("base", list(...)),
|
||||
bdi = function(...) tag("bdi", list(...)),
|
||||
bdo = function(...) tag("bdo", list(...)),
|
||||
blockquote = function(...) tag("blockquote", list(...)),
|
||||
body = function(...) tag("body", list(...)),
|
||||
br = function(...) tag("br", list(...)),
|
||||
button = function(...) tag("button", list(...)),
|
||||
canvas = function(...) tag("canvas", list(...)),
|
||||
caption = function(...) tag("caption", list(...)),
|
||||
cite = function(...) tag("cite", list(...)),
|
||||
code = function(...) tag("code", list(...)),
|
||||
col = function(...) tag("col", list(...)),
|
||||
colgroup = function(...) tag("colgroup", list(...)),
|
||||
command = function(...) tag("command", list(...)),
|
||||
data = function(...) tag("data", list(...)),
|
||||
datalist = function(...) tag("datalist", list(...)),
|
||||
dd = function(...) tag("dd", list(...)),
|
||||
del = function(...) tag("del", list(...)),
|
||||
details = function(...) tag("details", list(...)),
|
||||
dfn = function(...) tag("dfn", list(...)),
|
||||
div = function(...) tag("div", list(...)),
|
||||
dl = function(...) tag("dl", list(...)),
|
||||
dt = function(...) tag("dt", list(...)),
|
||||
em = function(...) tag("em", list(...)),
|
||||
embed = function(...) tag("embed", list(...)),
|
||||
eventsource = function(...) tag("eventsource", list(...)),
|
||||
fieldset = function(...) tag("fieldset", list(...)),
|
||||
figcaption = function(...) tag("figcaption", list(...)),
|
||||
figure = function(...) tag("figure", list(...)),
|
||||
footer = function(...) tag("footer", list(...)),
|
||||
form = function(...) tag("form", list(...)),
|
||||
h1 = function(...) tag("h1", list(...)),
|
||||
h2 = function(...) tag("h2", list(...)),
|
||||
h3 = function(...) tag("h3", list(...)),
|
||||
h4 = function(...) tag("h4", list(...)),
|
||||
h5 = function(...) tag("h5", list(...)),
|
||||
h6 = function(...) tag("h6", list(...)),
|
||||
head = function(...) tag("head", list(...)),
|
||||
header = function(...) tag("header", list(...)),
|
||||
hgroup = function(...) tag("hgroup", list(...)),
|
||||
hr = function(...) tag("hr", list(...)),
|
||||
html = function(...) tag("html", list(...)),
|
||||
i = function(...) tag("i", list(...)),
|
||||
iframe = function(...) tag("iframe", list(...)),
|
||||
img = function(...) tag("img", list(...)),
|
||||
input = function(...) tag("input", list(...)),
|
||||
ins = function(...) tag("ins", list(...)),
|
||||
kbd = function(...) tag("kbd", list(...)),
|
||||
keygen = function(...) tag("keygen", list(...)),
|
||||
label = function(...) tag("label", list(...)),
|
||||
legend = function(...) tag("legend", list(...)),
|
||||
li = function(...) tag("li", list(...)),
|
||||
link = function(...) tag("link", list(...)),
|
||||
mark = function(...) tag("mark", list(...)),
|
||||
map = function(...) tag("map", list(...)),
|
||||
menu = function(...) tag("menu", list(...)),
|
||||
meta = function(...) tag("meta", list(...)),
|
||||
meter = function(...) tag("meter", list(...)),
|
||||
nav = function(...) tag("nav", list(...)),
|
||||
noscript = function(...) tag("noscript", list(...)),
|
||||
object = function(...) tag("object", list(...)),
|
||||
ol = function(...) tag("ol", list(...)),
|
||||
optgroup = function(...) tag("optgroup", list(...)),
|
||||
option = function(...) tag("option", list(...)),
|
||||
output = function(...) tag("output", list(...)),
|
||||
p = function(...) tag("p", list(...)),
|
||||
param = function(...) tag("param", list(...)),
|
||||
pre = function(...) tag("pre", list(...)),
|
||||
progress = function(...) tag("progress", list(...)),
|
||||
q = function(...) tag("q", list(...)),
|
||||
ruby = function(...) tag("ruby", list(...)),
|
||||
rp = function(...) tag("rp", list(...)),
|
||||
rt = function(...) tag("rt", list(...)),
|
||||
s = function(...) tag("s", list(...)),
|
||||
samp = function(...) tag("samp", list(...)),
|
||||
script = function(...) tag("script", list(...)),
|
||||
section = function(...) tag("section", list(...)),
|
||||
select = function(...) tag("select", list(...)),
|
||||
small = function(...) tag("small", list(...)),
|
||||
source = function(...) tag("source", list(...)),
|
||||
span = function(...) tag("span", list(...)),
|
||||
strong = function(...) tag("strong", list(...)),
|
||||
style = function(...) tag("style", list(...)),
|
||||
sub = function(...) tag("sub", list(...)),
|
||||
summary = function(...) tag("summary", list(...)),
|
||||
sup = function(...) tag("sup", list(...)),
|
||||
table = function(...) tag("table", list(...)),
|
||||
tbody = function(...) tag("tbody", list(...)),
|
||||
td = function(...) tag("td", list(...)),
|
||||
textarea = function(...) tag("textarea", list(...)),
|
||||
tfoot = function(...) tag("tfoot", list(...)),
|
||||
th = function(...) tag("th", list(...)),
|
||||
thead = function(...) tag("thead", list(...)),
|
||||
time = function(...) tag("time", list(...)),
|
||||
title = function(...) tag("title", list(...)),
|
||||
tr = function(...) tag("tr", list(...)),
|
||||
track = function(...) tag("track", list(...)),
|
||||
u = function(...) tag("u", list(...)),
|
||||
ul = function(...) tag("ul", list(...)),
|
||||
var = function(...) tag("var", list(...)),
|
||||
video = function(...) tag("video", list(...)),
|
||||
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
|
||||
class(htmlText) <- c("html", "character")
|
||||
htmlText
|
||||
}
|
||||
|
||||
#' Evaluate an expression using the \code{tags}
|
||||
#'
|
||||
#' This function makes it simpler to write HTML-generating code. Instead of
|
||||
#' needing to specify \code{tags} each time a tag function is used, as in
|
||||
#' \code{tags$div()} and \code{tags$p()}, code inside \code{withTags} is
|
||||
#' evaluated with \code{tags} searched first, so you can simply use
|
||||
#' \code{div()} and \code{p()}.
|
||||
#'
|
||||
#' If your code uses an object which happens to have the same name as an
|
||||
#' HTML tag function, such as \code{source()} or \code{summary()}, it will call
|
||||
#' the tag function. To call the intended (non-tags function), specify the
|
||||
#' namespace, as in \code{base::source()} or \code{base::summary()}.
|
||||
#'
|
||||
#' @param code A set of tags.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Using tags$ each time
|
||||
#' tags$div(class = "myclass",
|
||||
#' tags$h3("header"),
|
||||
#' tags$p("text")
|
||||
#' )
|
||||
#'
|
||||
#' # Equivalent to above, but using withTags
|
||||
#' withTags(
|
||||
#' div(class = "myclass",
|
||||
#' h3("header"),
|
||||
#' p("text")
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
withTags <- function(code) {
|
||||
eval(substitute(code), envir = as.list(tags), enclos = parent.frame())
|
||||
}
|
||||
|
||||
|
||||
# Given a list of tags, lists, and other items, return a flat list, where the
|
||||
# items from the inner, nested lists are pulled to the top level, recursively.
|
||||
flattenTags <- function(x) {
|
||||
if (isTag(x)) {
|
||||
# For tags, wrap them into a list (which will be unwrapped by caller)
|
||||
list(x)
|
||||
} else if (is.list(x)) {
|
||||
if (length(x) == 0) {
|
||||
# Empty lists are simply returned
|
||||
x
|
||||
} else {
|
||||
# For items that are lists (but not tags), recurse
|
||||
unlist(lapply(x, flattenTags), recursive = FALSE)
|
||||
}
|
||||
|
||||
} else if (is.character(x)){
|
||||
# This will preserve attributes if x is a character with attribute,
|
||||
# like what HTML() produces
|
||||
list(x)
|
||||
|
||||
} else {
|
||||
# For other items, coerce to character and wrap them into a list (which
|
||||
# will be unwrapped by caller). Note that this will strip attributes.
|
||||
list(as.character(x))
|
||||
}
|
||||
}
|
||||
|
||||
14
R/timer.R
14
R/timer.R
@@ -23,17 +23,17 @@ TimerCallbacks <- setRefClass(
|
||||
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() {
|
||||
@@ -46,18 +46,18 @@ 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(FALSE)
|
||||
|
||||
|
||||
for (id in elapsed$id) {
|
||||
thisFunc <- .funcs$remove(as.character(id))
|
||||
# TODO: Catch exception, and...?
|
||||
|
||||
394
R/update-input.R
Normal file
394
R/update-input.R
Normal file
@@ -0,0 +1,394 @@
|
||||
#' 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)) mapply(choices, names(choices),
|
||||
SIMPLIFY = FALSE, USE.NAMES = FALSE,
|
||||
FUN = function(value, name) {
|
||||
list(value = value,
|
||||
label = name)
|
||||
}
|
||||
)
|
||||
|
||||
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
|
||||
448
R/utils.R
448
R/utils.R
@@ -1,13 +1,13 @@
|
||||
#' Make a random number generator repeatable
|
||||
#'
|
||||
#' Given a function that generates random data, returns a wrapped version of
|
||||
#'
|
||||
#' 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}}.
|
||||
@@ -19,11 +19,11 @@
|
||||
#' 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())) {
|
||||
@@ -33,10 +33,10 @@ repeatable <- function(rngfunc, seed = runif(1, 0, .Machine$integer.max)) {
|
||||
else {
|
||||
on.exit(rm('.Random.seed', pos=globalenv()))
|
||||
}
|
||||
|
||||
|
||||
set.seed(seed)
|
||||
|
||||
do.call(rngfunc, list(...))
|
||||
|
||||
rngfunc(...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,19 @@ repeatable <- function(rngfunc, seed = runif(1, 0, .Machine$integer.max)) {
|
||||
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))]
|
||||
}
|
||||
|
||||
knownContentTypes <- Map$new()
|
||||
knownContentTypes$mset(
|
||||
html='text/html; charset=UTF-8',
|
||||
@@ -101,4 +114,419 @@ knownContentTypes$mset(
|
||||
|
||||
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, query) {
|
||||
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))
|
||||
if (nrow(fdata) == 0) fdata <- list()
|
||||
|
||||
toJSON(list(
|
||||
sEcho = as.integer(sEcho),
|
||||
iTotalRecords = n,
|
||||
iTotalDisplayRecords = nrow(data),
|
||||
aaData = fdata
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
21
README.md
21
README.md
@@ -1,4 +1,6 @@
|
||||
# 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.
|
||||
|
||||
@@ -10,27 +12,34 @@ For an introduction and examples, visit the [Shiny homepage](http://www.rstudio.
|
||||
* 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:",
|
||||
c("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=TRUE))
|
||||
),
|
||||
|
||||
# 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:",
|
||||
c("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.
|
||||
@@ -1,18 +1,20 @@
|
||||
library(shiny)
|
||||
|
||||
shinyServer(function(input, output) {
|
||||
output$contents <- reactiveTable(function() {
|
||||
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 'data'
|
||||
# columns. The 'data' column will contain the local filenames where the data
|
||||
# can be found.
|
||||
# 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$data, header=input$header, sep=input$sep, quote=input$quote)
|
||||
read.csv(inFile$datapath, header=input$header, sep=input$sep,
|
||||
quote=input$quote)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
library(shiny)
|
||||
|
||||
shinyUI(pageWithSidebar(
|
||||
headerPanel("CSV Viewer"),
|
||||
sidebarPanel(
|
||||
fileInput('file1', 'Choose CSV File',
|
||||
accept=c('text/csv', 'text/comma-separated-values,text/plain')),
|
||||
tags$hr(),
|
||||
checkboxInput('header', 'Header', TRUE),
|
||||
radioButtons('sep', 'Separator',
|
||||
c(Comma=',',
|
||||
Semicolon=';',
|
||||
Tab='\t'),
|
||||
'Comma'),
|
||||
radioButtons('quote', 'Quote',
|
||||
c(None='',
|
||||
'Double Quote'='"',
|
||||
'Single Quote'="'"),
|
||||
'Double Quote')
|
||||
),
|
||||
mainPanel(
|
||||
tableOutput('contents')
|
||||
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.
|
||||
@@ -1,18 +1,21 @@
|
||||
shinyServer(function(input, output) {
|
||||
datasetInput <- reactive(function() {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
output$table <- reactiveTable(function() {
|
||||
output$table <- renderTable({
|
||||
datasetInput()
|
||||
})
|
||||
|
||||
output$downloadData <- downloadHandler(
|
||||
filename = function() { paste(input$dataset, '.csv', sep='') },
|
||||
content = function(conn) {
|
||||
write.csv(datasetInput(), conn)
|
||||
})
|
||||
filename = function() {
|
||||
paste(input$dataset, '.csv', sep='')
|
||||
},
|
||||
content = function(file) {
|
||||
write.csv(datasetInput(), file)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
shinyUI(pageWithSidebar(
|
||||
headerPanel('Download Example'),
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
downloadButton('downloadData', 'Download')
|
||||
),
|
||||
mainPanel(
|
||||
tableOutput('table')
|
||||
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")
|
||||
))
|
||||
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
56
inst/tests/test-bootstrap.r
Normal file
56
inst/tests/test-bootstrap.r
Normal file
@@ -0,0 +1,56 @@
|
||||
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, NA, 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)
|
||||
choices <- x[[2]]$children
|
||||
|
||||
expect_equal(choices[[1]]$children[[1]], 'a')
|
||||
expect_equal(choices[[1]]$attribs$value, 'x1')
|
||||
expect_equal(choices[[1]]$attribs$selected, 'selected')
|
||||
|
||||
expect_equal(choices[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]]$attribs$value, 'x2')
|
||||
expect_equal(choices[[2]]$attribs$selected, NULL)
|
||||
|
||||
expect_equal(choices[[3]]$children[[1]], 'b')
|
||||
expect_equal(choices[[3]]$attribs$value, 'x3')
|
||||
expect_equal(choices[[3]]$attribs$selected, NULL)
|
||||
|
||||
|
||||
# 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))
|
||||
)
|
||||
})
|
||||
702
inst/tests/test-reactivity.r
Normal file
702
inst/tests/test-reactivity.r
Normal file
@@ -0,0 +1,702 @@
|
||||
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))
|
||||
})
|
||||
|
||||
test_that("{} and NULL also work in reactive()", {
|
||||
reactive({})
|
||||
reactive(NULL)
|
||||
})
|
||||
26
inst/tests/test-staticdocs.R
Normal file
26
inst/tests/test-staticdocs.R
Normal file
@@ -0,0 +1,26 @@
|
||||
context("staticdocs")
|
||||
|
||||
test_that("All man pages have an entry in staticdocs/index.r", {
|
||||
if (!all(file.exists(c('../../staticdocs', '../../man')))) {
|
||||
# This test works only when run against a package directory
|
||||
return()
|
||||
}
|
||||
# Known not to be indexed
|
||||
known_unindexed <- c("shiny-package")
|
||||
|
||||
indexed_topics <- local({
|
||||
result <- character(0)
|
||||
sd_section <- function(dummy1, dummy2, section_topics) {
|
||||
result <<- c(result, section_topics)
|
||||
}
|
||||
source("../../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.
|
||||
expect_equivalent(sort(all_topics), sort(c(known_unindexed, indexed_topics)))
|
||||
})
|
||||
432
inst/tests/test-tags.r
Normal file
432
inst/tests/test-tags.r
Normal file
@@ -0,0 +1,432 @@
|
||||
context("tags")
|
||||
|
||||
test_that("Basic tag writing works", {
|
||||
expect_equal(as.character(tagList("hi")), HTML("hi"))
|
||||
expect_equal(
|
||||
as.character(tagList("one", "two", tagList("three"))),
|
||||
HTML("one\ntwo\nthree"))
|
||||
expect_equal(
|
||||
as.character(tags$b("one")),
|
||||
HTML("<b>one</b>"))
|
||||
expect_equal(
|
||||
as.character(tags$b("one", "two")),
|
||||
HTML("<b>\n one\n two\n</b>"))
|
||||
expect_equal(
|
||||
as.character(tagList(list("one"))),
|
||||
HTML("one"))
|
||||
expect_equal(
|
||||
as.character(tagList(list(tagList("one")))),
|
||||
HTML("one"))
|
||||
expect_equal(
|
||||
as.character(tagList(tags$br(), "one")),
|
||||
HTML("<br/>\none"))
|
||||
})
|
||||
|
||||
|
||||
test_that("withTags works", {
|
||||
output_tags <- tags$div(class = "myclass",
|
||||
tags$h3("header"),
|
||||
tags$p("text here")
|
||||
)
|
||||
output_withhtml <- withTags(
|
||||
div(class = "myclass",
|
||||
h3("header"),
|
||||
p("text here")
|
||||
)
|
||||
)
|
||||
expect_identical(output_tags, output_withhtml)
|
||||
|
||||
|
||||
# Check that current environment is searched
|
||||
x <- 100
|
||||
expect_identical(tags$p(x), withTags(p(x)))
|
||||
|
||||
# Just to make sure, run it in a function, which has its own environment
|
||||
foo <- function() {
|
||||
y <- 100
|
||||
withTags(p(y))
|
||||
}
|
||||
expect_identical(tags$p(100), foo())
|
||||
})
|
||||
|
||||
|
||||
test_that("HTML escaping in tags", {
|
||||
# Regular text is escaped
|
||||
expect_equivalent(format(div("<a&b>")), "<div><a&b></div>")
|
||||
|
||||
# Text in HTML() isn't escaped
|
||||
expect_equivalent(format(div(HTML("<a&b>"))), "<div><a&b></div>")
|
||||
|
||||
# Text in a property is escaped
|
||||
expect_equivalent(format(div(class = "<a&b>", "text")),
|
||||
'<div class="<a&b>">text</div>')
|
||||
|
||||
# HTML() has no effect in a property like 'class'
|
||||
expect_equivalent(format(div(class = HTML("<a&b>"), "text")),
|
||||
'<div class="<a&b>">text</div>')
|
||||
})
|
||||
|
||||
|
||||
test_that("Adding child tags", {
|
||||
tag_list <- list(tags$p("tag1"), tags$b("tag2"), tags$i("tag3"))
|
||||
|
||||
# Creating nested tags by calling the tag$div function and passing a list
|
||||
t1 <- tags$div(class="foo", tag_list)
|
||||
expect_equal(length(t1$children), 1)
|
||||
expect_equal(length(t1$children[[1]]), 3)
|
||||
expect_equal(t1$children[[1]][[1]]$name, "p")
|
||||
expect_equal(t1$children[[1]][[1]]$children[[1]], "tag1")
|
||||
expect_equal(t1$children[[1]][[2]]$name, "b")
|
||||
expect_equal(t1$children[[1]][[2]]$children[[1]], "tag2")
|
||||
expect_equal(t1$children[[1]][[3]]$name, "i")
|
||||
expect_equal(t1$children[[1]][[3]]$children[[1]], "tag3")
|
||||
|
||||
|
||||
# div tag used as starting point for tests below
|
||||
div_tag <- tags$div(class="foo")
|
||||
|
||||
# Appending each child
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChild(t2, tag_list[[2]])
|
||||
t2 <- tagAppendChild(t2, tag_list[[3]])
|
||||
t2a <- do.call(tags$div, c(tag_list, class="foo"))
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
|
||||
# tagSetChildren, using list argument
|
||||
t2 <- tagSetChildren(div_tag, list = tag_list)
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagSetChildren, using ... arguments
|
||||
t2 <- tagSetChildren(div_tag, tag_list[[1]], tag_list[[2]], tag_list[[3]])
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagSetChildren, using ... and list arguments
|
||||
t2 <- tagSetChildren(div_tag, tag_list[[1]], list = tag_list[2:3])
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagSetChildren overwrites existing children
|
||||
t2 <- tagAppendChild(div_tag, p("should replace this tag"))
|
||||
t2 <- tagSetChildren(div_tag, list = tag_list)
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
|
||||
# tagAppendChildren, using list argument
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChildren(t2, list = tag_list[2:3])
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagAppendChildren, using ... arguments
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChildren(t2, tag_list[[2]], tag_list[[3]])
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagAppendChildren, using ... and list arguments
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChildren(t2, tag_list[[2]], list = list(tag_list[[3]]))
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagAppendChildren can start with no children
|
||||
t2 <- tagAppendChildren(div_tag, list = tag_list)
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
|
||||
# tagSetChildren preserves attributes
|
||||
x <- tagSetChildren(div(), HTML("text"))
|
||||
expect_identical(attr(x$children[[1]], "html"), TRUE)
|
||||
|
||||
# tagAppendChildren preserves attributes
|
||||
x <- tagAppendChildren(div(), HTML("text"))
|
||||
expect_identical(attr(x$children[[1]], "html"), TRUE)
|
||||
})
|
||||
|
||||
|
||||
test_that("Creating simple tags", {
|
||||
# Empty tag
|
||||
expect_identical(
|
||||
div(),
|
||||
structure(
|
||||
list(name = "div", attribs = list(), children = list()),
|
||||
.Names = c("name", "attribs", "children"),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
)
|
||||
|
||||
# Tag with text
|
||||
expect_identical(
|
||||
div("text"),
|
||||
structure(
|
||||
list(name = "div", attribs = list(), children = list("text")),
|
||||
.Names = c("name", "attribs", "children"),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
)
|
||||
|
||||
# NULL attributes are dropped
|
||||
expect_identical(
|
||||
div(a = NULL, b = "value"),
|
||||
div(b = "value")
|
||||
)
|
||||
|
||||
# NULL children are dropped
|
||||
expect_identical(
|
||||
renderTags(div("foo", NULL, list(NULL, list(NULL, "bar"))))$html,
|
||||
renderTags(div("foo", "bar"))$html
|
||||
)
|
||||
|
||||
# Numbers are coerced to strings
|
||||
expect_identical(
|
||||
renderTags(div(1234))$html,
|
||||
renderTags(div("1234"))$html
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
test_that("Creating nested tags", {
|
||||
# Simple version
|
||||
# Note that the $children list should not have a names attribute
|
||||
expect_identical(
|
||||
div(class="foo", list("a", "b")),
|
||||
structure(
|
||||
list(name = "div",
|
||||
attribs = structure(list(class = "foo"), .Names = "class"),
|
||||
children = list(list("a", "b"))),
|
||||
.Names = c("name", "attribs", "children"),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
)
|
||||
|
||||
# More complex version
|
||||
t1 <- withTags(
|
||||
div(class = "foo",
|
||||
p("child tag"),
|
||||
list(
|
||||
p("in-list child tag 1"),
|
||||
"in-list character string",
|
||||
p(),
|
||||
p("in-list child tag 2")
|
||||
),
|
||||
"character string",
|
||||
1234
|
||||
)
|
||||
)
|
||||
|
||||
# t1 should be identical to this data structure.
|
||||
# The nested list should be flattened, and non-tag, non-strings should be
|
||||
# converted to strings
|
||||
t1_full <- structure(
|
||||
list(
|
||||
name = "div",
|
||||
attribs = list(class = "foo"),
|
||||
children = list(
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list("child tag")),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list("in-list child tag 1")),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
"in-list character string",
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list()),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list("in-list child tag 2")),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
"character string",
|
||||
"1234"
|
||||
)
|
||||
),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
|
||||
expect_identical(renderTags(t1)$html, renderTags(t1_full)$html)
|
||||
})
|
||||
|
||||
test_that("Attributes are preserved", {
|
||||
# HTML() adds an attribute to the data structure (note that this is
|
||||
# different from the 'attribs' field in the list)
|
||||
x <- HTML("<tag>&&</tag>")
|
||||
expect_identical(attr(x, "html"), TRUE)
|
||||
expect_equivalent(format(x), "<tag>&&</tag>")
|
||||
|
||||
# Make sure attributes are preserved when wrapped in other tags
|
||||
x <- div(HTML("<tag>&&</tag>"))
|
||||
expect_equivalent(x$children[[1]], HTML("<tag>&&</tag>"))
|
||||
expect_identical(attr(x$children[[1]], "html"), TRUE)
|
||||
expect_equivalent(format(x), "<div><tag>&&</tag></div>")
|
||||
|
||||
# Deeper nesting
|
||||
x <- div(p(HTML("<tag>&&</tag>")))
|
||||
expect_equivalent(x$children[[1]]$children[[1]], HTML("<tag>&&</tag>"))
|
||||
expect_identical(attr(x$children[[1]]$children[[1]], "html"), TRUE)
|
||||
expect_equivalent(format(x), "<div>\n <p><tag>&&</tag></p>\n</div>")
|
||||
})
|
||||
|
||||
|
||||
test_that("Flattening a list of tags", {
|
||||
# Flatten a nested list
|
||||
nested <- list(
|
||||
"a1",
|
||||
list(
|
||||
"b1",
|
||||
list("c1", "c2"),
|
||||
list(),
|
||||
"b2",
|
||||
list("d1", "d2")
|
||||
),
|
||||
"a2"
|
||||
)
|
||||
flat <- list("a1", "b1", "c1", "c2", "b2", "d1", "d2", "a2")
|
||||
expect_identical(flattenTags(nested), flat)
|
||||
|
||||
# no-op for flat lists
|
||||
expect_identical(flattenTags(list(a="1", "b")), list(a="1", "b"))
|
||||
|
||||
# numbers are coerced to character
|
||||
expect_identical(flattenTags(list(a=1, "b")), list(a="1", "b"))
|
||||
|
||||
# empty list results in empty list
|
||||
expect_identical(flattenTags(list()), list())
|
||||
|
||||
# preserve attributes
|
||||
nested <- list("txt1", list(structure("txt2", prop="prop2")))
|
||||
flat <- list("txt1",
|
||||
structure("txt2", prop="prop2"))
|
||||
expect_identical(flattenTags(nested), flat)
|
||||
})
|
||||
|
||||
test_that("Head and singleton behavior", {
|
||||
result <- renderTags(tagList(
|
||||
tags$head(singleton("hello"))
|
||||
))
|
||||
|
||||
expect_identical(result$html, HTML(""))
|
||||
expect_identical(result$head, HTML(" hello"))
|
||||
expect_identical(result$singletons, "60eed8231e688bcba7c275c58dd2e3b4dacb61f0")
|
||||
|
||||
# Ensure that "hello" actually behaves like a singleton
|
||||
result2 <- renderTags(tagList(
|
||||
tags$head(singleton("hello"))
|
||||
), singletons = result$singletons)
|
||||
|
||||
expect_identical(result$singletons, result2$singletons)
|
||||
expect_identical(result2$head, HTML(""))
|
||||
expect_identical(result2$html, HTML(""))
|
||||
|
||||
result3 <- renderTags(tagList(
|
||||
tags$head(singleton("hello"), singleton("hello"))
|
||||
))
|
||||
expect_identical(result$singletons, result3$singletons)
|
||||
expect_identical(result3$head, HTML(" hello"))
|
||||
|
||||
# Ensure that singleton can be applied to lists, not just tags
|
||||
result4 <- renderTags(list(singleton(list("hello")), singleton(list("hello"))))
|
||||
expect_identical(result4$singletons, "d7319e3f14167c4c056dd7aa0b274c83fe2291f6")
|
||||
expect_identical(result4$html, renderTags(HTML("hello"))$html)
|
||||
})
|
||||
|
||||
test_that("Factors are treated as characters, not numbers", {
|
||||
myfactors <- factor(LETTERS[1:3])
|
||||
expect_identical(
|
||||
as.character(tags$option(value=myfactors[[1]], myfactors[[1]])),
|
||||
HTML('<option value="A">A</option>')
|
||||
)
|
||||
|
||||
expect_identical(
|
||||
as.character(tags$option(value=myfactors[[1]], value='B', value=3, myfactors[[1]])),
|
||||
HTML('<option value="A B 3">A</option>')
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Unusual list contents are rendered correctly", {
|
||||
expect_identical(renderTags(list(NULL)), renderTags(HTML("")))
|
||||
expect_identical(renderTags(list(100)), renderTags(HTML("100")))
|
||||
expect_identical(renderTags(list(list(100))), renderTags(HTML("100")))
|
||||
expect_identical(renderTags(list(list())), renderTags(HTML("")))
|
||||
expect_identical(renderTags(NULL), renderTags(HTML("")))
|
||||
})
|
||||
|
||||
test_that("Low-level singleton manipulation methods", {
|
||||
# Default arguments drop singleton duplicates and strips the
|
||||
# singletons it keeps of the singleton bit
|
||||
result1 <- takeSingletons(tags$div(
|
||||
singleton(tags$head(tags$script("foo"))),
|
||||
singleton(tags$head(tags$script("foo")))
|
||||
))
|
||||
|
||||
expect_identical(result1$ui$children[[2]], NULL)
|
||||
expect_false(is(result1$ui$children[[1]], "shiny.singleton"))
|
||||
|
||||
# desingleton=FALSE means drop duplicates but don't strip the
|
||||
# singleton bit
|
||||
result2 <- takeSingletons(tags$div(
|
||||
singleton(tags$head(tags$script("foo"))),
|
||||
singleton(tags$head(tags$script("foo")))
|
||||
), desingleton=FALSE)
|
||||
|
||||
expect_identical(result2$ui$children[[2]], NULL)
|
||||
expect_is(result2$ui$children[[1]], "shiny.singleton")
|
||||
|
||||
result3 <- surroundSingletons(tags$div(
|
||||
singleton(tags$script("foo")),
|
||||
singleton(tags$script("foo"))
|
||||
))
|
||||
|
||||
expect_identical(
|
||||
renderTags(result3)$html,
|
||||
HTML("<div>
|
||||
<!--SHINY.SINGLETON[58b302d493b50acb75e4a5606687cadccdf902d8]-->
|
||||
<script>foo</script>
|
||||
<!--/SHINY.SINGLETON[58b302d493b50acb75e4a5606687cadccdf902d8]-->
|
||||
<!--SHINY.SINGLETON[58b302d493b50acb75e4a5606687cadccdf902d8]-->
|
||||
<script>foo</script>
|
||||
<!--/SHINY.SINGLETON[58b302d493b50acb75e4a5606687cadccdf902d8]-->
|
||||
</div>")
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Indenting can be controlled/suppressed", {
|
||||
expect_identical(
|
||||
renderTags(tags$div("a", "b"))$html,
|
||||
HTML("<div>\n a\n b\n</div>")
|
||||
)
|
||||
expect_identical(
|
||||
format(tags$div("a", "b")),
|
||||
"<div>\n a\n b\n</div>"
|
||||
)
|
||||
|
||||
expect_identical(
|
||||
renderTags(tags$div("a", "b"), indent = 2)$html,
|
||||
HTML(" <div>\n a\n b\n </div>")
|
||||
)
|
||||
expect_identical(
|
||||
format(tags$div("a", "b"), indent = 2),
|
||||
" <div>\n a\n b\n </div>"
|
||||
)
|
||||
|
||||
expect_identical(
|
||||
renderTags(tags$div("a", "b"), indent = FALSE)$html,
|
||||
HTML("<div>\na\nb\n</div>")
|
||||
)
|
||||
expect_identical(
|
||||
format(tags$div("a", "b"), indent = FALSE),
|
||||
"<div>\na\nb\n</div>"
|
||||
)
|
||||
|
||||
expect_identical(
|
||||
renderTags(tagList(tags$div("a", "b")), indent = FALSE)$html,
|
||||
HTML("<div>\na\nb\n</div>")
|
||||
)
|
||||
expect_identical(
|
||||
format(tagList(tags$div("a", "b")), indent = FALSE),
|
||||
"<div>\na\nb\n</div>"
|
||||
)
|
||||
})
|
||||
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"))
|
||||
})
|
||||
818
inst/www/reactive-graph.html
Normal file
818
inst/www/reactive-graph.html
Normal file
@@ -0,0 +1,818 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
|
||||
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
|
||||
<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400,600' rel='stylesheet' type='text/css'>
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
font-weight: 400;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
div {
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-o-user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
#instructions, #ended {
|
||||
position: relative;
|
||||
font-weight: 200;
|
||||
color: #444;
|
||||
top: 20px;
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
#ended strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
svg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.node {
|
||||
cursor: pointer;
|
||||
}
|
||||
.node text {
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
font-weight: normal;
|
||||
text-anchor: start;
|
||||
fill: #999;
|
||||
user-select: none;
|
||||
transition: fill 0.75s ease;
|
||||
}
|
||||
.node.running text {
|
||||
fill: black;
|
||||
}
|
||||
.node.changed text {
|
||||
fill: red;
|
||||
}
|
||||
.node text tspan {
|
||||
white-space: pre;
|
||||
}
|
||||
.node path {
|
||||
fill: white;
|
||||
stroke: #777;
|
||||
stroke-width: 7.5px;
|
||||
transition: fill 0.75s ease;
|
||||
}
|
||||
.node.observer path {
|
||||
}
|
||||
.node.observable path {
|
||||
}
|
||||
.node.value path {
|
||||
}
|
||||
.node.invalidated path {
|
||||
fill: #E0E0E0;
|
||||
/*fill: url(#diagonalHatch);*/
|
||||
}
|
||||
.node.running path {
|
||||
fill: #61B97E;
|
||||
}
|
||||
#legend {
|
||||
font-size: 22px;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
right: 20px;
|
||||
}
|
||||
.color {
|
||||
display: inline-block;
|
||||
border: 1px solid #777;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
.color.normal {
|
||||
background-color: #white;
|
||||
}
|
||||
.color.invalidated {
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
.color.running {
|
||||
background-color: #61B97E;
|
||||
}
|
||||
#triangle {
|
||||
fill: #CCC;
|
||||
}
|
||||
.link {
|
||||
fill: none;
|
||||
stroke: #CCC;
|
||||
stroke-width: 0.5px;
|
||||
}
|
||||
#description {
|
||||
position: fixed;
|
||||
width: 300px;
|
||||
left: 630px;
|
||||
top: 36px;
|
||||
height: auto;
|
||||
display: none;
|
||||
}
|
||||
#timeline {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 20px;
|
||||
transition: height 500ms;
|
||||
}
|
||||
#timeline, #timeline * {
|
||||
cursor: pointer;
|
||||
}
|
||||
#timeline:hover {
|
||||
height: 32px;
|
||||
}
|
||||
#timeline-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 12px;
|
||||
background-color: silver;
|
||||
}
|
||||
#timeline-fill {
|
||||
background-color: #28A3F2;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
transition: width 500ms;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var log = [
|
||||
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr \"dataset\"" },
|
||||
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 1\n $ dataset: chr \"rock\"" },
|
||||
{ "action" : "valueChange", "id" : "input$dataset", "value" : " chr \"rock\"" },
|
||||
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:2] \"caption\" \"dataset\"" },
|
||||
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 2\n $ caption: chr \"Data Summary\"\n $ dataset: chr \"rock\"" },
|
||||
{ "action" : "valueChange", "id" : "input$caption", "value" : " chr \"Data Summary\"" },
|
||||
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
|
||||
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Data Summary\"\n $ obs : num 10\n $ dataset: chr \"rock\"" },
|
||||
{ "action" : "valueChange", "id" : "input$obs", "value" : " num 10" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr \"output_caption_hidden\"" },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 1\n $ output_caption_hidden: logi FALSE" },
|
||||
{ "action" : "valueChange", "id" : "clientData$output_caption_hidden", "value" : " logi FALSE" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:2] \"output_caption_hidden\" \"output_summary_hidden\"" },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 2\n $ output_caption_hidden: logi FALSE\n $ output_summary_hidden: logi FALSE" },
|
||||
{ "action" : "valueChange", "id" : "clientData$output_summary_hidden", "value" : " logi FALSE" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:3] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\"" },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 3\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ output_summary_hidden: logi FALSE" },
|
||||
{ "action" : "valueChange", "id" : "clientData$output_view_hidden", "value" : " logi FALSE" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:4] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 4\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ output_summary_hidden: logi FALSE" },
|
||||
{ "action" : "valueChange", "id" : "clientData$pixelratio", "value" : " num 2" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:5] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 5\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
|
||||
{ "action" : "valueChange", "id" : "clientData$url_protocol", "value" : " chr \"http:\"" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:6] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 6\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
|
||||
{ "action" : "valueChange", "id" : "clientData$url_hostname", "value" : " chr \"localhost\"" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:7] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 7\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
|
||||
{ "action" : "valueChange", "id" : "clientData$url_port", "value" : " chr \"8100\"" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:8] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 8\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
|
||||
{ "action" : "valueChange", "id" : "clientData$url_pathname", "value" : " chr \"/\"" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:9] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 9\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_search : chr \"\"\n $ url_protocol : chr \"http:\"" },
|
||||
{ "action" : "valueChange", "id" : "clientData$url_search", "value" : " chr \"\"" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:10] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 10\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ url_hash_initial : chr \"\"\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_search : chr \"\"\n $ url_protocol : chr \"http:\"" },
|
||||
{ "action" : "valueChange", "id" : "clientData$url_hash_initial", "value" : " chr \"\"" },
|
||||
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:11] \"allowDataUriScheme\" \"output_caption_hidden\" \"output_summary_hidden\" ..." },
|
||||
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 11\n $ allowDataUriScheme : logi TRUE\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ url_hash_initial : chr \"\"\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_search : chr \"\"\n $ url_protocol : chr \"http:\"" },
|
||||
{ "action" : "valueChange", "id" : "clientData$allowDataUriScheme", "value" : " logi TRUE" },
|
||||
{ "action" : "ctx", "id" : "1", "label" : "output$caption <- renderText({ \n input$caption\n})", "type" : "observer", "prevId" : "" },
|
||||
{ "action" : "invalidate", "id" : "1" },
|
||||
{ "action" : "ctx", "id" : "2", "label" : "output$summary <- renderPrint({ \n dataset <- datasetInput()\n summary(dataset)\n})", "type" : "observer", "prevId" : "" },
|
||||
{ "action" : "invalidate", "id" : "2" },
|
||||
{ "action" : "ctx", "id" : "3", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "" },
|
||||
{ "action" : "invalidate", "id" : "3" },
|
||||
{ "action" : "ctx", "id" : "4", "label" : "output$caption <- renderText({ \n input$caption\n})", "type" : "observer", "prevId" : "1" },
|
||||
{ "action" : "enter", "id" : "4" },
|
||||
{ "action" : "dep", "id" : "4", "dependsOn" : "input$caption" },
|
||||
{ "action" : "exit", "id" : "4" },
|
||||
{ "action" : "ctx", "id" : "5", "label" : "output$summary <- renderPrint({ \n dataset <- datasetInput()\n summary(dataset)\n})", "type" : "observer", "prevId" : "2" },
|
||||
{ "action" : "enter", "id" : "5" },
|
||||
{ "action" : "ctx", "id" : "6", "label" : "reactive({ \n switch(input$dataset, rock = rock, pressure = pressure, cars = cars)\n})", "type" : "observable", "prevId" : "" },
|
||||
{ "action" : "enter", "id" : "6" },
|
||||
{ "action" : "dep", "id" : "6", "dependsOn" : "input$dataset" },
|
||||
{ "action" : "exit", "id" : "6" },
|
||||
{ "action" : "depId", "id" : "5", "dependsOn" : "6" },
|
||||
{ "action" : "exit", "id" : "5" },
|
||||
{ "action" : "ctx", "id" : "7", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "3" },
|
||||
{ "action" : "enter", "id" : "7" },
|
||||
{ "action" : "depId", "id" : "7", "dependsOn" : "6" },
|
||||
{ "action" : "dep", "id" : "7", "dependsOn" : "input$obs" },
|
||||
{ "action" : "exit", "id" : "7" },
|
||||
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
|
||||
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Pressure Summary\"\n $ obs : num 10\n $ dataset: chr \"rock\"" },
|
||||
{ "action" : "valueChange", "id" : "input$caption", "value" : " chr \"Pressure Summary\"" },
|
||||
{ "action" : "invalidate", "id" : "4" },
|
||||
{ "action" : "ctx", "id" : "8", "label" : "output$caption <- renderText({ \n input$caption\n})", "type" : "observer", "prevId" : "4" },
|
||||
{ "action" : "enter", "id" : "8" },
|
||||
{ "action" : "dep", "id" : "8", "dependsOn" : "input$caption" },
|
||||
{ "action" : "exit", "id" : "8" },
|
||||
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
|
||||
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Pressure Summary\"\n $ obs : num 10\n $ dataset: chr \"pressure\"" },
|
||||
{ "action" : "valueChange", "id" : "input$dataset", "value" : " chr \"pressure\"" },
|
||||
{ "action" : "invalidate", "id" : "6" },
|
||||
{ "action" : "invalidate", "id" : "5" },
|
||||
{ "action" : "invalidate", "id" : "7" },
|
||||
{ "action" : "ctx", "id" : "9", "label" : "output$summary <- renderPrint({ \n dataset <- datasetInput()\n summary(dataset)\n})", "type" : "observer", "prevId" : "5" },
|
||||
{ "action" : "enter", "id" : "9" },
|
||||
{ "action" : "ctx", "id" : "10", "label" : "reactive({ \n switch(input$dataset, rock = rock, pressure = pressure, cars = cars)\n})", "type" : "observable", "prevId" : "6" },
|
||||
{ "action" : "enter", "id" : "10" },
|
||||
{ "action" : "dep", "id" : "10", "dependsOn" : "input$dataset" },
|
||||
{ "action" : "exit", "id" : "10" },
|
||||
{ "action" : "depId", "id" : "9", "dependsOn" : "10" },
|
||||
{ "action" : "exit", "id" : "9" },
|
||||
{ "action" : "ctx", "id" : "11", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "7" },
|
||||
{ "action" : "enter", "id" : "11" },
|
||||
{ "action" : "depId", "id" : "11", "dependsOn" : "10" },
|
||||
{ "action" : "dep", "id" : "11", "dependsOn" : "input$obs" },
|
||||
{ "action" : "exit", "id" : "11" },
|
||||
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
|
||||
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Pressure Summary\"\n $ obs : num 15\n $ dataset: chr \"pressure\"" },
|
||||
{ "action" : "valueChange", "id" : "input$obs", "value" : " num 15" },
|
||||
{ "action" : "invalidate", "id" : "11" },
|
||||
{ "action" : "ctx", "id" : "12", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "11" },
|
||||
{ "action" : "enter", "id" : "12" },
|
||||
{ "action" : "depId", "id" : "12", "dependsOn" : "10" },
|
||||
{ "action" : "dep", "id" : "12", "dependsOn" : "input$obs" },
|
||||
{ "action" : "exit", "id" : "12" }
|
||||
];
|
||||
try {
|
||||
log = __DATA__;
|
||||
} catch (e) {}
|
||||
|
||||
var nodes = {};
|
||||
var nodeList = [];
|
||||
var nodeSelection = null;
|
||||
var links = [];
|
||||
var linkSelection = null;
|
||||
|
||||
var node, link; // d3 selections
|
||||
|
||||
var MAX_LINES = 6;
|
||||
|
||||
var force = d3.layout.force()
|
||||
.charge(-100)
|
||||
.nodes(nodeList)
|
||||
.links(links);
|
||||
force.on('tick', onTick);
|
||||
|
||||
function pathDataForNode(node) {
|
||||
switch (node.type) {
|
||||
case 'observer':
|
||||
return 'M -25,-50 c -75,0 -75,100 0,100 l 100,0 l 0,-100 Z';
|
||||
case 'observable':
|
||||
return 'M -25,-50 c -75,0 -75,100 0,100 l 60,0 l 50,-50 l -50,-50 Z';
|
||||
case 'value':
|
||||
return 'M -50,-50 l 0,100 l 100,0 l 50,-50 l -50,-50 Z';
|
||||
}
|
||||
}
|
||||
|
||||
function getSourceCoords(node) {
|
||||
switch (node.type) {
|
||||
case 'observer':
|
||||
case 'observable':
|
||||
return {x: node.x - 5, y: node.y};
|
||||
default:
|
||||
return {x: node.x, y: node.y};
|
||||
}
|
||||
}
|
||||
|
||||
function getTargetCoords(node) {
|
||||
switch (node.type) {
|
||||
case 'observable':
|
||||
return {x: node.x + 7, y: node.y};
|
||||
case 'value':
|
||||
return {x: node.x + 8, y: node.y};
|
||||
default:
|
||||
return {x: node.x, y: node.y};
|
||||
}
|
||||
}
|
||||
|
||||
function multilineTextNode(node) {
|
||||
var MAX_LINES = 6;
|
||||
var fade = false;
|
||||
var el = d3.select(this);
|
||||
var lines = el.text().split('\n');
|
||||
if (lines.length > MAX_LINES) {
|
||||
lines.splice(MAX_LINES);
|
||||
fade = true;
|
||||
}
|
||||
el.text('');
|
||||
var tspan = el.selectAll('tspan').data(lines);
|
||||
tspan.enter().append('tspan');
|
||||
tspan
|
||||
.attr('x', 8)
|
||||
.attr('dy', function(line, i) { return i > 0 ? '1em' : 0})
|
||||
.attr('opacity', function(line, i) {
|
||||
if (!fade)
|
||||
return 1;
|
||||
return Math.min(1, (MAX_LINES - i) * 0.25 - 0.15);
|
||||
})
|
||||
.text(function(line) { return line; });
|
||||
}
|
||||
|
||||
function update() {
|
||||
force.size([document.documentElement.clientWidth / 4,
|
||||
document.documentElement.clientHeight / 4]);
|
||||
|
||||
var layoutDirty = false;
|
||||
|
||||
node = d3.select('#nodes').selectAll('.node').data(nodeList);
|
||||
layoutDirty = layoutDirty || !node.enter().empty() || !node.exit().empty();
|
||||
var newG = node.enter().append('g')
|
||||
.attr('class', function(n) {return 'node ' + n.type;})
|
||||
.attr('r', 5)
|
||||
// don't show until next tick
|
||||
.style('display', 'none')
|
||||
.on('mousedown', function() {
|
||||
d3.event.stopPropagation();
|
||||
})
|
||||
.on('mouseover', function(n) {
|
||||
$('#description').text(n.label);
|
||||
})
|
||||
.on('mouseout', function(d, i) {
|
||||
$('#description').html('');
|
||||
})
|
||||
.call(force.drag);
|
||||
newG.append('path')
|
||||
.attr('transform', 'scale(0.08)')
|
||||
.attr('stroke', 'black')
|
||||
.attr('stroke-width', 4)
|
||||
.attr('fill', 'white')
|
||||
.attr('d', pathDataForNode);
|
||||
newG.append('text')
|
||||
.attr('x', 3)
|
||||
.attr('y', 0)
|
||||
.attr('font-size', 3.25)
|
||||
.attr('transform', function(n) {
|
||||
if (n.type !== 'observer')
|
||||
return 'translate(1.5, 0)';
|
||||
else
|
||||
return null;
|
||||
})
|
||||
node.exit().remove();
|
||||
node
|
||||
.classed('invalidated', function(n) { return n.invalidated; })
|
||||
.classed('running', function(n) { return n.running; })
|
||||
.classed('changed', function(n) { return n.changed; })
|
||||
.attr('fill', function(n) {
|
||||
if (n.invalidated)
|
||||
return "url(#diagonalHatch)";
|
||||
else
|
||||
return null;
|
||||
});
|
||||
var tspan = node.selectAll('text').filter(function(n) {
|
||||
// This filter is used to disregard all nodes whose labels have
|
||||
// not changed since the last time we updated them.
|
||||
var changed = n.label !== this.label;
|
||||
this.label = n.label;
|
||||
return changed;
|
||||
}).selectAll('tspan')
|
||||
.data(function(n) {
|
||||
var lines = n.label.replace(/ /g, '\xA0').split('\n');
|
||||
if (lines.length > MAX_LINES) {
|
||||
lines.splice(MAX_LINES);
|
||||
}
|
||||
return lines;
|
||||
});
|
||||
tspan.enter().append('tspan');
|
||||
tspan.exit().remove();
|
||||
tspan
|
||||
.attr('x', 8)
|
||||
.attr('dy', function(line, i) { return i > 0 ? '1em' : 0})
|
||||
.attr('opacity', function(line, i) {
|
||||
return Math.min(1, (MAX_LINES - i) * 0.25 - 0.15);
|
||||
})
|
||||
.text(function(line) { return line; });
|
||||
|
||||
link = d3.select('#links').selectAll('.link').data(links);
|
||||
layoutDirty = layoutDirty || !link.enter().empty() || !link.exit().empty();
|
||||
link.enter().append('path')
|
||||
.attr('class', 'link')
|
||||
.attr('marker-mid', 'url(#triangle)');
|
||||
link.exit().remove();
|
||||
|
||||
if (layoutDirty) {
|
||||
force
|
||||
.nodes(nodeList.filter(function(n) {return !n.hide;}))
|
||||
.start();
|
||||
layoutDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onTick() {
|
||||
node
|
||||
.style('display', null)
|
||||
.attr('transform', function(n) {
|
||||
return 'translate(' + n.x + ' ' + n.y + ')';
|
||||
});
|
||||
link
|
||||
.attr('d', function(link) {
|
||||
var source = getSourceCoords(link.source);
|
||||
var target = getTargetCoords(link.target)
|
||||
var mid = {
|
||||
x: (source.x + target.x) / 2,
|
||||
y: (source.y + target.y) / 2
|
||||
}
|
||||
return 'M' + source.x + ',' + source.y +
|
||||
' L' + mid.x + ',' + mid.y +
|
||||
' L' + target.x + ',' + target.y;
|
||||
});
|
||||
}
|
||||
|
||||
function createNodeWithUndo(data) {
|
||||
var node;
|
||||
if (!data.prevId) {
|
||||
node = {
|
||||
label: data.label,
|
||||
type: data.type,
|
||||
hide: data.hide
|
||||
};
|
||||
nodes[data.id] = node;
|
||||
pushUndo(function() {
|
||||
delete nodes[data.id];
|
||||
});
|
||||
if (!node.hide) {
|
||||
nodeList.push(node);
|
||||
pushUndo(function() {
|
||||
nodeList.pop();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
node = nodes[data.prevId];
|
||||
var oldLabel = node.label;
|
||||
var oldInvalidated = node.invalidated;
|
||||
delete nodes[data.prevId];
|
||||
nodes[data.id] = node;
|
||||
node.label = data.label;
|
||||
node.invalidated = false;
|
||||
pushUndo(function() {
|
||||
node.label = oldLabel;
|
||||
node.invalidated = oldInvalidated;
|
||||
delete nodes[data.id];
|
||||
nodes[data.prevId] = node;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Array.prototype.pushWithUndo = function(value) {
|
||||
var self = this;
|
||||
this.push(value);
|
||||
pushUndo(function() {
|
||||
self.pop();
|
||||
});
|
||||
}
|
||||
|
||||
Array.prototype.shiftWithUndo = function(value) {
|
||||
var self = this;
|
||||
var value = this.shift();
|
||||
pushUndo(function() {
|
||||
self.unshift(value);
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
var undoStack = [];
|
||||
var currentUndos = null;
|
||||
function startUndoScope() {
|
||||
if (currentUndos !== null)
|
||||
throw new Error('Illegal state');
|
||||
currentUndos = [];
|
||||
}
|
||||
function pushUndo(func) {
|
||||
currentUndos.push(func);
|
||||
}
|
||||
function endUndoScope() {
|
||||
var localUndos = currentUndos;
|
||||
undoStack.push(function() {
|
||||
while (localUndos.length) {
|
||||
localUndos.pop()();
|
||||
}
|
||||
});
|
||||
currentUndos = null;
|
||||
}
|
||||
function undo() {
|
||||
if (undoStack.length) {
|
||||
undoStack.pop()();
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function undoAll() {
|
||||
while (undo()) {}
|
||||
}
|
||||
|
||||
// Here we monkeypatch Math.random to take part in the undo mechanism.
|
||||
// This allows "random" d3 force-layout decisions to be reproducible.
|
||||
// If we don't do this, then doing/undoing/redoing a node creation step
|
||||
// looks very confusing, as the node comes flying in from a different
|
||||
// direction each time.
|
||||
var trueRandom = Math.random;
|
||||
Math.random = (function() {
|
||||
var randomStack = [];
|
||||
return function() {
|
||||
if (!currentUndos)
|
||||
return trueRandom();
|
||||
|
||||
var value;
|
||||
if (randomStack.length > 0) {
|
||||
value = randomStack.pop();
|
||||
}
|
||||
else {
|
||||
value = trueRandom();
|
||||
}
|
||||
pushUndo(function() {
|
||||
randomStack.push(value);
|
||||
});
|
||||
return value;
|
||||
};
|
||||
})();
|
||||
|
||||
var callbacks = {
|
||||
ctx: function(data) {
|
||||
createNodeWithUndo(data);
|
||||
return true;
|
||||
},
|
||||
dep: function(data) {
|
||||
var dependsOn = nodes[data.dependsOn];
|
||||
if (!dependsOn) {
|
||||
createNodeWithUndo({id: data.dependsOn, label: data.dependsOn, type: 'value'});
|
||||
dependsOn = nodes[data.dependsOn];
|
||||
}
|
||||
if (dependsOn.hide) {
|
||||
dependsOn.hide = false;
|
||||
nodeList.push(dependsOn);
|
||||
pushUndo(function() {
|
||||
dependsOn.hide = true;
|
||||
nodeList.pop();
|
||||
});
|
||||
}
|
||||
links.push({
|
||||
source: nodes[data.id],
|
||||
target: nodes[data.dependsOn]
|
||||
});
|
||||
pushUndo(function() {
|
||||
links.pop();
|
||||
});
|
||||
},
|
||||
depId: function(data) {
|
||||
links.push({
|
||||
source: nodes[data.id],
|
||||
target: nodes[data.dependsOn]
|
||||
});
|
||||
pushUndo(function() {
|
||||
links.pop();
|
||||
});
|
||||
},
|
||||
invalidate: function(data) {
|
||||
var node = nodes[data.id];
|
||||
if (node.invalidated)
|
||||
throw new Error('Illegal sequence');
|
||||
|
||||
node.invalidated = true;
|
||||
pushUndo(function() {
|
||||
node.invalidated = false;
|
||||
});
|
||||
|
||||
var origLinks = links;
|
||||
links = links.filter(function(link) {
|
||||
return link.source !== node;
|
||||
});
|
||||
pushUndo(function() {
|
||||
links = origLinks;
|
||||
});
|
||||
},
|
||||
valueChange: function(data) {
|
||||
var existed = !!nodes[data.id];
|
||||
createNodeWithUndo({
|
||||
id: data.id,
|
||||
label: data.id + ' = ' + data.value,
|
||||
type: 'value',
|
||||
prevId: nodes[data.id] ? data.id : null,
|
||||
hide: existed ? nodes[data.id].hide : true
|
||||
});
|
||||
if (!existed || nodes[data.id].hide)
|
||||
return true;
|
||||
nodes[data.id].changed = true;
|
||||
pushUndo(function() {
|
||||
nodes[data.id].changed = false;
|
||||
});
|
||||
executeBeforeNextCommand.pushWithUndo(function() {
|
||||
nodes[data.id].changed = false;
|
||||
pushUndo(function() {
|
||||
nodes[data.id].changed = true;
|
||||
});
|
||||
});
|
||||
},
|
||||
enter: function(data) {
|
||||
var node = nodes[data.id];
|
||||
node.running = true;
|
||||
pushUndo(function() {
|
||||
node.running = false;
|
||||
});
|
||||
},
|
||||
exit: function(data) {
|
||||
var node = nodes[data.id];
|
||||
node.running = false;
|
||||
pushUndo(function() {
|
||||
node.running = true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function processMessage(data, suppressUpdate) {
|
||||
console.log(JSON.stringify(data));
|
||||
if (!callbacks.hasOwnProperty(data.action))
|
||||
throw new Error('Unknown action ' + data.action);
|
||||
var result = callbacks[data.action].call(callbacks, data);
|
||||
if (!suppressUpdate)
|
||||
update();
|
||||
return result;
|
||||
}
|
||||
|
||||
var executeBeforeNextCommand = [];
|
||||
function doNext(suppressUpdate) {
|
||||
if (!log.length)
|
||||
return;
|
||||
|
||||
startUndoScope();
|
||||
while (executeBeforeNextCommand.length) {
|
||||
executeBeforeNextCommand.shiftWithUndo()();
|
||||
}
|
||||
while (log.length) {
|
||||
var result = (function() {
|
||||
var message = log.shift();
|
||||
pushUndo(function() {
|
||||
log.unshift(message);
|
||||
})
|
||||
return processMessage(message, suppressUpdate);
|
||||
})();
|
||||
if (!result)
|
||||
break;
|
||||
}
|
||||
if (!log.length) {
|
||||
$('#ended').fadeIn(1500);
|
||||
pushUndo(function() {
|
||||
$('#ended').hide();
|
||||
});
|
||||
}
|
||||
step++;
|
||||
updateTimeline();
|
||||
pushUndo(function() {
|
||||
step--;
|
||||
updateTimeline();
|
||||
});
|
||||
endUndoScope();
|
||||
}
|
||||
|
||||
function countSteps() {
|
||||
if (undoStack.length !== 0) {
|
||||
throw new Error(
|
||||
'Illegal state; must call countSteps before execution begins');
|
||||
}
|
||||
var steps = 0;
|
||||
while (log.length) {
|
||||
doNext();
|
||||
steps++;
|
||||
}
|
||||
while (undoStack.length)
|
||||
undoStack.pop()();
|
||||
return steps;
|
||||
}
|
||||
|
||||
function updateTimeline() {
|
||||
$('#timeline-fill').width((step/totalSteps*100) + '%');
|
||||
}
|
||||
|
||||
function zoom() {
|
||||
var scale = d3.event.scale;
|
||||
var x = d3.event.translate[0];
|
||||
var y = d3.event.translate[1];
|
||||
d3.select('#viz').attr('transform', 'scale(' + scale + ') translate(' + x/scale + ' ' + y/scale + ')');
|
||||
}
|
||||
|
||||
// The total number of steps, as far as the user is concerned, in the log.
|
||||
// This may/will be different than the number of log entries, since each
|
||||
// step may include more than one log entry.
|
||||
var totalSteps;
|
||||
// The current step we're on.
|
||||
var step;
|
||||
$(function() {
|
||||
d3.select('svg').call(d3.behavior.zoom().scale(4).on('zoom', zoom));
|
||||
$(document.body).on('keydown', function(e) {
|
||||
if (e.which === 39 || e.which === 32) { // space, right
|
||||
// Move one step ahead
|
||||
doNext();
|
||||
}
|
||||
if (e.which === 37) { // left
|
||||
// Move one step back
|
||||
undo();
|
||||
}
|
||||
if (e.which === 35) { // end
|
||||
// Seek to end
|
||||
while (log.length) {
|
||||
doNext();
|
||||
}
|
||||
}
|
||||
if (e.which === 36) { // home
|
||||
// Seek to beginning
|
||||
undoAll();
|
||||
}
|
||||
});
|
||||
|
||||
// Timeline click and scrub
|
||||
$('#timeline').on('click mousemove', function(e) {
|
||||
// Make sure left mouse button is down.
|
||||
// Firefox is stupid; e.which is always 1 on mousemove events,
|
||||
// even when button is not down!! So read e.originalEvent.buttons.
|
||||
if (typeof(e.originalEvent.buttons) !== 'undefined') {
|
||||
if (e.originalEvent.buttons !== 1)
|
||||
return;
|
||||
} else if (e.which !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var timeline = e.currentTarget;
|
||||
var pos = e.offsetX || e.originalEvent.layerX;
|
||||
var width = timeline.offsetWidth;
|
||||
var targetStep = Math.round((pos/width) * totalSteps);
|
||||
while (step < targetStep) {
|
||||
doNext();
|
||||
}
|
||||
while (step > targetStep && step != 1) {
|
||||
undo();
|
||||
}
|
||||
});
|
||||
|
||||
totalSteps = countSteps();
|
||||
step = 0;
|
||||
|
||||
doNext();
|
||||
|
||||
// don't allow undoing past initial state
|
||||
while (undoStack.length)
|
||||
undoStack.pop();
|
||||
executeBeforeNextCommand.push(function() {
|
||||
$('#instructions').fadeOut(1000);
|
||||
// It's weird for the instructions to fade back in, so no pushUndo here
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<body>
|
||||
<svg>
|
||||
<defs>
|
||||
<marker id="triangle"
|
||||
viewBox="0 0 10 10"
|
||||
refX="5" refY="5"
|
||||
markerWidth="6"
|
||||
markerHeight="6"
|
||||
orient="auto">
|
||||
<path d="M 10 0 L 0 5 L 10 10 z" />
|
||||
</marker>
|
||||
<pattern id="diagonalHatch" patternUnits="userSpaceOnUse" width="1" height="1">
|
||||
<path stroke="black" stroke-width="0.25" fill="none"
|
||||
d="M-1,1 l2,-2
|
||||
M0,4 l4,-4
|
||||
M3,5 l2,-2" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<g id="viz" transform="scale(4)">
|
||||
<g id="links"></g>
|
||||
<g id="nodes"></g>
|
||||
</g>
|
||||
</svg>
|
||||
<div id="instructions">
|
||||
Press right-arrow to advance
|
||||
</div>
|
||||
<div id="ended" style="display: none;">
|
||||
<strong>You’ve reached the end</strong><br/>Press the Home key to start over
|
||||
</div>
|
||||
<div id="legend">
|
||||
<div class="color normal"></div> Normal<br/>
|
||||
<div class="color invalidated"></div> Invalidated<br/>
|
||||
<div class="color running"></div> Running<br/>
|
||||
</div>
|
||||
<br/>
|
||||
<pre id="description"><br/></pre>
|
||||
<div id="timeline">
|
||||
<div id="timeline-bg">
|
||||
<div id="timeline-fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +1,11 @@
|
||||
/*!
|
||||
* Bootstrap Responsive v2.1.0
|
||||
* Bootstrap Responsive v2.3.2
|
||||
*
|
||||
* Copyright 2012 Twitter, Inc
|
||||
* Copyright 2013 Twitter, Inc
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Designed and built with all the love in the world @twitter by @mdo and @fat.
|
||||
* Designed and built with all the love in the world by @mdo and @fat.
|
||||
*/
|
||||
|
||||
.clearfix {
|
||||
@@ -40,6 +40,10 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@-ms-viewport {
|
||||
width: device-width;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
@@ -91,6 +95,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.visible-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.visible-print {
|
||||
display: inherit !important;
|
||||
}
|
||||
.hidden-print {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.row {
|
||||
margin-left: -30px;
|
||||
@@ -107,6 +124,7 @@
|
||||
}
|
||||
[class*="span"] {
|
||||
float: left;
|
||||
min-height: 1px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
.container,
|
||||
@@ -214,6 +232,9 @@
|
||||
.row-fluid [class*="span"]:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.row-fluid .controls-row [class*="span"] + [class*="span"] {
|
||||
margin-left: 2.564102564102564%;
|
||||
}
|
||||
.row-fluid .span12 {
|
||||
width: 100%;
|
||||
*width: 99.94680851063829%;
|
||||
@@ -453,6 +474,7 @@
|
||||
}
|
||||
[class*="span"] {
|
||||
float: left;
|
||||
min-height: 1px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.container,
|
||||
@@ -560,6 +582,9 @@
|
||||
.row-fluid [class*="span"]:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.row-fluid .controls-row [class*="span"] + [class*="span"] {
|
||||
margin-left: 2.7624309392265194%;
|
||||
}
|
||||
.row-fluid .span12 {
|
||||
width: 100%;
|
||||
*width: 99.94680851063829%;
|
||||
@@ -780,7 +805,8 @@
|
||||
padding-left: 20px;
|
||||
}
|
||||
.navbar-fixed-top,
|
||||
.navbar-fixed-bottom {
|
||||
.navbar-fixed-bottom,
|
||||
.navbar-static-top {
|
||||
margin-right: -20px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
@@ -811,11 +837,15 @@
|
||||
margin-left: 0;
|
||||
}
|
||||
[class*="span"],
|
||||
.uneditable-input[class*="span"],
|
||||
.row-fluid [class*="span"] {
|
||||
display: block;
|
||||
float: none;
|
||||
width: auto;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.span12,
|
||||
.row-fluid .span12 {
|
||||
@@ -824,6 +854,9 @@
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.row-fluid [class*="offset"]:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.input-large,
|
||||
.input-xlarge,
|
||||
.input-xxlarge,
|
||||
@@ -845,6 +878,9 @@
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
.controls-row [class*="span"] + [class*="span"] {
|
||||
margin-left: 0;
|
||||
}
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
@@ -853,8 +889,11 @@
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
.modal.fade {
|
||||
top: -100px;
|
||||
}
|
||||
.modal.fade.in {
|
||||
top: auto;
|
||||
top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,7 +909,7 @@
|
||||
input[type="radio"] {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.form-horizontal .control-group > label {
|
||||
.form-horizontal .control-label {
|
||||
float: none;
|
||||
width: auto;
|
||||
padding-top: 0;
|
||||
@@ -886,6 +925,16 @@
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.media .pull-left,
|
||||
.media .pull-right {
|
||||
display: block;
|
||||
float: none;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.media-object {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
.modal {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
@@ -944,14 +993,14 @@
|
||||
display: none;
|
||||
}
|
||||
.nav-collapse .nav .nav-header {
|
||||
color: #555555;
|
||||
color: #777777;
|
||||
text-shadow: none;
|
||||
}
|
||||
.nav-collapse .nav > li > a,
|
||||
.nav-collapse .dropdown-menu a {
|
||||
padding: 9px 15px;
|
||||
font-weight: bold;
|
||||
color: #555555;
|
||||
color: #777777;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
@@ -967,11 +1016,19 @@
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.nav-collapse .nav > li > a:hover,
|
||||
.nav-collapse .dropdown-menu a:hover {
|
||||
.nav-collapse .nav > li > a:focus,
|
||||
.nav-collapse .dropdown-menu a:hover,
|
||||
.nav-collapse .dropdown-menu a:focus {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
.navbar-inverse .nav-collapse .nav > li > a,
|
||||
.navbar-inverse .nav-collapse .dropdown-menu a {
|
||||
color: #999999;
|
||||
}
|
||||
.navbar-inverse .nav-collapse .nav > li > a:hover,
|
||||
.navbar-inverse .nav-collapse .dropdown-menu a:hover {
|
||||
.navbar-inverse .nav-collapse .nav > li > a:focus,
|
||||
.navbar-inverse .nav-collapse .dropdown-menu a:hover,
|
||||
.navbar-inverse .nav-collapse .dropdown-menu a:focus {
|
||||
background-color: #111111;
|
||||
}
|
||||
.nav-collapse.in .btn-group {
|
||||
@@ -982,7 +1039,7 @@
|
||||
position: static;
|
||||
top: auto;
|
||||
left: auto;
|
||||
display: block;
|
||||
display: none;
|
||||
float: none;
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
@@ -996,6 +1053,9 @@
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.nav-collapse .open > .dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
.nav-collapse .dropdown-menu:before,
|
||||
.nav-collapse .dropdown-menu:after {
|
||||
display: none;
|
||||
@@ -1003,6 +1063,10 @@
|
||||
.nav-collapse .dropdown-menu .divider {
|
||||
display: none;
|
||||
}
|
||||
.nav-collapse .nav > li > .dropdown-menu:before,
|
||||
.nav-collapse .nav > li > .dropdown-menu:after {
|
||||
display: none;
|
||||
}
|
||||
.nav-collapse .navbar-form,
|
||||
.nav-collapse .navbar-search {
|
||||
float: none;
|
||||
@@ -1014,6 +1078,11 @@
|
||||
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.navbar-inverse .nav-collapse .navbar-form,
|
||||
.navbar-inverse .nav-collapse .navbar-search {
|
||||
border-top-color: #111111;
|
||||
border-bottom-color: #111111;
|
||||
}
|
||||
.navbar .nav-collapse .nav.pull-right {
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
|
||||
File diff suppressed because one or more lines are too long
1557
inst/www/shared/bootstrap/css/bootstrap.css
vendored
1557
inst/www/shared/bootstrap/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
669
inst/www/shared/bootstrap/js/bootstrap.js
vendored
669
inst/www/shared/bootstrap/js/bootstrap.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
123
inst/www/shared/datatables/css/DT_bootstrap.css
Normal file
123
inst/www/shared/datatables/css/DT_bootstrap.css
Normal file
@@ -0,0 +1,123 @@
|
||||
|
||||
div.dataTables_length label {
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.dataTables_length select {
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
div.dataTables_filter label {
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.dataTables_info {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
div.dataTables_paginate {
|
||||
float: right;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table.table {
|
||||
clear: both;
|
||||
margin-bottom: 6px !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
table.table thead .sorting,
|
||||
table.table thead .sorting_asc,
|
||||
table.table thead .sorting_desc,
|
||||
table.table thead .sorting_asc_disabled,
|
||||
table.table thead .sorting_desc_disabled {
|
||||
cursor: pointer;
|
||||
*cursor: hand;
|
||||
}
|
||||
|
||||
table.table thead .sorting { background: url('images/sort_both.png') no-repeat center right; }
|
||||
table.table thead .sorting_asc { background: url('images/sort_asc.png') no-repeat center right; }
|
||||
table.table thead .sorting_desc { background: url('images/sort_desc.png') no-repeat center right; }
|
||||
|
||||
table.table thead .sorting_asc_disabled { background: url('images/sort_asc_disabled.png') no-repeat center right; }
|
||||
table.table thead .sorting_desc_disabled { background: url('images/sort_desc_disabled.png') no-repeat center right; }
|
||||
|
||||
table.dataTable th:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
table.dataTable tr.odd td.sorting_1 { background-color: #D3D6FF; }
|
||||
table.dataTable tr.odd td.sorting_2 { background-color: #DADCFF; }
|
||||
table.dataTable tr.odd td.sorting_3 { background-color: #E0E2FF; }
|
||||
table.dataTable tr.even td.sorting_1 { background-color: #EAEBFF; }
|
||||
table.dataTable tr.even td.sorting_2 { background-color: #F2F3FF; }
|
||||
table.dataTable tr.even td.sorting_3 { background-color: #F9F9FF; }
|
||||
|
||||
/* Scrolling */
|
||||
div.dataTables_scrollHead table {
|
||||
margin-bottom: 0 !important;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
div.dataTables_scrollHead table thead tr:last-child th:first-child,
|
||||
div.dataTables_scrollHead table thead tr:last-child td:first-child {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
div.dataTables_scrollBody table {
|
||||
border-top: none;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
div.dataTables_scrollBody tbody tr:first-child th,
|
||||
div.dataTables_scrollBody tbody tr:first-child td {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
div.dataTables_scrollFoot table {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* Active rows */
|
||||
|
||||
.table tbody tr.active td,
|
||||
.table tbody tr.active th {
|
||||
background-color: #08C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.table tbody tr.active:hover td,
|
||||
.table tbody tr.active:hover th {
|
||||
background-color: #0075b0 !important;
|
||||
}
|
||||
|
||||
.table-striped tbody tr.active:nth-child(odd) td,
|
||||
.table-striped tbody tr.active:nth-child(odd) th {
|
||||
background-color: #017ebc;
|
||||
}
|
||||
|
||||
/* Processing indicator */
|
||||
|
||||
.dataTables_processing {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 250px;
|
||||
height: 30px;
|
||||
margin-left: -125px;
|
||||
margin-top: -15px;
|
||||
padding: 14px 0 2px 0;
|
||||
border: 1px solid #ddd;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* Search boxes in the footer */
|
||||
table tfoot input {
|
||||
width: 100%;
|
||||
}
|
||||
BIN
inst/www/shared/datatables/css/images/sort_asc.png
Normal file
BIN
inst/www/shared/datatables/css/images/sort_asc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
inst/www/shared/datatables/css/images/sort_asc_disabled.png
Normal file
BIN
inst/www/shared/datatables/css/images/sort_asc_disabled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
inst/www/shared/datatables/css/images/sort_both.png
Normal file
BIN
inst/www/shared/datatables/css/images/sort_both.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user