mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 07:58:11 -05:00
Compare commits
1259 Commits
v0.10.1
...
joe/bugfix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d04b21876 | ||
|
|
e38c9443d9 | ||
|
|
fb4ad55cac | ||
|
|
28547e90d1 | ||
|
|
f70187597f | ||
|
|
333e454e78 | ||
|
|
61cfd11644 | ||
|
|
1fea54ca5a | ||
|
|
faccc42b22 | ||
|
|
a45b58d956 | ||
|
|
2c5e9a5e76 | ||
|
|
9fb847b179 | ||
|
|
b8341b2ba8 | ||
|
|
26d6e4da2c | ||
|
|
b16ed602d5 | ||
|
|
24aab4d5d3 | ||
|
|
2eb69d421a | ||
|
|
177a2a8a1e | ||
|
|
8b21a87175 | ||
|
|
6d2dd8e315 | ||
|
|
8b3aff599b | ||
|
|
4c652389c5 | ||
|
|
bf52075d1b | ||
|
|
77a9b66028 | ||
|
|
e813dab81c | ||
|
|
360c1d5953 | ||
|
|
9588c36abb | ||
|
|
f9200ac135 | ||
|
|
fffb9606ec | ||
|
|
e92eee5ffc | ||
|
|
293c1d471c | ||
|
|
384240b6a4 | ||
|
|
2269e05058 | ||
|
|
dd7a3269ad | ||
|
|
157d1b20c5 | ||
|
|
85fe0c00c2 | ||
|
|
91092b8a96 | ||
|
|
1ed237cfcc | ||
|
|
c7044498d5 | ||
|
|
1d2a2fbcae | ||
|
|
9b015e8cae | ||
|
|
0a8c26fff4 | ||
|
|
506de72666 | ||
|
|
a5b4156b56 | ||
|
|
da4b42cb1d | ||
|
|
53790f8247 | ||
|
|
69780d4727 | ||
|
|
aa2b644684 | ||
|
|
a12e8875a6 | ||
|
|
9e91b265ce | ||
|
|
8c12e3ab90 | ||
|
|
7e303b4fc0 | ||
|
|
40e0fcff30 | ||
|
|
3c9e74b23e | ||
|
|
6b001eb7c3 | ||
|
|
f81621aa66 | ||
|
|
08c7484087 | ||
|
|
a8c68f3e30 | ||
|
|
0e6698d760 | ||
|
|
f3d4f9ff23 | ||
|
|
d711f17081 | ||
|
|
d35eba45c5 | ||
|
|
cd53e79b19 | ||
|
|
3db7029534 | ||
|
|
ad1e52bf19 | ||
|
|
e08791a284 | ||
|
|
8d1deeb568 | ||
|
|
375c7789a2 | ||
|
|
ec8a81aedb | ||
|
|
033d513aee | ||
|
|
fb3e4e4881 | ||
|
|
8a30c006e7 | ||
|
|
3f76679673 | ||
|
|
1cee5d4b41 | ||
|
|
3107eec697 | ||
|
|
477d46316e | ||
|
|
3133693a0e | ||
|
|
bc7d701298 | ||
|
|
5d6d75b4f3 | ||
|
|
73d48a7b37 | ||
|
|
ed7b9a9989 | ||
|
|
e1a955752f | ||
|
|
0bdc8f0b2b | ||
|
|
a692b3ced8 | ||
|
|
2f5b93861d | ||
|
|
110183585c | ||
|
|
7eb29586a7 | ||
|
|
401065a23e | ||
|
|
4e5e0fb0ce | ||
|
|
d41a06611e | ||
|
|
26c3c27726 | ||
|
|
19ab63e041 | ||
|
|
5dafdab3d7 | ||
|
|
afbb17d428 | ||
|
|
8a721fbd25 | ||
|
|
5d91a409e7 | ||
|
|
8470f7caf8 | ||
|
|
67e279928e | ||
|
|
77ac3a62b7 | ||
|
|
12eaa3a162 | ||
|
|
bbd5dd7b4f | ||
|
|
38fcd6e267 | ||
|
|
fd7f683eaa | ||
|
|
e15f9acd91 | ||
|
|
7cb0882c73 | ||
|
|
486d4d1c88 | ||
|
|
ded8b13e96 | ||
|
|
c7eb7ba861 | ||
|
|
4920bff8fd | ||
|
|
d78edf5dda | ||
|
|
7510c02d83 | ||
|
|
2d7b729473 | ||
|
|
0495fe2d71 | ||
|
|
d7da5df734 | ||
|
|
4462b6bd39 | ||
|
|
80e1edeeb2 | ||
|
|
11af421f10 | ||
|
|
686ff235e7 | ||
|
|
31f76a6d4d | ||
|
|
50078078e0 | ||
|
|
be85e1e2f7 | ||
|
|
9ad1574292 | ||
|
|
4b71825707 | ||
|
|
fb1fd88947 | ||
|
|
dca527d8b6 | ||
|
|
3452a445fe | ||
|
|
a06e9d2bef | ||
|
|
7a3961a280 | ||
|
|
54729d8fb4 | ||
|
|
c2e17ee182 | ||
|
|
bc0064d4b9 | ||
|
|
03685dbb61 | ||
|
|
26fcba8ed5 | ||
|
|
bc15b65538 | ||
|
|
e9ab34a9c1 | ||
|
|
0bf512ebdd | ||
|
|
7646fbeaa0 | ||
|
|
84b4766013 | ||
|
|
3a48734b2f | ||
|
|
36ae332959 | ||
|
|
3e0d8da9d6 | ||
|
|
2fcb4dbe50 | ||
|
|
09c93bfb39 | ||
|
|
34068b1598 | ||
|
|
a67da1c99a | ||
|
|
0d6754761d | ||
|
|
898f7b66cf | ||
|
|
c18f3e86f0 | ||
|
|
de51922f10 | ||
|
|
be0cb18bfc | ||
|
|
39fd1db3c0 | ||
|
|
b4565e7354 | ||
|
|
e28cada4dd | ||
|
|
6daac65968 | ||
|
|
1ecc49c450 | ||
|
|
f96e7d9aaa | ||
|
|
c637bba867 | ||
|
|
bdc6554ca8 | ||
|
|
ecb59e9c31 | ||
|
|
1b39184e98 | ||
|
|
2a35ba64f7 | ||
|
|
3a5123627d | ||
|
|
a18eeecd59 | ||
|
|
85e3f04738 | ||
|
|
cc59864377 | ||
|
|
5b10cbf2e2 | ||
|
|
fc6b83bb5d | ||
|
|
bc509f55d9 | ||
|
|
f81301ece6 | ||
|
|
382e5c1f43 | ||
|
|
0243f74dcd | ||
|
|
58737ef454 | ||
|
|
940cea82ca | ||
|
|
5683e36733 | ||
|
|
f5137b7935 | ||
|
|
0c2af42c69 | ||
|
|
760dc5d0c6 | ||
|
|
5331aa08a7 | ||
|
|
375d7cc7b1 | ||
|
|
a05f3dd640 | ||
|
|
b91c1b44ba | ||
|
|
6efb01a397 | ||
|
|
1843eca6c0 | ||
|
|
506e3e8a48 | ||
|
|
0e5a3cc5aa | ||
|
|
d2dd76e13d | ||
|
|
470b82fd64 | ||
|
|
e04dd3a4b1 | ||
|
|
2d39e06c97 | ||
|
|
e1fc74bdc1 | ||
|
|
3ab5d7f861 | ||
|
|
d63dd6086a | ||
|
|
a8d9895a9b | ||
|
|
f8a7257af3 | ||
|
|
4703028988 | ||
|
|
87523cdbd5 | ||
|
|
d9567ed035 | ||
|
|
0ab277662a | ||
|
|
2eeb94e39c | ||
|
|
4b441d10b3 | ||
|
|
37a1d3d61e | ||
|
|
3839338c15 | ||
|
|
bdee5790e6 | ||
|
|
d0dab25dae | ||
|
|
b14b7b00c2 | ||
|
|
248bfcccda | ||
|
|
9b5833205b | ||
|
|
07f8589090 | ||
|
|
f77f83dfeb | ||
|
|
e3d3d916ba | ||
|
|
cccf219cd2 | ||
|
|
0896b2f7b8 | ||
|
|
cc406262ac | ||
|
|
0f20063eb8 | ||
|
|
5f32b165f2 | ||
|
|
3cadd1789b | ||
|
|
e486778b36 | ||
|
|
7fe6453bbb | ||
|
|
9f88d2b6d6 | ||
|
|
8f9d52699d | ||
|
|
0a774a8c55 | ||
|
|
d4ced34a11 | ||
|
|
85a762a0b9 | ||
|
|
b255fecc6e | ||
|
|
734d2e2594 | ||
|
|
2e292b4636 | ||
|
|
f0bc7356ac | ||
|
|
1bcb6ab931 | ||
|
|
ef65937662 | ||
|
|
3369b8b5b2 | ||
|
|
28db561cd9 | ||
|
|
0622326e1b | ||
|
|
c6e2593e4e | ||
|
|
d0e3279a67 | ||
|
|
aee5bda9ec | ||
|
|
979b4a8861 | ||
|
|
c10cd4b474 | ||
|
|
4aa1d19845 | ||
|
|
7ff51d89fc | ||
|
|
ea9d94e42f | ||
|
|
a9ba0fdb0b | ||
|
|
af19c3331c | ||
|
|
5e98b930ee | ||
|
|
057d160392 | ||
|
|
6b2899c219 | ||
|
|
85290e687c | ||
|
|
d778e81f42 | ||
|
|
2bfad21604 | ||
|
|
373e0d3a9f | ||
|
|
5e83403d0c | ||
|
|
cbe76aab83 | ||
|
|
26de088520 | ||
|
|
98430edb17 | ||
|
|
48c6784e51 | ||
|
|
dc0f5af3ef | ||
|
|
af85e6f2a6 | ||
|
|
4e91af4d64 | ||
|
|
faf87a5dee | ||
|
|
517c5d356f | ||
|
|
931be22247 | ||
|
|
8697360eb7 | ||
|
|
e3a867132a | ||
|
|
c96debadc5 | ||
|
|
02520d4f54 | ||
|
|
5070b63d5b | ||
|
|
eaa722b10d | ||
|
|
1bc3c90286 | ||
|
|
afd00edee3 | ||
|
|
b712398208 | ||
|
|
7586e91b4f | ||
|
|
9eba82c107 | ||
|
|
ccdc219a09 | ||
|
|
60d01e76e9 | ||
|
|
b5cfd4152e | ||
|
|
32c4c8ae32 | ||
|
|
bd4c506d22 | ||
|
|
476dd7cd56 | ||
|
|
8176f84715 | ||
|
|
6bd33721d8 | ||
|
|
c9d9671288 | ||
|
|
2a821edf5f | ||
|
|
68b85bdc87 | ||
|
|
83cf5907c3 | ||
|
|
c912b6547c | ||
|
|
bf04b74f87 | ||
|
|
9d1e008990 | ||
|
|
d9e5285a3b | ||
|
|
84937b7a0b | ||
|
|
924b3e16cf | ||
|
|
2a8cf01410 | ||
|
|
a3a5cfee6c | ||
|
|
2c04441591 | ||
|
|
a4eab8e216 | ||
|
|
189f9589d4 | ||
|
|
880721e0d0 | ||
|
|
6ab65e2031 | ||
|
|
e871934cfd | ||
|
|
686390c1f2 | ||
|
|
a8b9fb1708 | ||
|
|
55d3764169 | ||
|
|
cb5bc3d631 | ||
|
|
543e66eb00 | ||
|
|
b658983fb8 | ||
|
|
cfb3e42337 | ||
|
|
36815b5e43 | ||
|
|
897e077aca | ||
|
|
f395960ffa | ||
|
|
fb301717f5 | ||
|
|
46da93519f | ||
|
|
ce0f2c51a9 | ||
|
|
04b4b8da4f | ||
|
|
877d7451dd | ||
|
|
7e6a68a2b1 | ||
|
|
caca515ba0 | ||
|
|
d548b78dee | ||
|
|
f2410abc48 | ||
|
|
483a7d34c5 | ||
|
|
e872411285 | ||
|
|
fc7e6bf542 | ||
|
|
16d42b6421 | ||
|
|
2f25d25eec | ||
|
|
be1081a4b9 | ||
|
|
1608b652d7 | ||
|
|
5dd19a878c | ||
|
|
3314f4b5b8 | ||
|
|
5977e0fe89 | ||
|
|
f477dcba4a | ||
|
|
6c5f0c5379 | ||
|
|
257eb1bed0 | ||
|
|
9c4d142c2d | ||
|
|
8e89a1f154 | ||
|
|
b0952c0374 | ||
|
|
ac95dcb3f2 | ||
|
|
ce4043f038 | ||
|
|
0d26857e31 | ||
|
|
85bea95f6b | ||
|
|
10a46c507f | ||
|
|
d35d76e1d0 | ||
|
|
aaa05b22df | ||
|
|
c5fa30f0de | ||
|
|
43fe1a9a0e | ||
|
|
aa296fcb69 | ||
|
|
b9c7023489 | ||
|
|
efcd286039 | ||
|
|
98014f9495 | ||
|
|
2702a18ea2 | ||
|
|
4a8da3e1e2 | ||
|
|
cfe38c00f3 | ||
|
|
af0463ed46 | ||
|
|
c02f4691e0 | ||
|
|
5d89393fff | ||
|
|
e7ce28204b | ||
|
|
8fc4a75e8c | ||
|
|
26c89a09e8 | ||
|
|
25a1493520 | ||
|
|
b18722f776 | ||
|
|
3f3fd9ae21 | ||
|
|
94ea3c7dab | ||
|
|
6c2fea7926 | ||
|
|
e08fd47b0e | ||
|
|
0fd76e8768 | ||
|
|
72aaf3055a | ||
|
|
94a943a68c | ||
|
|
e867dcfdb1 | ||
|
|
9a22a89b06 | ||
|
|
791e8200bc | ||
|
|
d96217d49a | ||
|
|
6bfd65aa19 | ||
|
|
2da9bc07ac | ||
|
|
6d7a562b7a | ||
|
|
0aa1dfb8e1 | ||
|
|
e9e7dc298f | ||
|
|
ed3b71e396 | ||
|
|
3450a037a9 | ||
|
|
f57626d256 | ||
|
|
c1c3fa4d3a | ||
|
|
300433f7de | ||
|
|
eee6f4ed81 | ||
|
|
2eb29bd8aa | ||
|
|
3a0ce86f51 | ||
|
|
6041b8cbb2 | ||
|
|
3ba8fcb7b8 | ||
|
|
f74d9c93a2 | ||
|
|
739c162281 | ||
|
|
a2700c900d | ||
|
|
4e1caee7da | ||
|
|
76a54249bb | ||
|
|
0e894cb043 | ||
|
|
01bbee59eb | ||
|
|
26a0c3520c | ||
|
|
6056c35de3 | ||
|
|
4202991ca5 | ||
|
|
788931c7c7 | ||
|
|
b2d0505c7c | ||
|
|
8b710d651f | ||
|
|
93697bb01d | ||
|
|
89cd58e4f8 | ||
|
|
a622f029a0 | ||
|
|
97afb52904 | ||
|
|
02ea31be08 | ||
|
|
b5e49a6619 | ||
|
|
179c931f85 | ||
|
|
4d3fa2c8ac | ||
|
|
8e4f7387d0 | ||
|
|
feb630b2c5 | ||
|
|
948dfbb56b | ||
|
|
5c3ac75b34 | ||
|
|
adc5c8e37a | ||
|
|
52d594c143 | ||
|
|
1018b0d966 | ||
|
|
0ce153d788 | ||
|
|
ff9756c739 | ||
|
|
b3dd7e5397 | ||
|
|
6ac0a80896 | ||
|
|
93f774c7e7 | ||
|
|
661c08549d | ||
|
|
c8acc44012 | ||
|
|
aabf00659e | ||
|
|
a9bc41492c | ||
|
|
12b0484e9a | ||
|
|
d3605dbcb3 | ||
|
|
d9a016f94c | ||
|
|
a13657ac23 | ||
|
|
d6c95a9e89 | ||
|
|
825e9e04c1 | ||
|
|
c596e44c5a | ||
|
|
66be9004fe | ||
|
|
119ebb0f07 | ||
|
|
d509fcac29 | ||
|
|
c79e933586 | ||
|
|
3c3cfc02a0 | ||
|
|
e32de7b940 | ||
|
|
d7b1759afb | ||
|
|
e391c1fda3 | ||
|
|
46d8d3b469 | ||
|
|
9fde7509fa | ||
|
|
a767a61f43 | ||
|
|
ad28e03536 | ||
|
|
5f9e9c2e03 | ||
|
|
2be6d7a65c | ||
|
|
712c4cb985 | ||
|
|
7948a0a4fa | ||
|
|
ce9e95f256 | ||
|
|
7ed5ca94a2 | ||
|
|
e635589c52 | ||
|
|
490721437f | ||
|
|
7e25a1566f | ||
|
|
c08b3b0c30 | ||
|
|
f1de132a2a | ||
|
|
c440c60bdf | ||
|
|
c367176a17 | ||
|
|
3d13c39a4c | ||
|
|
07f2792cf9 | ||
|
|
303fce5f15 | ||
|
|
46c8bfdd34 | ||
|
|
c36a22ad5e | ||
|
|
49eba95a9c | ||
|
|
2dacc6ce40 | ||
|
|
5ccf02f5c3 | ||
|
|
86c67de8ff | ||
|
|
dac7eb5997 | ||
|
|
fd725552a5 | ||
|
|
1e1a897970 | ||
|
|
59a643c006 | ||
|
|
1a492208e6 | ||
|
|
bc0a19f55d | ||
|
|
85e2e00bc4 | ||
|
|
93dd8bbf28 | ||
|
|
2f15a219df | ||
|
|
4fc73b1344 | ||
|
|
48b56ba08d | ||
|
|
94ca733c7c | ||
|
|
4af2436a0e | ||
|
|
20c2bb9d50 | ||
|
|
33bac0db3c | ||
|
|
8893db9098 | ||
|
|
c46658a5c8 | ||
|
|
5f651aed3e | ||
|
|
534bc9c6e2 | ||
|
|
bb09885237 | ||
|
|
3ff6aaa6db | ||
|
|
fa7c034d16 | ||
|
|
ca870ccd75 | ||
|
|
89fe2ff217 | ||
|
|
6c6775376e | ||
|
|
bd3b3881d8 | ||
|
|
078189599c | ||
|
|
9afcd2a411 | ||
|
|
0afd1649c1 | ||
|
|
0947a63103 | ||
|
|
79223bddc5 | ||
|
|
1871dd6b71 | ||
|
|
4adc3088d1 | ||
|
|
79dd7e1bf5 | ||
|
|
6718d377bb | ||
|
|
d4e3329d7a | ||
|
|
99295f0983 | ||
|
|
3606c36cb9 | ||
|
|
3d5c184acc | ||
|
|
9e03b17498 | ||
|
|
129714b044 | ||
|
|
67823556d2 | ||
|
|
273e71e3c4 | ||
|
|
076ac26929 | ||
|
|
5a022b0a2c | ||
|
|
9ab493a81f | ||
|
|
dfc1f32595 | ||
|
|
e6fd30fb78 | ||
|
|
c8d338912a | ||
|
|
d9d9e0b33f | ||
|
|
1da7b83956 | ||
|
|
29c545d2e3 | ||
|
|
431b345c82 | ||
|
|
8773b1b38f | ||
|
|
52efb3dc16 | ||
|
|
9ccd179b04 | ||
|
|
a8b35c49a7 | ||
|
|
4e027f1a45 | ||
|
|
119182454b | ||
|
|
04e10a4f0d | ||
|
|
4e5c5f9c5b | ||
|
|
1ee2a25eca | ||
|
|
838e132515 | ||
|
|
da76a843ee | ||
|
|
b0676b8b31 | ||
|
|
83c3656d29 | ||
|
|
5ddd6cc94e | ||
|
|
90419765af | ||
|
|
acad9354a6 | ||
|
|
9105dd7b04 | ||
|
|
71adee1f38 | ||
|
|
3cbe3831ec | ||
|
|
619aa4f05a | ||
|
|
4df37d6f3e | ||
|
|
58f8b482f5 | ||
|
|
168f5e32af | ||
|
|
06bf28f10c | ||
|
|
620fdc0d9f | ||
|
|
bdac0e2456 | ||
|
|
60a57a0a40 | ||
|
|
a242ae3849 | ||
|
|
ced4060b5c | ||
|
|
0b3eb7a237 | ||
|
|
c82f87cd76 | ||
|
|
51d8a6d9bf | ||
|
|
d334aa2088 | ||
|
|
710e003bdc | ||
|
|
b2f5b4f861 | ||
|
|
0ac87930c8 | ||
|
|
241a482236 | ||
|
|
2abaffafcf | ||
|
|
4545fedf31 | ||
|
|
a47a690a68 | ||
|
|
f89c44e899 | ||
|
|
59b0df5c82 | ||
|
|
5ec6ffb30a | ||
|
|
5956d2009c | ||
|
|
d9c7f21c02 | ||
|
|
926e508b8d | ||
|
|
ac83772945 | ||
|
|
cddf5cf70f | ||
|
|
d53acdb46a | ||
|
|
cfae8f4fc6 | ||
|
|
74cd4cecbf | ||
|
|
3e9e6a1389 | ||
|
|
9788450c08 | ||
|
|
10b27aed34 | ||
|
|
64f95be828 | ||
|
|
a54634023b | ||
|
|
9d942b78ef | ||
|
|
4cd5357241 | ||
|
|
f985a96988 | ||
|
|
0e3938da79 | ||
|
|
ec9bfc4731 | ||
|
|
9b91ebb8d2 | ||
|
|
da3f2367d7 | ||
|
|
17cdeec34b | ||
|
|
3446afd087 | ||
|
|
b12fef652c | ||
|
|
21c7193281 | ||
|
|
a5e64274a2 | ||
|
|
3817202875 | ||
|
|
874fcb12a1 | ||
|
|
e0c5783703 | ||
|
|
a57e037b05 | ||
|
|
8546918cbb | ||
|
|
82284029f2 | ||
|
|
7c20e865a5 | ||
|
|
79267d4e12 | ||
|
|
50aeb70597 | ||
|
|
1d22a79074 | ||
|
|
7f442f4206 | ||
|
|
985326989c | ||
|
|
be8f2afa37 | ||
|
|
98882984b4 | ||
|
|
a6cd0fdb85 | ||
|
|
7bc5ba7e9a | ||
|
|
37e552cd36 | ||
|
|
51e2a4de7d | ||
|
|
91ce2fcb06 | ||
|
|
925a379702 | ||
|
|
3153cfd0ff | ||
|
|
ac8831b4c7 | ||
|
|
acc535e1a4 | ||
|
|
fdacb4fe7d | ||
|
|
fc7208469d | ||
|
|
5c38cb733a | ||
|
|
515a67a320 | ||
|
|
941348f1db | ||
|
|
8d7752b0bc | ||
|
|
15af660424 | ||
|
|
790555ae89 | ||
|
|
3cc4df4e29 | ||
|
|
395d1cee70 | ||
|
|
89bc7efbca | ||
|
|
8f893a9752 | ||
|
|
54e02e412c | ||
|
|
808d7aab3f | ||
|
|
24a8f8f38b | ||
|
|
90c00bed2f | ||
|
|
054c911a1f | ||
|
|
c2d5432a5d | ||
|
|
dd64b70f5b | ||
|
|
a69dbeb10f | ||
|
|
976a768446 | ||
|
|
5612cec91f | ||
|
|
46996eb81c | ||
|
|
12990f9fb2 | ||
|
|
77ff988232 | ||
|
|
8df98c29b8 | ||
|
|
7554f8395b | ||
|
|
6fc0bac106 | ||
|
|
d252feddc9 | ||
|
|
8c2645498d | ||
|
|
528acc4aa4 | ||
|
|
2ce45eab06 | ||
|
|
074c24aa10 | ||
|
|
79e4007732 | ||
|
|
742ce6673c | ||
|
|
4a74f588b9 | ||
|
|
f876dc066c | ||
|
|
65aaf386c2 | ||
|
|
4eae6bd362 | ||
|
|
4f7cfd6bd4 | ||
|
|
1136ad09ee | ||
|
|
4a67bd945c | ||
|
|
a2841f7cf2 | ||
|
|
4fa6e9dafe | ||
|
|
7ebd7959f9 | ||
|
|
4386015cff | ||
|
|
5cd014f7e6 | ||
|
|
30d0bfbdf0 | ||
|
|
48cfeca220 | ||
|
|
5fefc48a0b | ||
|
|
42f2ae16ec | ||
|
|
3d47b0201f | ||
|
|
7607b1215f | ||
|
|
eae2b40898 | ||
|
|
8e253046d8 | ||
|
|
b0e17f02b5 | ||
|
|
3f3c131737 | ||
|
|
2b227fcca5 | ||
|
|
639e55b537 | ||
|
|
8386404b25 | ||
|
|
a093afb630 | ||
|
|
7208688128 | ||
|
|
b94d406bd9 | ||
|
|
79188b7d62 | ||
|
|
85b2fc503d | ||
|
|
57f33109b2 | ||
|
|
d039547886 | ||
|
|
55621b7826 | ||
|
|
854730f258 | ||
|
|
9c4f73f314 | ||
|
|
e1fa491af7 | ||
|
|
b909a3e05c | ||
|
|
4159c539e7 | ||
|
|
d1bd232ec7 | ||
|
|
a47898a2c4 | ||
|
|
06b69e516a | ||
|
|
312c89aaee | ||
|
|
a5b1f020ae | ||
|
|
e9cd1bef43 | ||
|
|
7b17ce5de1 | ||
|
|
ffb1b06bf4 | ||
|
|
21e9ffec97 | ||
|
|
7cfaa2adfc | ||
|
|
4e80e35976 | ||
|
|
e9f78f6ace | ||
|
|
4d074c6fa9 | ||
|
|
0b3c2e198e | ||
|
|
48cd7200cc | ||
|
|
d925702b98 | ||
|
|
55cbd72a47 | ||
|
|
fbf1ba172f | ||
|
|
abb722f405 | ||
|
|
2c121dc2c3 | ||
|
|
dbcdbda2ef | ||
|
|
03784ba82e | ||
|
|
04768ad8fa | ||
|
|
29943408e5 | ||
|
|
dbadc05bef | ||
|
|
2e6347b380 | ||
|
|
6c65b1ffde | ||
|
|
358416687f | ||
|
|
1dc061e1c6 | ||
|
|
e7f2572f42 | ||
|
|
463ee7e027 | ||
|
|
667bc95d02 | ||
|
|
3f8cc71814 | ||
|
|
a9a6e96a6a | ||
|
|
02caf05aaf | ||
|
|
da88678a43 | ||
|
|
1e8a9de60a | ||
|
|
1a6181cb15 | ||
|
|
ece69342b8 | ||
|
|
fe601d631b | ||
|
|
42d2e5aaef | ||
|
|
bac7ab7f01 | ||
|
|
c6ac1474d4 | ||
|
|
5a9c4ad8f3 | ||
|
|
babc59f85c | ||
|
|
982a4361cd | ||
|
|
15e53ca55e | ||
|
|
ceb428b8bd | ||
|
|
b7fe3ed745 | ||
|
|
817b614e61 | ||
|
|
3321e95b54 | ||
|
|
fd5f536d14 | ||
|
|
fdbcb32be7 | ||
|
|
a774cee1d9 | ||
|
|
1c46f227d2 | ||
|
|
6191c96347 | ||
|
|
12918fdfee | ||
|
|
8cea246fc8 | ||
|
|
41455a008b | ||
|
|
be22d5c95d | ||
|
|
b298a62e46 | ||
|
|
839045ed66 | ||
|
|
a4a5c61fef | ||
|
|
dd6aa9e4cf | ||
|
|
e299b51779 | ||
|
|
d3f0129c3f | ||
|
|
ae938c66cc | ||
|
|
8d662f4f89 | ||
|
|
a1caa41fe9 | ||
|
|
6028263681 | ||
|
|
30f9291496 | ||
|
|
2400a44ded | ||
|
|
952556a9d1 | ||
|
|
2e18b5c64b | ||
|
|
cb499c6105 | ||
|
|
7f87bcddc1 | ||
|
|
6798917bc9 | ||
|
|
cf3f32ac37 | ||
|
|
a41e1dafc2 | ||
|
|
3ac5124e5c | ||
|
|
83a472794b | ||
|
|
af78d62b76 | ||
|
|
27a0708909 | ||
|
|
5ab58f86ba | ||
|
|
2c0bfdccf9 | ||
|
|
581618370b | ||
|
|
bdf217c45a | ||
|
|
42d6594159 | ||
|
|
fac1750b63 | ||
|
|
6907d192f5 | ||
|
|
0380f36489 | ||
|
|
1524dc2680 | ||
|
|
c95cc0a52b | ||
|
|
a876c5a888 | ||
|
|
bdd925886a | ||
|
|
4d3d292add | ||
|
|
48f03e79e2 | ||
|
|
e3af622a36 | ||
|
|
dcb300ab50 | ||
|
|
95ffd46a63 | ||
|
|
6f5d4a8620 | ||
|
|
62855cc969 | ||
|
|
bdfe9dfab2 | ||
|
|
465ddf1a01 | ||
|
|
18a4ac1653 | ||
|
|
76d55144c5 | ||
|
|
665590a2f9 | ||
|
|
81aa9a31c6 | ||
|
|
082490708f | ||
|
|
20ca4f8260 | ||
|
|
eda057eb07 | ||
|
|
aed31c0eba | ||
|
|
ea585458c7 | ||
|
|
e226e1c045 | ||
|
|
ef330dd613 | ||
|
|
1225560ccd | ||
|
|
eef184e6ef | ||
|
|
50337eb731 | ||
|
|
77f5e7d581 | ||
|
|
02118bac76 | ||
|
|
8b41be238a | ||
|
|
8791e70c67 | ||
|
|
34a4b8f2d2 | ||
|
|
54c936b010 | ||
|
|
ed8d95e055 | ||
|
|
25e8d080af | ||
|
|
4599a7ab4e | ||
|
|
5a106e0e6d | ||
|
|
e4a211ba02 | ||
|
|
7ab373c942 | ||
|
|
18f1ebf715 | ||
|
|
2c02f44b26 | ||
|
|
8ae8168252 | ||
|
|
c504f0b166 | ||
|
|
23da559d26 | ||
|
|
8b3ca12658 | ||
|
|
0f70b5662c | ||
|
|
475541cac0 | ||
|
|
007d6ad9c3 | ||
|
|
79db8d91ab | ||
|
|
a0c6feb386 | ||
|
|
1c72601123 | ||
|
|
c6bcdd1ae1 | ||
|
|
e0acdba626 | ||
|
|
a73e72f267 | ||
|
|
ee6682e7c8 | ||
|
|
6c36ea5753 | ||
|
|
3f798f1843 | ||
|
|
4a806db8ae | ||
|
|
9c12125512 | ||
|
|
51130793fe | ||
|
|
d39c93ee7e | ||
|
|
e1cfb29fa0 | ||
|
|
4dde16d836 | ||
|
|
1c57ef5931 | ||
|
|
0f642fe3ad | ||
|
|
46844b516a | ||
|
|
b9cef228d9 | ||
|
|
fd71d04000 | ||
|
|
bedb811621 | ||
|
|
173736fd69 | ||
|
|
21eea27c4a | ||
|
|
b39f04fbc3 | ||
|
|
04f898be21 | ||
|
|
defedda891 | ||
|
|
bb2c8e5fd2 | ||
|
|
7410bb9e9a | ||
|
|
fd3907596b | ||
|
|
221a233ea4 | ||
|
|
389adda8ec | ||
|
|
835b65740b | ||
|
|
b24abffd0d | ||
|
|
2ddf578f07 | ||
|
|
61b83c3e34 | ||
|
|
3454ddbb2a | ||
|
|
194e4ca70d | ||
|
|
074981a709 | ||
|
|
f157200a9f | ||
|
|
f70fcddaad | ||
|
|
aa2129b60e | ||
|
|
3794541828 | ||
|
|
b1a6dc65ba | ||
|
|
040ae293fb | ||
|
|
b74e93f3a4 | ||
|
|
dd3cc29af1 | ||
|
|
72b9a93088 | ||
|
|
4446f0a1f3 | ||
|
|
35802c12f6 | ||
|
|
f5fd30883a | ||
|
|
f8e720d6dc | ||
|
|
9f8f04caec | ||
|
|
69aec85882 | ||
|
|
e84012ec38 | ||
|
|
128df6a2bf | ||
|
|
7b76951461 | ||
|
|
38d87450e6 | ||
|
|
b9c6b84ff5 | ||
|
|
5ba1d57b1f | ||
|
|
a4d567e44d | ||
|
|
19a62ba7e9 | ||
|
|
65d6c7b558 | ||
|
|
8c828e7881 | ||
|
|
b711780688 | ||
|
|
855cdc8eda | ||
|
|
c930cce914 | ||
|
|
424696cf96 | ||
|
|
ea4ef45289 | ||
|
|
d2e13e6ac6 | ||
|
|
70ff6658eb | ||
|
|
3e1732b9c0 | ||
|
|
4eb18dc797 | ||
|
|
6e7adfadb0 | ||
|
|
a866224f7b | ||
|
|
77c50d6880 | ||
|
|
4290e2e7b0 | ||
|
|
4e8632de73 | ||
|
|
2792fe82a3 | ||
|
|
ab6df9f8a9 | ||
|
|
425489c74b | ||
|
|
9f04517801 | ||
|
|
9954639096 | ||
|
|
cd78364c17 | ||
|
|
563b986591 | ||
|
|
fe5552773d | ||
|
|
b7acaf9519 | ||
|
|
deffc90531 | ||
|
|
f54a6e8513 | ||
|
|
ab4dc641af | ||
|
|
dfe725dd78 | ||
|
|
dbe1e24561 | ||
|
|
075557929a | ||
|
|
d994db9c74 | ||
|
|
c462c61ac9 | ||
|
|
1ef9719059 | ||
|
|
3df0e7899c | ||
|
|
927972d889 | ||
|
|
00f409d21d | ||
|
|
6aea95b486 | ||
|
|
e4dd78b9a4 | ||
|
|
d940d5a597 | ||
|
|
5f1ea21be1 | ||
|
|
278851224d | ||
|
|
0161bc5a4e | ||
|
|
9cf5168d3f | ||
|
|
f066a4bdf4 | ||
|
|
0009756ed9 | ||
|
|
481e5437f1 | ||
|
|
238f54e011 | ||
|
|
180525f538 | ||
|
|
9a1958d19a | ||
|
|
e1469a1e44 | ||
|
|
85bde2b00d | ||
|
|
b20a15b4ed | ||
|
|
a67161f521 | ||
|
|
8d621e87a0 | ||
|
|
a560034614 | ||
|
|
b329c1f94c | ||
|
|
06459c0f52 | ||
|
|
2655c2511d | ||
|
|
fa9fdb01b0 | ||
|
|
4896948d38 | ||
|
|
133a37817e | ||
|
|
0cdee5ed7a | ||
|
|
2c21989b47 | ||
|
|
17f796847d | ||
|
|
02578dbf3a | ||
|
|
9f86e84830 | ||
|
|
1ae65bb999 | ||
|
|
ca72f5fa99 | ||
|
|
bc9b537d84 | ||
|
|
d9336d0ba2 | ||
|
|
eb537eaa24 | ||
|
|
f1d814ba04 | ||
|
|
760d360e96 | ||
|
|
5431fa1405 | ||
|
|
eb809e388b | ||
|
|
0205ad7a4f | ||
|
|
9599b14aa8 | ||
|
|
d72585e3d1 | ||
|
|
735a15d7c6 | ||
|
|
0984e6ed90 | ||
|
|
6004a4250a | ||
|
|
b39fcc75d0 | ||
|
|
a401553c13 | ||
|
|
a312abff31 | ||
|
|
92c1722469 | ||
|
|
e48136d31e | ||
|
|
b9d6f6fd9c | ||
|
|
66c8dfe44d | ||
|
|
5910c936b9 | ||
|
|
a2645061fe | ||
|
|
7b887d2fd5 | ||
|
|
8d2367ed82 | ||
|
|
075583f4be | ||
|
|
ce86e0a54a | ||
|
|
9308f8ea69 | ||
|
|
cc8b425adc | ||
|
|
25cf7955c9 | ||
|
|
d393865efc | ||
|
|
9d877320d8 | ||
|
|
1f72bef393 | ||
|
|
99c1069229 | ||
|
|
28718429e3 | ||
|
|
e145021760 | ||
|
|
b7dd51c594 | ||
|
|
9ebc3e5eec | ||
|
|
4db60d7b27 | ||
|
|
5d85199ae3 | ||
|
|
088c9e5450 | ||
|
|
bd51d1b2a2 | ||
|
|
0de7b6f63a | ||
|
|
85706eac84 | ||
|
|
96b646b94a | ||
|
|
753f6fc671 | ||
|
|
3f186747ed | ||
|
|
e6748c0793 | ||
|
|
2383796c49 | ||
|
|
fb38ab4ef2 | ||
|
|
1242a62bca | ||
|
|
55ceb12277 | ||
|
|
6dd4e7676d | ||
|
|
4abb58b239 | ||
|
|
385dab6036 | ||
|
|
2e9291decf | ||
|
|
623036f03b | ||
|
|
98075e9d9d | ||
|
|
b80ce0e222 | ||
|
|
b0162efb1f | ||
|
|
7b3e9ab031 | ||
|
|
b35e866d95 | ||
|
|
2d273d1550 | ||
|
|
6aef3eb879 | ||
|
|
61b0393657 | ||
|
|
30bf6c0714 | ||
|
|
fc64b115d3 | ||
|
|
bdc3f24ca6 | ||
|
|
b28300b184 | ||
|
|
5c1f9d03e4 | ||
|
|
ed368a225d | ||
|
|
2f17f392d5 | ||
|
|
b00a6e526e | ||
|
|
75031df45b | ||
|
|
39148a99d0 | ||
|
|
d72f5ee2ad | ||
|
|
c6ea459a68 | ||
|
|
e8525e5513 | ||
|
|
9046b85ca4 | ||
|
|
19257b76d2 | ||
|
|
fd8990b294 | ||
|
|
eb34f5e058 | ||
|
|
f8ced7e065 | ||
|
|
bf0a2d3c6c | ||
|
|
f5878326f7 | ||
|
|
1f0b439168 | ||
|
|
3464e11152 | ||
|
|
b5f99be635 | ||
|
|
3153b35c2a | ||
|
|
c450f9abc2 | ||
|
|
912434b259 | ||
|
|
5f61fc658a | ||
|
|
a268b74636 | ||
|
|
82a1bf7afd | ||
|
|
4392676667 | ||
|
|
c47ec2c3c0 | ||
|
|
e756fc8daa | ||
|
|
4629262a1a | ||
|
|
741aec198b | ||
|
|
c3e5b4fa36 | ||
|
|
d022907bcd | ||
|
|
8a7603a45c | ||
|
|
2ade82d883 | ||
|
|
3d8c38b558 | ||
|
|
0f2d0a33b4 | ||
|
|
d4ce1499cc | ||
|
|
9ab7895772 | ||
|
|
ee9f79e287 | ||
|
|
ad61dafb9a | ||
|
|
b81361ff6d | ||
|
|
52417c0a60 | ||
|
|
000beeb737 | ||
|
|
7cce94000b | ||
|
|
7a4bdb4191 | ||
|
|
279a8ff989 | ||
|
|
ed06a05ff0 | ||
|
|
a56ac24cc2 | ||
|
|
c8efd31d08 | ||
|
|
d8875151e8 | ||
|
|
6082431161 | ||
|
|
846c23a5a2 | ||
|
|
9a5faa92c4 | ||
|
|
f149be26ce | ||
|
|
c6d6ca5a5c | ||
|
|
98dc8069e3 | ||
|
|
32f5d68e51 | ||
|
|
855347a5d3 | ||
|
|
78c22cb74a | ||
|
|
8626a3efea | ||
|
|
d7e0d64ac2 | ||
|
|
e26b1e54c8 | ||
|
|
594024472d | ||
|
|
5f60d09d0b | ||
|
|
a05abda25c | ||
|
|
daa8e44180 | ||
|
|
837a26daee | ||
|
|
1223d3a2b3 | ||
|
|
a4d7eaf709 | ||
|
|
4b12d5527b | ||
|
|
e7553d6f15 | ||
|
|
b795d7de4e | ||
|
|
e732afd64c | ||
|
|
40b25e3826 | ||
|
|
b4d80b467a | ||
|
|
62ef377dd1 | ||
|
|
b85b1e95b4 | ||
|
|
f2ab7001d2 | ||
|
|
7fa6a2e9ef | ||
|
|
c499bbaa9f | ||
|
|
f2b16c414f | ||
|
|
2a73e28b19 | ||
|
|
9252d1290c | ||
|
|
049bd85b29 | ||
|
|
f95a0fa1d3 | ||
|
|
416466a0e1 | ||
|
|
bfd97c5484 | ||
|
|
9c1badd6aa | ||
|
|
929cb29c5b | ||
|
|
1e788962ef | ||
|
|
26f8c11526 | ||
|
|
a9b3922061 | ||
|
|
00ebe4fab2 | ||
|
|
1bee6aece7 | ||
|
|
be4eff413d | ||
|
|
e56571840b | ||
|
|
f931222256 | ||
|
|
25ff7d430d | ||
|
|
d9cf3ceb9b | ||
|
|
c37a84ab4d | ||
|
|
cf97fc9b95 | ||
|
|
03a8651cfd | ||
|
|
b206525f8e | ||
|
|
443850b778 | ||
|
|
6962fbf6f7 | ||
|
|
a2a7173e30 | ||
|
|
698ad2b890 | ||
|
|
dd43a82042 | ||
|
|
a25515dc2b | ||
|
|
e1a8e119bf | ||
|
|
4712b72019 | ||
|
|
2d162b0d4e | ||
|
|
1ea4b4d0b5 | ||
|
|
c75c7019a8 | ||
|
|
67ab5fbc8a | ||
|
|
31a5bee228 | ||
|
|
bcd7195bf5 | ||
|
|
322a9d397a | ||
|
|
71e6646ea0 | ||
|
|
b505507c35 | ||
|
|
76535c1da5 | ||
|
|
9edd94e1c0 | ||
|
|
32449b3c55 | ||
|
|
76c3d38e11 | ||
|
|
0e8ed7d770 | ||
|
|
5af4743f2f | ||
|
|
ab86232294 | ||
|
|
0202daf551 | ||
|
|
3b062de156 | ||
|
|
65786c4d41 | ||
|
|
d3bb742308 | ||
|
|
f98aa3f12b | ||
|
|
70881bb367 | ||
|
|
50adb5fdf5 | ||
|
|
c05701fc19 | ||
|
|
5e68b5f3e3 | ||
|
|
5f46d97409 | ||
|
|
7f07e47488 | ||
|
|
3999f4d3a5 | ||
|
|
b3d6220b01 | ||
|
|
7d88775b5a | ||
|
|
2ca5d3e0df | ||
|
|
3f34030a12 | ||
|
|
11dfa3d9aa | ||
|
|
6923a11038 | ||
|
|
33d78fcf29 | ||
|
|
2b3cfdf18b | ||
|
|
aa7a3f7013 | ||
|
|
1e1b3c8f5f | ||
|
|
a7cac36974 | ||
|
|
3af800e522 | ||
|
|
cf135dd658 | ||
|
|
4e9d113896 | ||
|
|
af8e81d208 | ||
|
|
83fa1ea4a6 | ||
|
|
a72f8b7b65 | ||
|
|
1b940a3117 | ||
|
|
5012831e7b | ||
|
|
85fb08e9a5 | ||
|
|
7ac84b6a91 | ||
|
|
3a72526016 | ||
|
|
c3e78f41b0 | ||
|
|
fdea9dddee | ||
|
|
2d3dd7d91d | ||
|
|
2487245e94 | ||
|
|
a4f12691ce | ||
|
|
e7e83eb8bb | ||
|
|
e8ffb68c08 | ||
|
|
283f69cb65 | ||
|
|
0fb4ab2dcf | ||
|
|
063ac989be | ||
|
|
82fb11a7b5 | ||
|
|
d238d61906 | ||
|
|
22fdc90159 | ||
|
|
b6cf4c4375 | ||
|
|
8aa32fff34 | ||
|
|
76d6ffea4a | ||
|
|
ea8ca8ea1e | ||
|
|
ed9ca04c58 | ||
|
|
093fbaa178 | ||
|
|
5f4c8cf176 | ||
|
|
c7ee37804c | ||
|
|
e0c31aa5af | ||
|
|
ab1494777d | ||
|
|
c01fafb605 | ||
|
|
5be2ffc0b2 | ||
|
|
3ede9396da | ||
|
|
e400a7a15e | ||
|
|
2939006a9a | ||
|
|
df975a0b6b | ||
|
|
bda86de632 | ||
|
|
5738a4ba48 | ||
|
|
38cfa46131 | ||
|
|
9525df1381 | ||
|
|
d393577ba8 | ||
|
|
f7c2a07b70 | ||
|
|
adf69b4890 | ||
|
|
593d22b640 | ||
|
|
7706eebafb | ||
|
|
91bd5127fb | ||
|
|
b87a0d7f25 | ||
|
|
0f431ed384 | ||
|
|
d520921b13 | ||
|
|
8d4b9076f7 | ||
|
|
93ebbcaf04 | ||
|
|
ecde1580fd | ||
|
|
8f73bb222c | ||
|
|
1108e04eff | ||
|
|
0f1a8f3358 | ||
|
|
77de4df0ff | ||
|
|
0564de37ee | ||
|
|
8c584ae0e0 | ||
|
|
30c80279c8 | ||
|
|
3f0ab9a88a | ||
|
|
6da3fcf446 | ||
|
|
26746ca303 | ||
|
|
64158eac43 | ||
|
|
9e3a7f5eda | ||
|
|
0a4f3db8ae | ||
|
|
c095085d84 | ||
|
|
41cc4e68e3 | ||
|
|
21fb7959a5 | ||
|
|
3aa22127e0 | ||
|
|
a679a37ffa | ||
|
|
649857ec28 | ||
|
|
4a6136b918 | ||
|
|
6e1c468a80 | ||
|
|
cadcd0c5e8 | ||
|
|
a531c306f1 | ||
|
|
b0a9449335 | ||
|
|
b6deb87cae | ||
|
|
7319c88674 | ||
|
|
d36317c563 | ||
|
|
1917202bd0 | ||
|
|
a8338912e0 | ||
|
|
bfea3201e8 | ||
|
|
a9bb440e6c | ||
|
|
f0942d58e8 | ||
|
|
aa093659a0 | ||
|
|
cccdbc05a5 | ||
|
|
dfa32b7be4 | ||
|
|
c7e0bd037a | ||
|
|
e4239c924b | ||
|
|
49a08d14c3 | ||
|
|
25990f59d8 |
@@ -6,8 +6,13 @@
|
||||
^shiny\.cmd$
|
||||
^run\.R$
|
||||
^\.gitignore$
|
||||
^smoketests$
|
||||
^res$
|
||||
^man-roxygen$
|
||||
^\.travis\.yml$
|
||||
^staticdocs$
|
||||
^tools$
|
||||
^srcjs$
|
||||
^CONTRIBUTING.md$
|
||||
^cran-comments.md$
|
||||
^.*\.o$
|
||||
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1 +1,4 @@
|
||||
/NEWS merge=union
|
||||
/inst/www/shared/shiny.js -merge -diff
|
||||
*.min.js -merge -diff
|
||||
*.js.map -merge -diff
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@
|
||||
/src-x86_64/
|
||||
shinyapps/
|
||||
README.html
|
||||
.*.Rnb.cached
|
||||
|
||||
34
.travis.yml
34
.travis.yml
@@ -1,28 +1,8 @@
|
||||
# it is not really python, but there is no R support on Travis CI yet
|
||||
language: python
|
||||
language: r
|
||||
sudo: false
|
||||
cache: packages
|
||||
|
||||
# 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 -qq update
|
||||
- sudo apt-get -qq install r-base r-cran-shiny r-cran-cairo r-cran-markdown r-cran-knitr
|
||||
- "[ ! -d ~/R ] && mkdir ~/R"
|
||||
- echo "options(repos = c(CRAN = 'http://cran.rstudio.com'))" > ~/.Rprofile
|
||||
- Rscript -e "install.packages(c('xtable'), quiet = TRUE)"
|
||||
- Rscript -e "update.packages(instlib = '~/R', ask = FALSE, quiet = TRUE)"
|
||||
- Rscript -e "install.packages('$R_MY_PKG', dep = TRUE, quiet = TRUE)"
|
||||
|
||||
# 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
|
||||
notifications:
|
||||
email:
|
||||
on_success: change
|
||||
on_failure: change
|
||||
|
||||
10
CONTRIBUTING.md
Normal file
10
CONTRIBUTING.md
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
We welcome contributions to the **shiny** package. To submit a contribution:
|
||||
|
||||
1. [Fork](https://github.com/rstudio/shiny/fork) the repository and make your changes.
|
||||
|
||||
2. Ensure that you have signed the [individual](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioIndividualContributorAgreement.pdf) or [corporate](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioCorporateContributorAgreement.pdf) contributor agreement as appropriate. You can send the signed copy to jj@rstudio.com.
|
||||
|
||||
3. Submit a [pull request](https://help.github.com/articles/using-pull-requests).
|
||||
|
||||
We'll try to be as responsive as possible in reviewing and accepting pull requests. We appreciate your contributions!
|
||||
112
DESCRIPTION
112
DESCRIPTION
@@ -1,64 +1,144 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 0.10.1
|
||||
Date: 2014-06-13
|
||||
Author: RStudio, Inc.
|
||||
Maintainer: Winston Chang <winston@rstudio.com>
|
||||
Description: Shiny makes it incredibly easy to build interactive web
|
||||
Version: 0.13.2.9004
|
||||
Date: 2016-02-17
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
|
||||
person("JJ", "Allaire", role = "aut", email = "jj@rstudio.com"),
|
||||
person("Yihui", "Xie", role = "aut", email = "yihui@rstudio.com"),
|
||||
person("Jonathan", "McPherson", role = "aut", email = "jonathan@rstudio.com"),
|
||||
person(family = "RStudio", role = "cph"),
|
||||
person(family = "jQuery Foundation", role = "cph",
|
||||
comment = "jQuery library and jQuery UI library"),
|
||||
person(family = "jQuery contributors", role = c("ctb", "cph"),
|
||||
comment = "jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt"),
|
||||
person(family = "jQuery UI contributors", role = c("ctb", "cph"),
|
||||
comment = "jQuery UI library; authors listed in inst/www/shared/jqueryui/1.10.4/AUTHORS.txt"),
|
||||
person("Mark", "Otto", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person("Jacob", "Thornton", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person(family = "Bootstrap contributors", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person(family = "Twitter, Inc", role = "cph",
|
||||
comment = "Bootstrap library"),
|
||||
person("Alexander", "Farkas", role = c("ctb", "cph"),
|
||||
comment = "html5shiv library"),
|
||||
person("Scott", "Jehl", role = c("ctb", "cph"),
|
||||
comment = "Respond.js library"),
|
||||
person("Stefan", "Petre", role = c("ctb", "cph"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
person("Andrew", "Rowls", role = c("ctb", "cph"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
person("Dave", "Gandy", role = c("ctb", "cph"),
|
||||
comment = "Font-Awesome font"),
|
||||
person("Brian", "Reavis", role = c("ctb", "cph"),
|
||||
comment = "selectize.js library"),
|
||||
person("Kristopher Michael", "Kowal", role = c("ctb", "cph"),
|
||||
comment = "es5-shim library"),
|
||||
person(family = "es5-shim contributors", role = c("ctb", "cph"),
|
||||
comment = "es5-shim library"),
|
||||
person("Denis", "Ineshin", role = c("ctb", "cph"),
|
||||
comment = "ion.rangeSlider library"),
|
||||
person("Sami", "Samhuri", role = c("ctb", "cph"),
|
||||
comment = "Javascript strftime library"),
|
||||
person(family = "SpryMedia Limited", role = c("ctb", "cph"),
|
||||
comment = "DataTables library"),
|
||||
person("John", "Fraser", role = c("ctb", "cph"),
|
||||
comment = "showdown.js library"),
|
||||
person("John", "Gruber", role = c("ctb", "cph"),
|
||||
comment = "showdown.js library"),
|
||||
person("Ivan", "Sagalaev", role = c("ctb", "cph"),
|
||||
comment = "highlight.js library"),
|
||||
person(family = "R Core Team", role = c("ctb", "cph"),
|
||||
comment = "tar implementation from R")
|
||||
)
|
||||
Description: Makes it incredibly easy to build interactive web
|
||||
applications with R. Automatic "reactive" binding between inputs and
|
||||
outputs and extensive pre-built widgets make it possible to build
|
||||
beautiful, responsive, and powerful applications with minimal effort.
|
||||
License: GPL-3
|
||||
License: GPL-3 | file LICENSE
|
||||
Depends:
|
||||
R (>= 2.14.1),
|
||||
R (>= 3.0.0),
|
||||
methods
|
||||
Imports:
|
||||
tools,
|
||||
utils,
|
||||
httpuv (>= 1.2.0),
|
||||
caTools,
|
||||
RJSONIO,
|
||||
httpuv (>= 1.3.3),
|
||||
mime (>= 0.3),
|
||||
jsonlite (>= 0.9.16),
|
||||
xtable,
|
||||
digest,
|
||||
htmltools (>= 0.2.4)
|
||||
htmltools (>= 0.3.5),
|
||||
R6 (>= 2.0),
|
||||
sourcetools
|
||||
Suggests:
|
||||
datasets,
|
||||
Cairo (>= 1.5-5),
|
||||
testthat,
|
||||
knitr (>= 1.6),
|
||||
markdown
|
||||
URL: http://www.rstudio.com/shiny/
|
||||
markdown,
|
||||
rmarkdown,
|
||||
ggplot2
|
||||
URL: http://shiny.rstudio.com
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Roxygen: list(wrap = FALSE)
|
||||
VignetteBuilder: knitr
|
||||
Collate:
|
||||
'app.R'
|
||||
'bootstrap-layout.R'
|
||||
'conditions.R'
|
||||
'map.R'
|
||||
'globals.R'
|
||||
'utils.R'
|
||||
'bootstrap.R'
|
||||
'cache.R'
|
||||
'diagnose.R'
|
||||
'fileupload.R'
|
||||
'stack.R'
|
||||
'graph.R'
|
||||
'hooks.R'
|
||||
'html-deps.R'
|
||||
'htmltools.R'
|
||||
'image-interact-opts.R'
|
||||
'image-interact.R'
|
||||
'imageutils.R'
|
||||
'input-action.R'
|
||||
'input-checkbox.R'
|
||||
'input-checkboxgroup.R'
|
||||
'input-date.R'
|
||||
'input-daterange.R'
|
||||
'input-file.R'
|
||||
'input-numeric.R'
|
||||
'input-password.R'
|
||||
'input-radiobuttons.R'
|
||||
'input-select.R'
|
||||
'input-slider.R'
|
||||
'input-submit.R'
|
||||
'input-text.R'
|
||||
'input-utils.R'
|
||||
'insert-ui.R'
|
||||
'jqueryui.R'
|
||||
'middleware-shiny.R'
|
||||
'middleware.R'
|
||||
'modal.R'
|
||||
'modules.R'
|
||||
'notifications.R'
|
||||
'priorityqueue.R'
|
||||
'progress.R'
|
||||
'react.R'
|
||||
'reactive-domains.R'
|
||||
'reactives.R'
|
||||
'render-plot.R'
|
||||
'render-table.R'
|
||||
'run-url.R'
|
||||
'server-input-handlers.R'
|
||||
'server.R'
|
||||
'shiny.R'
|
||||
'shinyui.R'
|
||||
'shinywrappers.R'
|
||||
'showcase.R'
|
||||
'slider.R'
|
||||
'tar.R'
|
||||
'timer.R'
|
||||
'update-input.R'
|
||||
RoxygenNote: 5.0.1
|
||||
|
||||
62
NAMESPACE
62
NAMESPACE
@@ -1,8 +1,10 @@
|
||||
# Generated by roxygen2 (4.0.1): do not edit by hand
|
||||
# Generated by roxygen2: do not edit by hand
|
||||
|
||||
S3method("$",reactivevalues)
|
||||
S3method("$",session_proxy)
|
||||
S3method("$",shinyoutput)
|
||||
S3method("$<-",reactivevalues)
|
||||
S3method("$<-",session_proxy)
|
||||
S3method("$<-",shinyoutput)
|
||||
S3method("[",reactivevalues)
|
||||
S3method("[",shinyoutput)
|
||||
@@ -23,7 +25,12 @@ S3method(names,reactivevalues)
|
||||
S3method(print,reactive)
|
||||
S3method(print,shiny.appobj)
|
||||
S3method(str,reactivevalues)
|
||||
export("conditionStackTrace<-")
|
||||
export(..stacktraceoff..)
|
||||
export(..stacktraceon..)
|
||||
export(HTML)
|
||||
export(NS)
|
||||
export(Progress)
|
||||
export(a)
|
||||
export(absolutePanel)
|
||||
export(actionButton)
|
||||
@@ -32,29 +39,47 @@ export(addResourcePath)
|
||||
export(animationOptions)
|
||||
export(as.shiny.appobj)
|
||||
export(basicPage)
|
||||
export(bootstrapLib)
|
||||
export(bootstrapPage)
|
||||
export(br)
|
||||
export(browserViewer)
|
||||
export(brushOpts)
|
||||
export(brushedPoints)
|
||||
export(callModule)
|
||||
export(cancelOutput)
|
||||
export(captureStackTraces)
|
||||
export(checkboxGroupInput)
|
||||
export(checkboxInput)
|
||||
export(clickOpts)
|
||||
export(code)
|
||||
export(column)
|
||||
export(conditionStackTrace)
|
||||
export(conditionalPanel)
|
||||
export(createWebDependency)
|
||||
export(dataTableOutput)
|
||||
export(dateInput)
|
||||
export(dateRangeInput)
|
||||
export(dblclickOpts)
|
||||
export(dialogViewer)
|
||||
export(div)
|
||||
export(downloadButton)
|
||||
export(downloadHandler)
|
||||
export(downloadLink)
|
||||
export(em)
|
||||
export(eventReactive)
|
||||
export(exprToFunction)
|
||||
export(extractStackTrace)
|
||||
export(fileInput)
|
||||
export(fillCol)
|
||||
export(fillPage)
|
||||
export(fillRow)
|
||||
export(fixedPage)
|
||||
export(fixedPanel)
|
||||
export(fixedRow)
|
||||
export(flowLayout)
|
||||
export(fluidPage)
|
||||
export(fluidRow)
|
||||
export(formatStackTrace)
|
||||
export(getDefaultReactiveDomain)
|
||||
export(h1)
|
||||
export(h2)
|
||||
@@ -64,24 +89,30 @@ export(h5)
|
||||
export(h6)
|
||||
export(headerPanel)
|
||||
export(helpText)
|
||||
export(hoverOpts)
|
||||
export(hr)
|
||||
export(htmlOutput)
|
||||
export(htmlTemplate)
|
||||
export(icon)
|
||||
export(imageOutput)
|
||||
export(img)
|
||||
export(incProgress)
|
||||
export(includeCSS)
|
||||
export(includeHTML)
|
||||
export(includeMarkdown)
|
||||
export(includeScript)
|
||||
export(includeText)
|
||||
export(inputPanel)
|
||||
export(insertUI)
|
||||
export(installExprFunction)
|
||||
export(invalidateLater)
|
||||
export(is.reactive)
|
||||
export(is.reactivevalues)
|
||||
export(is.shiny.appobj)
|
||||
export(is.singleton)
|
||||
export(isolate)
|
||||
export(knit_print.html)
|
||||
export(knit_print.reactive)
|
||||
export(knit_print.shiny.appobj)
|
||||
export(knit_print.shiny.render.function)
|
||||
export(knit_print.shiny.tag)
|
||||
@@ -90,20 +121,29 @@ export(mainPanel)
|
||||
export(makeReactiveBinding)
|
||||
export(markRenderFunction)
|
||||
export(maskReactiveContext)
|
||||
export(modalButton)
|
||||
export(modalDialog)
|
||||
export(navbarMenu)
|
||||
export(navbarPage)
|
||||
export(navlistPanel)
|
||||
export(nearPoints)
|
||||
export(need)
|
||||
export(ns.sep)
|
||||
export(numericInput)
|
||||
export(observe)
|
||||
export(observeEvent)
|
||||
export(onReactiveDomainEnded)
|
||||
export(outputOptions)
|
||||
export(p)
|
||||
export(pageWithSidebar)
|
||||
export(paneViewer)
|
||||
export(parseQueryString)
|
||||
export(passwordInput)
|
||||
export(plotOutput)
|
||||
export(plotPNG)
|
||||
export(pre)
|
||||
export(printError)
|
||||
export(printStackTrace)
|
||||
export(radioButtons)
|
||||
export(reactive)
|
||||
export(reactiveFileReader)
|
||||
@@ -118,6 +158,9 @@ export(reactiveValues)
|
||||
export(reactiveValuesToList)
|
||||
export(registerInputHandler)
|
||||
export(removeInputHandler)
|
||||
export(removeModal)
|
||||
export(removeNotification)
|
||||
export(removeUI)
|
||||
export(renderDataTable)
|
||||
export(renderImage)
|
||||
export(renderPlot)
|
||||
@@ -126,18 +169,25 @@ export(renderTable)
|
||||
export(renderText)
|
||||
export(renderUI)
|
||||
export(repeatable)
|
||||
export(req)
|
||||
export(runApp)
|
||||
export(runExample)
|
||||
export(runGadget)
|
||||
export(runGist)
|
||||
export(runGitHub)
|
||||
export(runUrl)
|
||||
export(safeError)
|
||||
export(selectInput)
|
||||
export(selectizeInput)
|
||||
export(serverInfo)
|
||||
export(setProgress)
|
||||
export(shinyApp)
|
||||
export(shinyAppDir)
|
||||
export(shinyAppFile)
|
||||
export(shinyServer)
|
||||
export(shinyUI)
|
||||
export(showModal)
|
||||
export(showNotification)
|
||||
export(showReactLog)
|
||||
export(sidebarLayout)
|
||||
export(sidebarPanel)
|
||||
@@ -148,6 +198,7 @@ export(splitLayout)
|
||||
export(stopApp)
|
||||
export(strong)
|
||||
export(submitButton)
|
||||
export(suppressDependencies)
|
||||
export(tabPanel)
|
||||
export(tableOutput)
|
||||
export(tabsetPanel)
|
||||
@@ -162,10 +213,13 @@ export(textInput)
|
||||
export(textOutput)
|
||||
export(titlePanel)
|
||||
export(uiOutput)
|
||||
export(updateActionButton)
|
||||
export(updateCheckboxGroupInput)
|
||||
export(updateCheckboxInput)
|
||||
export(updateDateInput)
|
||||
export(updateDateRangeInput)
|
||||
export(updateNavbarPage)
|
||||
export(updateNavlistPanel)
|
||||
export(updateNumericInput)
|
||||
export(updateRadioButtons)
|
||||
export(updateSelectInput)
|
||||
@@ -178,13 +232,15 @@ export(validateCssUnit)
|
||||
export(verbatimTextOutput)
|
||||
export(verticalLayout)
|
||||
export(wellPanel)
|
||||
export(withLogErrors)
|
||||
export(withMathJax)
|
||||
export(withProgress)
|
||||
export(withReactiveDomain)
|
||||
export(withTags)
|
||||
import(caTools)
|
||||
import(R6)
|
||||
import(digest)
|
||||
import(htmltools)
|
||||
import(httpuv)
|
||||
import(methods)
|
||||
import(mime)
|
||||
import(xtable)
|
||||
importFrom(RJSONIO,fromJSON)
|
||||
|
||||
422
NEWS
422
NEWS
@@ -1,3 +1,423 @@
|
||||
shiny 0.13.2.9001
|
||||
--------------------------------------------------------------------------------
|
||||
* Added support for the `pool` package (use Shiny's timer/scheduler)
|
||||
|
||||
* `Display: Showcase` now displays the .js, .html and .css files in the `www`
|
||||
directory by default. In order to use showcase mode and not display these,
|
||||
include a new line in your Description file: `IncludeWWW: False`.
|
||||
|
||||
* Added insertUI and removeUI functions to be able to add and remove chunks
|
||||
of UI, standalone, and all independent of one another.
|
||||
|
||||
* Added an error sanitization option: `options(shiny.sanitize.errors = TRUE)`.
|
||||
By default, this option is `FALSE`. When `TRUE`, normal errors will be
|
||||
sanitized, displaying only a generic error message. This changes the look
|
||||
of an app when errors are printed (but the console log remains the same).
|
||||
|
||||
* Closed #1161: Deprecated the `position` argument to `tabsetPanel()` since
|
||||
Bootstrap 3 stopped supporting this feature.
|
||||
|
||||
* BREAKING CHANGE: The long-deprecated ability to pass a `func` argument to
|
||||
many of the `render` functions has been removed.
|
||||
|
||||
* Added the option of passing arguments to an `xxxOutput()` function through
|
||||
the corresponding `renderXXX()` function via an `outputArgs` parameter to the
|
||||
latter. This is only valid for snippets of Shiny code in an interactive
|
||||
`runtime: shiny` Rmd document (never for full apps, even if embedded in an
|
||||
Rmd).
|
||||
|
||||
* Added the ability for the client browser to reconnect to a new session on
|
||||
the server, by setting `session$allowReconnect(TRUE)`. This requires a
|
||||
version of Shiny Server that supports reconnections. (#1074)
|
||||
|
||||
* Added a new notification API. From R, there are new functions
|
||||
`showNotification` and `hideNotification`. From JavaScript, there is a new
|
||||
`Shiny.notification` object that controls notifications. (#1141)
|
||||
|
||||
* Improved `renderTable()` function to make the tables look prettier and also
|
||||
provide the user with a lot more parameters to customize their tables with.
|
||||
|
||||
* Added `updateActionButton()` function, so the user can change an Action Button's
|
||||
(or Link's) label and/or icon. Also check that the icon argument (for both
|
||||
creation and updating of a button) is valid and throw a warning otherwise.
|
||||
|
||||
* Fixed #1056: Upgraded Bootstrap to 3.3.6.
|
||||
|
||||
* Updated ion.RangeSlider to 2.1.2.
|
||||
|
||||
* Fixed #561: DataTables might pop up a warning when the data is updated
|
||||
extremely frequently.
|
||||
|
||||
* Fixed #776: In some browsers, plots sometimes flickered when updated.
|
||||
|
||||
* When resized, plots are drawn with `replayPlot()`, instead of re-executing
|
||||
all plotting code. This results in faster plot rendering.
|
||||
|
||||
* Added `cancelOutput` function, and a `cancelOutput` parameter to `req`. The
|
||||
function causes the currently executing output to cancel its execution, and
|
||||
leave its previous state alone (as opposed to clearing the output). The `req`
|
||||
parameter similarly modifies the behavior of `req`.
|
||||
|
||||
* Added code diagnostics: if there is an error parsing ui.R, server.R, app.R,
|
||||
or global.R, Shiny will search the code for missing commas, extra commas,
|
||||
and unmatched braces, parens, and brackets, and will print out messages
|
||||
pointing out those problems.
|
||||
|
||||
* Added support for horizontal dividers in `navbarMenu`. (#888)
|
||||
|
||||
* Fixed #543, #855: When navbarPage had a navbarMenu as the first item, it
|
||||
not automatically select an item.
|
||||
|
||||
* navbarPage previously did not have an option to set the selected tab. (#970)
|
||||
|
||||
* navbarMenu now has dividers and dropdown headers (#888)
|
||||
|
||||
* Added `placeholder` option to `passwordInput`.
|
||||
|
||||
* Almost all code examples now have a runnable example with `shinyApp()`, so
|
||||
that users can run the examples and see them in action. (#1137, #1158)
|
||||
|
||||
* Added `session$resetBrush(brushId)` (R) and `Shiny.resetBrush(brushId)` (JS)
|
||||
to programatically clear brushes from `imageOutput`/`plotOutput`.
|
||||
|
||||
* Fixed #1253: Memory could leak when an observer was destroyed without first
|
||||
being invalidated.
|
||||
|
||||
shiny 0.13.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Updated documentation for `htmlTemplate`.
|
||||
|
||||
shiny 0.13.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* `flexCol` did not work on RStudio for Windows or Linux.
|
||||
|
||||
* Fixed RStudio debugger integration.
|
||||
|
||||
* BREAKING CHANGE: The long-deprecated ability to pass functions (rather than
|
||||
expressions) to reactive() and observe() has finally been removed.
|
||||
|
||||
shiny 0.13.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fixed #962: plot interactions did not work with the development version of
|
||||
ggplot2 (after ggplot2 1.0.1).
|
||||
|
||||
* Fixed #902: the `drag_drop` plugin of the selectize input did not work.
|
||||
|
||||
* Fixed #933: `updateSliderInput()` does not work when only the label is
|
||||
updated.
|
||||
|
||||
* Multiple imageOutput/plotOutput calls can now share the same brush id. Shiny
|
||||
will ensure that performing a brush operation will clear any other brush with
|
||||
the same id.
|
||||
|
||||
* Added `placeholder` option to `textInput`.
|
||||
|
||||
* Improved support for Unicode characters on Windows (#968).
|
||||
|
||||
* Fixed bug in `selectInput` and `selectizeInput` where values with double
|
||||
quotes were not properly escaped.
|
||||
|
||||
* `runApp()` can now take a path to any .R file that yields a `shinyApp` object;
|
||||
previously, the path had to be a directory that contained an app.R file (or
|
||||
server.R if using separately defined server and UI). Similarly, introduced
|
||||
`shinyAppFile()` function which creates a `shinyApp` object for an .R file
|
||||
path, just as `shinyAppDir()` does for a directory path.
|
||||
|
||||
* Introduced Shiny Modules, which are designed to 1) simplify the reuse of
|
||||
Shiny UI/server logic and 2) make authoring and maintaining complex Shiny
|
||||
apps much easier. See the article linked from `?callModule`.
|
||||
|
||||
* `invalidateLater` and `reactiveTimer` no longer require an explicit `session`
|
||||
argument; the default value uses the current session.
|
||||
|
||||
* Added `session$reload()` method, the equivalent of hitting the browser's
|
||||
Reload button.
|
||||
|
||||
* Added `shiny.autoreload` option, which will automatically cause browsers to
|
||||
reload whenever Shiny app files change on disk. This is intended to shorten
|
||||
the feedback cycle when tweaking UI code.
|
||||
|
||||
* Errors are now printed with stack traces! This should make it tremendously
|
||||
easier to track down the causes of errors in Shiny. Try it by calling
|
||||
`stop("message")` from within an output, reactive, or observer. Shiny itself
|
||||
adds a lot of noise to the call stack, so by default, it attempts to hide all
|
||||
but the relevant levels of the call stack. You can turn off this behavior by
|
||||
setting `options(shiny.fullstacktrace=TRUE)` before or during app startup.
|
||||
|
||||
* Fixed #1018: the selected value of a selectize input is guaranteed to be
|
||||
selected in server-side mode.
|
||||
|
||||
* Added `req` function, which provides a simple way to prevent a reactive,
|
||||
observer, or output from executing until all required inputs and values are
|
||||
available. (Similar functionality has been available for a while using
|
||||
validate/need, but req provides a much simpler and more direct interface.)
|
||||
|
||||
* Improve stability with Shiny Server when many subapps are used, by deferring
|
||||
the loading of subapp iframes until a connection has first been established
|
||||
with the server.
|
||||
|
||||
* Upgrade to Font Awesome 4.5.0.
|
||||
|
||||
* Upgraded to Bootstrap 3.3.5.
|
||||
|
||||
* Switched to an almost-complete build of jQuery UI with the exception of the
|
||||
datepicker extension, which conflicts with Shiny's date picker.
|
||||
|
||||
* Added `fillPage` function, an alternative to `fluidPage`, `fixedPage`, etc.
|
||||
that is designed for apps that fill the entire available page width/height.
|
||||
|
||||
* Added `fillRow` and `fillCol` functions, for laying out proportional grids in
|
||||
`fillPage`. For modern browsers only.
|
||||
|
||||
* Added `runGadget`, `paneViewer`, `dialogViewer`, and `browserViewer`
|
||||
functions to support Shiny Gadgets. More detailed docs about gadgets coming
|
||||
soon.
|
||||
|
||||
* Added support for the new htmltools 0.3 feature `htmlTemplate`. It's now
|
||||
possible to use regular HTML markup to design your UI, but still use R
|
||||
expressions to define inputs, outputs, and HTML widgets.
|
||||
|
||||
shiny 0.12.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* GitHub changed URLs for gists from .tar.gz to .zip, so `runGist` was updated
|
||||
to work with the new URLs.
|
||||
|
||||
* Callbacks from the session object are now guaranteed to execute in the order
|
||||
in which registration occurred.
|
||||
|
||||
* Minor bugs in sliderInput's animation behavior have been fixed. (#852)
|
||||
|
||||
* Updated to ion.rangeSlider to 2.0.12.
|
||||
|
||||
* Added `shiny.minified` option, which controls whether the minified version
|
||||
of shiny.js is used. Setting it to FALSe can be useful for debugging. (#826,
|
||||
#850)
|
||||
|
||||
* Fixed an issue for outputting plots from ggplot objects which also have an
|
||||
additional class whose print method takes precedence over `print.ggplot`.
|
||||
(#840, 841)
|
||||
|
||||
* Added `width` option to Shiny's input functions. (#589, #834)
|
||||
|
||||
* Added two alias functions of `updateTabsetPanel()` to update the selected tab:
|
||||
`updateNavbarPage()` and `updateNavlistPanel()`. (#881)
|
||||
|
||||
* All non-base functions are now explicitly namespaced, to pass checks in
|
||||
R-devel.
|
||||
|
||||
* Shiny now correctly handles HTTP HEAD requests. (#876)
|
||||
|
||||
shiny 0.12.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fixed an issue where unbindAll() causes subsequent bindAll() to be ignored for
|
||||
previously bound outputs. (#856)
|
||||
|
||||
* Undeprecate `dataTableOutput` and `renderDataTable`, which had been deprecated
|
||||
in favor of the new DT package. The DT package is a bit too new and has a
|
||||
slightly different API, we were too hasty in deprecating the existing Shiny
|
||||
functions.
|
||||
|
||||
shiny 0.12.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Switched from RJSONIO to jsonlite. This improves consistency and speed when
|
||||
converting between R data structures and JSON. One notable change is that
|
||||
POSIXt objects are now serialized to JSON in UTC8601 format (like
|
||||
"2015-03-20T20:00:00Z"), instead of as seconds from the epoch).
|
||||
|
||||
* In addition to the existing support for clicking and hovering on plots
|
||||
created by base graphics, added support for double-clicking and brushing.
|
||||
(#769)
|
||||
|
||||
* Added support for clicking, hovering, double-clicking, and brushing for
|
||||
plots created by ggplot2, including support for facets. (#802)
|
||||
|
||||
* Added `nearPoints` and `brushedPoints` functions for easily selecting rows of
|
||||
data that are clicked/hovered, or brushed. (#802)
|
||||
|
||||
* Added `shiny.port` option. If this is option is set, `runApp()` will listen on
|
||||
this port by default. (#756)
|
||||
|
||||
* `runUrl`, `runGist`, and `runGitHub` now can save downloaded applications,
|
||||
with the `destdir` argument. (#688)
|
||||
|
||||
* Restored ability to set labels for `selectInput`. (#741)
|
||||
|
||||
* Travis continuous integration now uses Travis's native R support.
|
||||
|
||||
* Fixed encoding issue when the server receives data from the client browser.
|
||||
(#742)
|
||||
|
||||
* The `session` object now has class `ShinySession`, making it easier to test
|
||||
whether an object is indeed a session object. (#720, #746)
|
||||
|
||||
* Fix JavaScript error when an output appears in nested uiOutputs. (Thanks,
|
||||
Gregory Zhang. #749)
|
||||
|
||||
* Eliminate delay on receiving new value when `updateSliderInput(value=...)` is
|
||||
called.
|
||||
|
||||
* Updated to DataTables (Javascript library) 1.10.5.
|
||||
|
||||
* Fixed downloading of files that have no filename extension. (#575, #753)
|
||||
|
||||
* Fixed bug where nested UI outputs broke outputs. (#749, #750)
|
||||
|
||||
* Removed unneeded HTML ID attributes for `checkboxGroupInputs` and
|
||||
`radioButtons`. (#684)
|
||||
|
||||
* Fixed bug where checkboxes were still active even after `Shiny.unbindAll()`
|
||||
was called. (#206)
|
||||
|
||||
* The server side selectize input will load the first 1000 options by default
|
||||
before users start to type and search in the box. (#823)
|
||||
|
||||
* renderDataTable() and dataTableOutput() have been deprecated in shiny and will
|
||||
be removed in future versions of shiny. Please use the DT package instead:
|
||||
http://rstudio.github.io/DT/ (#807)
|
||||
|
||||
shiny 0.11.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Major client-side performance improvements for pages that have many
|
||||
conditionalPanels, tabPanels, and plotOutputs. (#693, #717, #723)
|
||||
|
||||
* `tabPanel`s now use the `title` for `value` by default. This fixes a bug
|
||||
in which an icon in the title caused problems with a conditionalPanel's test
|
||||
condition. (#725, #728)
|
||||
|
||||
* `selectInput` now has a `size` argument to control the height of the input
|
||||
box. (#729)
|
||||
|
||||
* `navbarPage` no longer includes a first row of extra whitespace when
|
||||
`header=NULL`. (#722)
|
||||
|
||||
* `selectInput`s now use Bootstrap styling when `selectize=FALSE`. (#724)
|
||||
|
||||
* Better vertical spacing of label for checkbox groups and radio buttons.
|
||||
|
||||
* `selectInput` correctly uses width for both selectize and non-selectize
|
||||
inputs. (#702)
|
||||
|
||||
* The wrapper tag generated by `htmlOutput` and `uiOutput` can now be any type
|
||||
of HTML tag, instead of just span and div. Also, custom classes are now
|
||||
allowed on the tag. (#704)
|
||||
|
||||
* Slider problems in IE 11 and Chrome on touchscreen-equipped Windows computers
|
||||
have been fixed. (#700)
|
||||
|
||||
* Sliders now work correctly with draggable panels. (#711)
|
||||
|
||||
* Fixed arguments in `fixedPanel`. (#709)
|
||||
|
||||
* downloadHandler content callback functions are now invoked with a temp file
|
||||
name that has the same extension as the final filename that will be used by
|
||||
the download. This is to deal with the fact that some file writing functions
|
||||
in R will auto-append the extension for their file type (pdf, zip).
|
||||
|
||||
shiny 0.11
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Changed sliders from jquery-slider to ion.rangeSlider. These sliders have
|
||||
an improved appearance, support updating more properties from the server,
|
||||
and can be controlled with keyboard input.
|
||||
|
||||
* Switched from Bootstrap 2 to Bootstrap 3. For most users, this will work
|
||||
seamlessly, but some users may need to use the shinybootstrap2 package for
|
||||
backward compatibility.
|
||||
|
||||
* The UI of a Shiny app can now have a body tag. This is useful for CSS
|
||||
templates that use classes on the body tag.
|
||||
|
||||
* `actionButton` and `actionLink` now pass their `...` arguments to the
|
||||
underlying tag function. (#607)
|
||||
|
||||
* Added `observeEvent` and `eventReactive` functions for clearer, more concise
|
||||
handling of `actionButton`, plot clicks, and other naturally-imperative
|
||||
inputs.
|
||||
|
||||
* Errors that happen in reactives no longer prevent any remaining pending
|
||||
observers from executing. It is also now possible for users to control how
|
||||
errors are handled, with the 'shiny.observer.error' global option. (#603,
|
||||
#604)
|
||||
|
||||
* Added an `escape` argument to `renderDataTable()` to escape the HTML entities
|
||||
in the data table for security reasons. This might break tables from previous
|
||||
versions of shiny that use raw HTML in the table content, and the old behavior
|
||||
can be brought back by `escape = FALSE` if you are aware of the security
|
||||
implications. (#627)
|
||||
|
||||
* Changed the URI encoding/decoding functions internally to use `encodeURI()`,
|
||||
`encodeURIComponent()`, and `decodeURIComponent()` from the httpuv package
|
||||
instead of `utils::URLencode()` and `utils::URLdecode()`. (#630)
|
||||
|
||||
* Shiny's web assets are now minified.
|
||||
|
||||
* The default reactive domain is now available in event handler functions. (#669)
|
||||
|
||||
* Password input fields can now be used, with `passwordInput()`. (#672)
|
||||
|
||||
shiny 0.10.2.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Remove use of `rstudio::viewer` in a code example, for R CMD check.
|
||||
|
||||
shiny 0.10.2.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Changed some examples to use \donttest instead of \dontrun.
|
||||
|
||||
shiny 0.10.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* The minimal version of R required for the shiny package is 3.0.0 now.
|
||||
|
||||
* Shiny apps can now consist of a single file, app.R, instead of ui.R and
|
||||
server.R.
|
||||
|
||||
* Upgraded DataTables from 1.9.4 to 1.10.2. This might be a breaking change if
|
||||
you have customized the DataTables options in your apps. (More info:
|
||||
https://github.com/rstudio/shiny/pull/558)
|
||||
|
||||
* File uploading via `fileInput()` works for Internet Explorer 8 and 9 now. Note
|
||||
IE8/9 do not support multiple files from a single file input. If you need to
|
||||
upload multiple files, you have to use one file input for each file.
|
||||
|
||||
* Switched away from reference classes to R6.
|
||||
|
||||
* Reactive log performance has been greatly improved.
|
||||
|
||||
* Added `Progress` and `withProgress`, to display the progress of computation
|
||||
on the client browser.
|
||||
|
||||
* Fixed #557: updateSelectizeInput(choices, server = TRUE) did not work when
|
||||
`choices` is a character vector.
|
||||
|
||||
* Searching in DataTables is case-insensitive and the search strings are not
|
||||
treated as regular expressions by default now. If you want case-sensitive
|
||||
searching or regular expressions, you can use the configuration options
|
||||
`search$caseInsensitive` and `search$regex`, e.g. `renderDataTable(...,
|
||||
options = list(search = list(caseInsensitve = FALSE, regex = TRUE)))`.
|
||||
|
||||
* Added support for `htmltools::htmlDependency`'s new `attachment` parameter to
|
||||
`renderUI`/`uiOutput`.
|
||||
|
||||
* Exported `createWebDependency`. It takes an `htmltools::htmlDependency` object
|
||||
and makes it available over Shiny's built-in web server.
|
||||
|
||||
* Custom output bindings can now render `htmltools::htmlDependency` objects at
|
||||
runtime using `Shiny.renderDependencies()`.
|
||||
|
||||
* Fixes to rounding behavior of sliderInput. (#301, #502)
|
||||
|
||||
* Updated selectize.js to version 0.11.2. (#596)
|
||||
* Added `position` parameter to `navbarPage`.
|
||||
|
||||
shiny 0.10.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -15,7 +435,7 @@ shiny 0.10.1
|
||||
* Added an `inline` argument to `textOutput()`, `imageOutput()`, `plotOutput()`,
|
||||
and `htmlOutput()`. When `inline = TRUE`, these outputs will be put in
|
||||
`span()` instead of the default `div()`. This occurs automatically when these
|
||||
outputs are created via the inline expressions (e.g. `r textOutput(expr)`) in
|
||||
outputs are created via the inline expressions (e.g. `r renderText(expr)`) in
|
||||
R Markdown documents. See an R Markdown example at
|
||||
http://shiny.rstudio.com/gallery/inline-output.html (#512)
|
||||
|
||||
|
||||
270
R/app.R
270
R/app.R
@@ -3,11 +3,16 @@
|
||||
#' Create a Shiny app object
|
||||
#'
|
||||
#' These functions create Shiny app objects from either an explicit UI/server
|
||||
#' pair (\code{shinyApp}), or by passing the path of a directory that
|
||||
#' contains a Shiny app (\code{shinyAppDir}). You generally shouldn't need to
|
||||
#' use these functions to create/run applications; they are intended for
|
||||
#' interoperability purposes, such as embedding Shiny apps inside a \pkg{knitr}
|
||||
#' document.
|
||||
#' pair (\code{shinyApp}), or by passing the path of a directory that contains a
|
||||
#' Shiny app (\code{shinyAppDir}). You generally shouldn't need to use these
|
||||
#' functions to create/run applications; they are intended for interoperability
|
||||
#' purposes, such as embedding Shiny apps inside a \pkg{knitr} document.
|
||||
#'
|
||||
#' Normally when this function is used at the R console, the Shiny app object is
|
||||
#' automatically passed to the \code{print()} function, which runs the app. If
|
||||
#' this is called in the middle of a function, the value will not be passed to
|
||||
#' \code{print()} and the app will not be run. To make the app run, pass the app
|
||||
#' object to \code{print()} or \code{\link{runApp}()}.
|
||||
#'
|
||||
#' @param ui The UI definition of the app (for example, a call to
|
||||
#' \code{fluidPage()} with nested controls)
|
||||
@@ -22,55 +27,50 @@
|
||||
#' request to determine whether the \code{ui} should be used to handle the
|
||||
#' request. Note that the entire request path must match the regular
|
||||
#' expression in order for the match to be considered successful.
|
||||
#' @return An object that represents the app. Printing the object will run the
|
||||
#' app.
|
||||
#' @return An object that represents the app. Printing the object or passing it
|
||||
#' to \code{\link{runApp}} will run the app.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' numericInput("n", "n", 1),
|
||||
#' plotOutput("plot")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot( plot(head(cars, input$n)) )
|
||||
#' },
|
||||
#' options=list(launch.browser = rstudio::viewer)
|
||||
#' )
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' numericInput("n", "n", 1),
|
||||
#' plotOutput("plot")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot( plot(head(cars, input$n)) )
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' shinyAppDir(system.file("examples/01_hello", package="shiny"))
|
||||
#' shinyAppDir(system.file("examples/01_hello", package="shiny"))
|
||||
#'
|
||||
#'
|
||||
#' # The object can be passed to runApp()
|
||||
#' app <- shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' numericInput("n", "n", 1),
|
||||
#' plotOutput("plot")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot( plot(head(cars, input$n)) )
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' runApp(app)
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
shinyApp <- function(ui, server, onStart=NULL, options=list(), uiPattern="/") {
|
||||
shinyApp <- function(ui=NULL, server=NULL, onStart=NULL, options=list(),
|
||||
uiPattern="/") {
|
||||
if (is.null(server)) {
|
||||
stop("`server` missing from shinyApp")
|
||||
}
|
||||
|
||||
# Ensure that the entire path is a match
|
||||
uiPattern <- sprintf("^%s$", uiPattern)
|
||||
|
||||
httpHandler <- function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
if (!isTRUE(grepl(uiPattern, req$PATH_INFO)))
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
on.exit(close(textConn))
|
||||
|
||||
uiValue <- if (is.function(ui)) {
|
||||
if (length(formals(ui)) > 0)
|
||||
ui(req)
|
||||
else
|
||||
ui()
|
||||
} else {
|
||||
ui
|
||||
}
|
||||
if (is.null(uiValue))
|
||||
return(NULL)
|
||||
|
||||
renderPage(uiValue, textConn)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(httpResponse(200, content=enc2utf8(html)))
|
||||
}
|
||||
httpHandler <- uiHttpHandler(ui, uiPattern)
|
||||
|
||||
serverFuncSource <- function() {
|
||||
server
|
||||
@@ -91,10 +91,7 @@ shinyApp <- function(ui, server, onStart=NULL, options=list(), uiPattern="/") {
|
||||
#' file and either ui.R or www/index.html)
|
||||
#' @export
|
||||
shinyAppDir <- function(appDir, options=list()) {
|
||||
# Most of the complexity here comes from needing to hot-reload if the .R files
|
||||
# change on disk, or are created, or are removed.
|
||||
|
||||
if (!file.exists(appDir)) {
|
||||
if (!utils::file_test('-d', appDir)) {
|
||||
stop("No Shiny application exists at the path \"", appDir, "\"")
|
||||
}
|
||||
|
||||
@@ -102,6 +99,29 @@ shinyAppDir <- function(appDir, options=list()) {
|
||||
# affected by future changes to the path)
|
||||
appDir <- normalizePath(appDir, mustWork = TRUE)
|
||||
|
||||
if (file.exists.ci(appDir, "server.R")) {
|
||||
shinyAppDir_serverR(appDir, options = options)
|
||||
} else if (file.exists.ci(appDir, "app.R")) {
|
||||
shinyAppDir_appR("app.R", appDir, options = options)
|
||||
} else {
|
||||
stop("App dir must contain either app.R or server.R.")
|
||||
}
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param appFile Path to a .R file containing a Shiny application
|
||||
#' @export
|
||||
shinyAppFile <- function(appFile, options=list()) {
|
||||
appFile <- normalizePath(appFile, mustWork = TRUE)
|
||||
shinyAppDir_appR(basename(appFile), dirname(appFile), options = options)
|
||||
}
|
||||
|
||||
# This reads in an app dir in the case that there's a server.R (and ui.R/www)
|
||||
# present, and returns a shiny.appobj.
|
||||
shinyAppDir_serverR <- function(appDir, options=list()) {
|
||||
# Most of the complexity here comes from needing to hot-reload if the .R files
|
||||
# change on disk, or are created, or are removed.
|
||||
|
||||
# uiHandlerSource is a function that returns an HTTP handler for serving up
|
||||
# ui.R as a webpage. The "cachedFuncWithFile" call makes sure that the closure
|
||||
# we're creating here only gets executed when ui.R's contents change.
|
||||
@@ -112,7 +132,7 @@ shinyAppDir <- function(appDir, options=list()) {
|
||||
# If not, then take the last expression that's returned from ui.R.
|
||||
.globals$ui <- NULL
|
||||
on.exit(.globals$ui <- NULL, add = FALSE)
|
||||
ui <- sourceUTF8(uiR, local = new.env(parent = globalenv()))$value
|
||||
ui <- sourceUTF8(uiR, envir = new.env(parent = globalenv()))
|
||||
if (!is.null(.globals$ui)) {
|
||||
ui <- .globals$ui[[1]]
|
||||
}
|
||||
@@ -135,7 +155,7 @@ shinyAppDir <- function(appDir, options=list()) {
|
||||
# server.R.
|
||||
.globals$server <- NULL
|
||||
on.exit(.globals$server <- NULL, add = TRUE)
|
||||
result <- sourceUTF8(serverR, local = new.env(parent = globalenv()))$value
|
||||
result <- sourceUTF8(serverR, envir = new.env(parent = globalenv()))
|
||||
if (!is.null(.globals$server)) {
|
||||
result <- .globals$server[[1]]
|
||||
}
|
||||
@@ -159,14 +179,18 @@ shinyAppDir <- function(appDir, options=list()) {
|
||||
}
|
||||
|
||||
oldwd <- NULL
|
||||
monitorHandle <- NULL
|
||||
onStart <- function() {
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
monitorHandle <<- initAutoReloadMonitor(appDir)
|
||||
if (file.exists(file.path.ci(appDir, "global.R")))
|
||||
sourceUTF8(file.path.ci(appDir, "global.R"))
|
||||
}
|
||||
onEnd <- function() {
|
||||
setwd(oldwd)
|
||||
monitorHandle()
|
||||
monitorHandle <<- NULL
|
||||
}
|
||||
|
||||
structure(
|
||||
@@ -180,6 +204,110 @@ shinyAppDir <- function(appDir, options=list()) {
|
||||
)
|
||||
}
|
||||
|
||||
# Start a reactive observer that continually monitors dir for changes to files
|
||||
# that have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif. Case is
|
||||
# ignored when checking extensions. If any changes are detected, all connected
|
||||
# Shiny sessions are reloaded.
|
||||
#
|
||||
# Use option(shiny.autoreload = TRUE) to enable this behavior. Since monitoring
|
||||
# for changes is expensive (we are polling for mtimes here, nothing fancy) this
|
||||
# feature is intended only for development.
|
||||
#
|
||||
# You can customize the file patterns Shiny will monitor by setting the
|
||||
# shiny.autoreload.pattern option. For example, to monitor only ui.R:
|
||||
# option(shiny.autoreload.pattern = glob2rx("ui.R"))
|
||||
#
|
||||
# The return value is a function that halts monitoring when called.
|
||||
initAutoReloadMonitor <- function(dir) {
|
||||
if (!getOption("shiny.autoreload", FALSE)) {
|
||||
return(function(){})
|
||||
}
|
||||
|
||||
filePattern <- getOption("shiny.autoreload.pattern",
|
||||
".*\\.(r|html?|js|css|png|jpe?g|gif)$")
|
||||
|
||||
lastValue <- NULL
|
||||
obs <- observe({
|
||||
files <- sort(list.files(dir, pattern = filePattern, recursive = TRUE,
|
||||
ignore.case = TRUE))
|
||||
times <- file.info(files)$mtime
|
||||
names(times) <- files
|
||||
|
||||
if (is.null(lastValue)) {
|
||||
# First run
|
||||
lastValue <<- times
|
||||
} else if (!identical(lastValue, times)) {
|
||||
# We've changed!
|
||||
lastValue <<- times
|
||||
for (session in appsByToken$values()) {
|
||||
session$reload()
|
||||
}
|
||||
}
|
||||
|
||||
invalidateLater(getOption("shiny.autoreload.interval", 500))
|
||||
})
|
||||
|
||||
obs$destroy
|
||||
}
|
||||
|
||||
# This reads in an app dir for a single-file application (e.g. app.R), and
|
||||
# returns a shiny.appobj.
|
||||
shinyAppDir_appR <- function(fileName, appDir, options=list()) {
|
||||
fullpath <- file.path.ci(appDir, fileName)
|
||||
|
||||
# This sources app.R and caches the content. When appObj() is called but
|
||||
# app.R hasn't changed, it won't re-source the file. But if called and
|
||||
# app.R has changed, it'll re-source the file and return the result.
|
||||
appObj <- cachedFuncWithFile(appDir, fileName, case.sensitive = FALSE,
|
||||
function(appR) {
|
||||
result <- sourceUTF8(fullpath, envir = new.env(parent = globalenv()))
|
||||
|
||||
if (!is.shiny.appobj(result))
|
||||
stop("app.R did not return a shiny.appobj object.")
|
||||
|
||||
return(result)
|
||||
}
|
||||
)
|
||||
|
||||
# A function that invokes the http handler from the appObj in app.R, but
|
||||
# since this uses appObj(), it only re-sources the file when it changes.
|
||||
dynHttpHandler <- function(...) {
|
||||
appObj()$httpHandler(...)
|
||||
}
|
||||
|
||||
dynServerFuncSource <- function(...) {
|
||||
appObj()$serverFuncSource(...)
|
||||
}
|
||||
|
||||
wwwDir <- file.path.ci(appDir, "www")
|
||||
fallbackWWWDir <- system.file("www-dir", package = "shiny")
|
||||
|
||||
oldwd <- NULL
|
||||
monitorHandle <- NULL
|
||||
onStart <- function() {
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
monitorHandle <<- initAutoReloadMonitor(appDir)
|
||||
}
|
||||
onEnd <- function() {
|
||||
setwd(oldwd)
|
||||
monitorHandle()
|
||||
monitorHandle <<- NULL
|
||||
}
|
||||
|
||||
structure(
|
||||
list(
|
||||
httpHandler = joinHandlers(c(dynHttpHandler, wwwDir, fallbackWWWDir)),
|
||||
serverFuncSource = dynServerFuncSource,
|
||||
onStart = onStart,
|
||||
onEnd = onEnd,
|
||||
options = options
|
||||
),
|
||||
class = "shiny.appobj"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param x Object to convert to a Shiny app.
|
||||
#' @export
|
||||
@@ -202,7 +330,16 @@ as.shiny.appobj.list <- function(x) {
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
as.shiny.appobj.character <- function(x) {
|
||||
shinyAppDir(x)
|
||||
if (identical(tolower(tools::file_ext(x)), "r"))
|
||||
shinyAppFile(x)
|
||||
else
|
||||
shinyAppDir(x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
is.shiny.appobj <- function(x) {
|
||||
inherits(x, "shiny.appobj")
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
@@ -230,7 +367,20 @@ as.tags.shiny.appobj <- function(x, ...) {
|
||||
height <- if (is.null(opts$height)) "400" else opts$height
|
||||
|
||||
path <- addSubApp(x)
|
||||
tags$iframe(src=path, width=width, height=height, class="shiny-frame")
|
||||
deferredIFrame(path, width, height)
|
||||
}
|
||||
|
||||
# Generate subapp iframes in such a way that they will not actually load right
|
||||
# away. Loading subapps immediately upon app load can result in a storm of
|
||||
# connections, all of which are contending for the few concurrent connections
|
||||
# that a browser will make to a specific origin. Instead, we load dummy iframes
|
||||
# and let the client load them when convenient. (See the initIframes function in
|
||||
# init_shiny.js.)
|
||||
deferredIFrame <- function(path, width, height) {
|
||||
tags$iframe("data-deferred-src" = path,
|
||||
width = width, height = height,
|
||||
class = "shiny-frame shiny-frame-deferred"
|
||||
)
|
||||
}
|
||||
|
||||
#' Knitr S3 methods
|
||||
@@ -281,8 +431,7 @@ knit_print.shiny.appobj <- function(x, ...) {
|
||||
}
|
||||
else {
|
||||
path <- addSubApp(x)
|
||||
output <- tags$iframe(src=path, width=width, height=height,
|
||||
class="shiny-frame")
|
||||
output <- deferredIFrame(path, width, height)
|
||||
}
|
||||
|
||||
# If embedded Shiny apps ever have JS/CSS dependencies (like pym.js) we'll
|
||||
@@ -306,3 +455,14 @@ knit_print.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
shiny_rmd_warning())
|
||||
output
|
||||
}
|
||||
|
||||
# Lets us drop reactive expressions directly into a knitr chunk and have the
|
||||
# value printed out! Nice for teaching if nothing else.
|
||||
#' @rdname knitr_methods
|
||||
#' @export
|
||||
knit_print.reactive <- function(x, ..., inline = FALSE) {
|
||||
renderFunc <- if (inline) renderText else renderPrint
|
||||
knitr::knit_print(renderFunc({
|
||||
x()
|
||||
}), inline = inline)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
#' @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 responsive This option is deprecated; it is no longer optional with
|
||||
#' Bootstrap 3.
|
||||
#' @param theme Alternative Bootstrap stylesheet (normally a css file within the
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' \code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.
|
||||
@@ -24,15 +24,18 @@
|
||||
#' 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
|
||||
#' @note See the \href{http://shiny.rstudio.com/articles/layout-guide.html}{
|
||||
#' Shiny-Application-Layout-Guide} for additional details on laying out fluid
|
||||
#' pages.
|
||||
#'
|
||||
#' @seealso \code{\link{column}}, \code{\link{sidebarLayout}}
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fluidPage(
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' # Example of UI with fluidPage
|
||||
#' ui <- fluidPage(
|
||||
#'
|
||||
#' # Application title
|
||||
#' titlePanel("Hello Shiny!"),
|
||||
@@ -53,9 +56,21 @@
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#' )
|
||||
#'
|
||||
#' shinyUI(fluidPage(
|
||||
#' # Server logic
|
||||
#' server <- function(input, output) {
|
||||
#' output$distPlot <- renderPlot({
|
||||
#' hist(rnorm(input$obs))
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' # Complete app with UI and server components
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#'
|
||||
#' # UI demonstrating column layouts
|
||||
#' ui <- fluidPage(
|
||||
#' title = "Hello Shiny!",
|
||||
#' fluidRow(
|
||||
#' column(width = 4,
|
||||
@@ -65,11 +80,13 @@
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#' )
|
||||
#'
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidPage <- function(..., title = NULL, responsive = TRUE, theme = NULL) {
|
||||
fluidPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
|
||||
bootstrapPage(div(class = "container-fluid", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
@@ -80,7 +97,7 @@ fluidPage <- function(..., title = NULL, responsive = TRUE, theme = NULL) {
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidRow <- function(...) {
|
||||
div(class = "row-fluid", ...)
|
||||
div(class = "row", ...)
|
||||
}
|
||||
|
||||
#' Create a page with a fixed layout
|
||||
@@ -95,8 +112,8 @@ fluidRow <- function(...) {
|
||||
#'
|
||||
#' @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 responsive This option is deprecated; it is no longer optional with
|
||||
#' Bootstrap 3.
|
||||
#' @param theme Alternative Bootstrap stylesheet (normally a css file within the
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' \code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.
|
||||
@@ -109,15 +126,17 @@ fluidRow <- function(...) {
|
||||
#' 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}{
|
||||
#' @note See the \href{http://shiny.rstudio.com/articles/layout-guide.html}{
|
||||
#' Shiny Application Layout Guide} for additional details on laying out fixed
|
||||
#' pages.
|
||||
#'
|
||||
#' @seealso \code{\link{column}}
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fixedPage(
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fixedPage(
|
||||
#' title = "Hello, Shiny!",
|
||||
#' fixedRow(
|
||||
#' column(width = 4,
|
||||
@@ -127,11 +146,14 @@ fluidRow <- function(...) {
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#' )
|
||||
#'
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#'
|
||||
#' @rdname fixedPage
|
||||
#' @export
|
||||
fixedPage <- function(..., title = NULL, responsive = TRUE, theme = NULL) {
|
||||
fixedPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
|
||||
bootstrapPage(div(class = "container", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
@@ -162,33 +184,52 @@ fixedRow <- function(...) {
|
||||
#' @seealso \code{\link{fluidRow}}, \code{\link{fixedRow}}.
|
||||
#'
|
||||
#' @examples
|
||||
#' fluidRow(
|
||||
#' column(4,
|
||||
#' sliderInput("obs", "Number of observations:",
|
||||
#' min = 1, max = 1000, value = 500)
|
||||
#' ),
|
||||
#' column(8,
|
||||
#' plotOutput("distPlot")
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' fluidRow(
|
||||
#' column(4,
|
||||
#' sliderInput("obs", "Number of observations:",
|
||||
#' min = 1, max = 1000, value = 500)
|
||||
#' ),
|
||||
#' column(8,
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' fluidRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' server <- function(input, output) {
|
||||
#' output$distPlot <- renderPlot({
|
||||
#' hist(rnorm(input$obs))
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#'
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' fluidRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#' @export
|
||||
column <- function(width, ..., offset = 0) {
|
||||
|
||||
if (!is.numeric(width) || (width < 1) || (width > 12))
|
||||
stop("column width must be between 1 and 12")
|
||||
|
||||
colClass <- paste0("span", width)
|
||||
colClass <- paste0("col-sm-", width)
|
||||
if (offset > 0)
|
||||
colClass <- paste0(colClass, " offset", offset)
|
||||
colClass <- paste0(colClass, " col-sm-offset-", offset)
|
||||
div(class = colClass, ...)
|
||||
}
|
||||
|
||||
@@ -204,13 +245,19 @@ column <- function(width, ..., offset = 0) {
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' titlePanel("Hello Shiny!")
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' titlePanel("Hello Shiny!")
|
||||
#' )
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#' @export
|
||||
titlePanel <- function(title, windowTitle=title) {
|
||||
tagList(
|
||||
tags$head(tags$title(windowTitle)),
|
||||
h2(style = "padding: 10px 0px;", title)
|
||||
h2(title)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -228,8 +275,11 @@ titlePanel <- function(title, windowTitle=title) {
|
||||
#' layout.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' # Define UI
|
||||
#' shinyUI(fluidPage(
|
||||
#' ui <- fluidPage(
|
||||
#'
|
||||
#' # Application title
|
||||
#' titlePanel("Hello Shiny!"),
|
||||
@@ -250,8 +300,18 @@ titlePanel <- function(title, windowTitle=title) {
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#' )
|
||||
#'
|
||||
#' # Server logic
|
||||
#' server <- function(input, output) {
|
||||
#' output$distPlot <- renderPlot({
|
||||
#' hist(rnorm(input$obs))
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' # Complete app with UI and server components
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
sidebarLayout <- function(sidebarPanel,
|
||||
mainPanel,
|
||||
@@ -288,13 +348,18 @@ sidebarLayout <- function(sidebarPanel,
|
||||
#' @seealso \code{\link{fluidPage}}, \code{\link{flowLayout}}
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fluidPage(
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' verticalLayout(
|
||||
#' a(href="http://example.com/link1", "Link One"),
|
||||
#' a(href="http://example.com/link2", "Link Two"),
|
||||
#' a(href="http://example.com/link3", "Link Three")
|
||||
#' )
|
||||
#' ))
|
||||
#' )
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#' @export
|
||||
verticalLayout <- function(..., fluid = TRUE) {
|
||||
lapply(list(...), function(row) {
|
||||
@@ -310,8 +375,8 @@ verticalLayout <- function(..., fluid = TRUE) {
|
||||
#'
|
||||
#' Lays out elements in a left-to-right, top-to-bottom arrangement. The elements
|
||||
#' on a given row will be top-aligned with each other. This layout will not work
|
||||
#' well with elements that have a percentage-based width (e.g. `plotOutput` at
|
||||
#' its default setting of `width = "100%"`).
|
||||
#' well with elements that have a percentage-based width (e.g.
|
||||
#' \code{\link{plotOutput}} at its default setting of \code{width = "100\%"}).
|
||||
#'
|
||||
#' @param ... Unnamed arguments will become child elements of the layout. Named
|
||||
#' arguments will become HTML attributes on the outermost tag.
|
||||
@@ -321,11 +386,16 @@ verticalLayout <- function(..., fluid = TRUE) {
|
||||
#' @seealso \code{\link{verticalLayout}}
|
||||
#'
|
||||
#' @examples
|
||||
#' flowLayout(
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- flowLayout(
|
||||
#' numericInput("rows", "How many rows?", 5),
|
||||
#' selectInput("letter", "Which letter?", LETTERS),
|
||||
#' sliderInput("value", "What value?", 0, 100, 50)
|
||||
#' )
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#' @export
|
||||
flowLayout <- function(..., cellArgs = list()) {
|
||||
|
||||
@@ -371,21 +441,33 @@ inputPanel <- function(...) {
|
||||
#' of the layout.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' # Server code used for all examples
|
||||
#' server <- function(input, output) {
|
||||
#' output$plot1 <- renderPlot(plot(cars))
|
||||
#' output$plot2 <- renderPlot(plot(pressure))
|
||||
#' output$plot3 <- renderPlot(plot(AirPassengers))
|
||||
#' }
|
||||
#'
|
||||
#' # Equal sizing
|
||||
#' splitLayout(
|
||||
#' ui <- splitLayout(
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2")
|
||||
#' )
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#' # Custom widths
|
||||
#' splitLayout(cellWidths = c("25%", "75%"),
|
||||
#' ui <- splitLayout(cellWidths = c("25%", "75%"),
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2")
|
||||
#' )
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#' # All cells at 300 pixels wide, with cell padding
|
||||
#' # and a border around everything
|
||||
#' splitLayout(
|
||||
#' ui <- splitLayout(
|
||||
#' style = "border: 1px solid silver;",
|
||||
#' cellWidths = 300,
|
||||
#' cellArgs = list(style = "padding: 6px"),
|
||||
@@ -393,6 +475,8 @@ inputPanel <- function(...) {
|
||||
#' plotOutput("plot2"),
|
||||
#' plotOutput("plot3")
|
||||
#' )
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) {
|
||||
|
||||
@@ -419,3 +503,193 @@ splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) {
|
||||
}, SIMPLIFY = FALSE)
|
||||
))
|
||||
}
|
||||
|
||||
#' Flex Box-based row/column layouts
|
||||
#'
|
||||
#' Creates row and column layouts with proportionally-sized cells, using the
|
||||
#' Flex Box layout model of CSS3. These can be nested to create arbitrary
|
||||
#' proportional-grid layouts. \strong{Warning:} Flex Box is not well supported
|
||||
#' by Internet Explorer, so these functions should only be used where modern
|
||||
#' browsers can be assumed.
|
||||
#'
|
||||
#' @details If you try to use \code{fillRow} and \code{fillCol} inside of other
|
||||
#' Shiny containers, such as \code{\link{sidebarLayout}},
|
||||
#' \code{\link{navbarPage}}, or even \code{tags$div}, you will probably find
|
||||
#' that they will not appear. This is due to \code{fillRow} and \code{fillCol}
|
||||
#' defaulting to \code{height="100\%"}, which will only work inside of
|
||||
#' containers that have determined their own size (rather than shrinking to
|
||||
#' the size of their contents, as is usually the case in HTML).
|
||||
#'
|
||||
#' To avoid this problem, you have two options:
|
||||
#' \itemize{
|
||||
#' \item only use \code{fillRow}/\code{fillCol} inside of \code{fillPage},
|
||||
#' \code{fillRow}, or \code{fillCol}
|
||||
#' \item provide an explicit \code{height} argument to
|
||||
#' \code{fillRow}/\code{fillCol}
|
||||
#' }
|
||||
#'
|
||||
#' @param ... UI objects to put in each row/column cell; each argument will
|
||||
#' occupy a single cell. (To put multiple items in a single cell, you can use
|
||||
#' \code{\link{tagList}} or \code{\link{div}} to combine them.) Named
|
||||
#' arguments will be used as attributes on the \code{div} element that
|
||||
#' encapsulates the row/column.
|
||||
#' @param flex Determines how space should be distributed to the cells. Can be a
|
||||
#' single value like \code{1} or \code{2} to evenly distribute the available
|
||||
#' space; or use a vector of numbers to specify the proportions. For example,
|
||||
#' \code{flex = c(2, 3)} would cause the space to be split 40\%/60\% between
|
||||
#' two cells. NA values will cause the corresponding cell to be sized
|
||||
#' according to its contents (without growing or shrinking).
|
||||
#' @param width,height The total amount of width and height to use for the
|
||||
#' entire row/column. For the default height of \code{"100\%"} to be
|
||||
#' effective, the parent must be \code{fillPage}, another
|
||||
#' \code{fillRow}/\code{fillCol}, or some other HTML element whose height is
|
||||
#' not determined by the height of its contents.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Only run this example in interactive R sessions.
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fillPage(fillRow(
|
||||
#' plotOutput("plotLeft", height = "100%"),
|
||||
#' fillCol(
|
||||
#' plotOutput("plotTopRight", height = "100%"),
|
||||
#' plotOutput("plotBottomRight", height = "100%")
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' output$plotLeft <- renderPlot(plot(cars))
|
||||
#' output$plotTopRight <- renderPlot(plot(pressure))
|
||||
#' output$plotBottomRight <- renderPlot(plot(AirPassengers))
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#' }
|
||||
#' @export
|
||||
fillRow <- function(..., flex = 1, width = "100%", height = "100%") {
|
||||
flexfill(..., direction = "row", flex = flex, width = width, height = height)
|
||||
}
|
||||
|
||||
#' @rdname fillRow
|
||||
#' @export
|
||||
fillCol <- function(..., flex = 1, width = "100%", height = "100%") {
|
||||
flexfill(..., direction = "column", flex = flex, width = width, height = height)
|
||||
}
|
||||
|
||||
flexfill <- function(..., direction, flex, width = width, height = height) {
|
||||
children <- list(...)
|
||||
attrs <- list()
|
||||
|
||||
if (!is.null(names(children))) {
|
||||
attrs <- children[names(children) != ""]
|
||||
children <- children[names(children) == ""]
|
||||
}
|
||||
|
||||
if (length(flex) > length(children)) {
|
||||
flex <- flex[1:length(children)]
|
||||
}
|
||||
|
||||
# The dimension along the main axis
|
||||
main <- switch(direction,
|
||||
row = "width",
|
||||
"row-reverse" = "width",
|
||||
column = "height",
|
||||
"column-reverse" = "height",
|
||||
stop("Unexpected direction")
|
||||
)
|
||||
# The dimension along the cross axis
|
||||
cross <- if (main == "width") "height" else "width"
|
||||
|
||||
divArgs <- list(
|
||||
class = sprintf("flexfill-container flexfill-container-%s", direction),
|
||||
style = css(
|
||||
display = "-webkit-flex",
|
||||
display = "-ms-flexbox",
|
||||
display = "flex",
|
||||
.webkit.flex.direction = direction,
|
||||
.ms.flex.direction = direction,
|
||||
flex.direction = direction,
|
||||
width = validateCssUnit(width),
|
||||
height = validateCssUnit(height)
|
||||
),
|
||||
mapply(children, flex, FUN = function(el, flexValue) {
|
||||
if (is.na(flexValue)) {
|
||||
# If the flex value is NA, then put the element in a simple flex item
|
||||
# that sizes itself (along the main axis) to its contents
|
||||
tags$div(
|
||||
class = "flexfill-item",
|
||||
style = css(
|
||||
position = "relative",
|
||||
"-webkit-flex" = "none",
|
||||
"-ms-flex" = "none",
|
||||
flex = "none"
|
||||
),
|
||||
style = paste0(main, ":auto;", cross, ":100%;"),
|
||||
el
|
||||
)
|
||||
} else if (is.numeric(flexValue)) {
|
||||
# If the flex value is numeric, we need *two* wrapper divs. The outer is
|
||||
# the flex item, and the inner is an absolute-fill div that is needed to
|
||||
# make percentage-based sizing for el work correctly. I don't understand
|
||||
# why this is needed but the truth is probably in this SO page:
|
||||
# http://stackoverflow.com/questions/15381172/css-flexbox-child-height-100
|
||||
tags$div(
|
||||
class = "flexfill-item",
|
||||
style = css(
|
||||
position = "relative",
|
||||
"-webkit-flex" = flexValue,
|
||||
"-ms-flex" = flexValue,
|
||||
flex = flexValue,
|
||||
width = "100%", height = "100%"
|
||||
),
|
||||
tags$div(
|
||||
class = "flexfill-item-inner",
|
||||
style = css(
|
||||
position = "absolute",
|
||||
top = 0, left = 0, right = 0, bottom = 0
|
||||
),
|
||||
el
|
||||
)
|
||||
)
|
||||
} else {
|
||||
stop("Unexpected flex argument: ", flexValue)
|
||||
}
|
||||
}, SIMPLIFY = FALSE, USE.NAMES = FALSE)
|
||||
)
|
||||
do.call(tags$div, c(attrs, divArgs))
|
||||
}
|
||||
|
||||
css <- function(..., collapse_ = "") {
|
||||
props <- list(...)
|
||||
if (length(props) == 0) {
|
||||
return("")
|
||||
}
|
||||
|
||||
if (is.null(names(props)) || any(names(props) == "")) {
|
||||
stop("cssList expects all arguments to be named")
|
||||
}
|
||||
|
||||
# Necessary to make factors show up as level names, not numbers
|
||||
props[] <- lapply(props, paste, collapse = " ")
|
||||
|
||||
# Drop null args
|
||||
props <- props[!sapply(props, empty)]
|
||||
if (length(props) == 0) {
|
||||
return("")
|
||||
}
|
||||
|
||||
# Replace all '.' and '_' in property names to '-'
|
||||
names(props) <- gsub("[._]", "-", tolower(gsub("([A-Z])", "-\\1", names(props))))
|
||||
|
||||
# Create "!important" suffix for each property whose name ends with !, then
|
||||
# remove the ! from the property name
|
||||
important <- ifelse(grepl("!$", names(props), perl = TRUE), " !important", "")
|
||||
names(props) <- sub("!$", "", names(props), perl = TRUE)
|
||||
|
||||
paste0(names(props), ":", props, important, ";", collapse = collapse_)
|
||||
}
|
||||
|
||||
empty <- function(x) {
|
||||
length(x) == 0 || (is.character(x) && !any(nzchar(x)))
|
||||
}
|
||||
|
||||
2004
R/bootstrap.R
2004
R/bootstrap.R
File diff suppressed because it is too large
Load Diff
21
R/cache.R
21
R/cache.R
@@ -2,18 +2,15 @@
|
||||
# files changes on disk. Each time the cache is dirtied, the set of files is
|
||||
# cleared. Therefore, the set of files needs to be re-built each time the cached
|
||||
# code executes. This approach allows for dynamic dependency graphs.
|
||||
CacheContext <- setRefClass(
|
||||
CacheContext <- R6Class(
|
||||
'CacheContext',
|
||||
fields = list(
|
||||
.dirty = 'logical',
|
||||
.tests = 'list'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
.dirty <<- TRUE
|
||||
# List of functions that return TRUE if dirty
|
||||
.tests <<- list()
|
||||
},
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.dirty = TRUE,
|
||||
# List of functions that return TRUE if dirty
|
||||
.tests = list(),
|
||||
|
||||
addDependencyFile = function(file) {
|
||||
if (.dirty)
|
||||
return()
|
||||
@@ -53,7 +50,7 @@ CacheContext <- setRefClass(
|
||||
},
|
||||
with = function(func) {
|
||||
oldCC <- .currentCacheContext$cc
|
||||
.currentCacheContext$cc <- .self
|
||||
.currentCacheContext$cc <- self
|
||||
on.exit(.currentCacheContext$cc <- oldCC)
|
||||
|
||||
return(func())
|
||||
|
||||
334
R/conditions.R
Normal file
334
R/conditions.R
Normal file
@@ -0,0 +1,334 @@
|
||||
#' Stack trace manipulation functions
|
||||
#'
|
||||
#' Advanced (borderline internal) functions for capturing, printing, and
|
||||
#' manipulating stack traces.
|
||||
#'
|
||||
#' @return \code{printError} and \code{printStackTrace} return
|
||||
#' \code{invisible()}. The other functions pass through the results of
|
||||
#' \code{expr}.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Keeps tryCatch and withVisible related calls off the
|
||||
#' # pretty-printed stack trace
|
||||
#'
|
||||
#' visibleFunction1 <- function() {
|
||||
#' stop("Kaboom!")
|
||||
#' }
|
||||
#'
|
||||
#' visibleFunction2 <- function() {
|
||||
#' visibleFunction1()
|
||||
#' }
|
||||
#'
|
||||
#' hiddenFunction <- function(expr) {
|
||||
#' expr
|
||||
#' }
|
||||
#'
|
||||
#' # An example without ..stacktraceon/off.. manipulation.
|
||||
#' # The outer "try" is just to prevent example() from stopping.
|
||||
#' try({
|
||||
#' # The withLogErrors call ensures that stack traces are captured
|
||||
#' # and that errors that bubble up are logged using warning().
|
||||
#' withLogErrors({
|
||||
#' # tryCatch and withVisible are just here to add some noise to
|
||||
#' # the stack trace.
|
||||
#' tryCatch(
|
||||
#' withVisible({
|
||||
#' hiddenFunction(visibleFunction2())
|
||||
#' })
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#'
|
||||
#' # Now the same example, but with ..stacktraceon/off.. to hide some
|
||||
#' # of the less-interesting bits (tryCatch and withVisible).
|
||||
#' ..stacktraceoff..({
|
||||
#' try({
|
||||
#' withLogErrors({
|
||||
#' tryCatch(
|
||||
#' withVisible(
|
||||
#' hiddenFunction(
|
||||
#' ..stacktraceon..(visibleFunction2())
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' })
|
||||
#'
|
||||
#'
|
||||
#' @name stacktrace
|
||||
#' @rdname stacktrace
|
||||
#' @keywords internal
|
||||
NULL
|
||||
|
||||
getCallNames <- function(calls) {
|
||||
sapply(calls, function(call) {
|
||||
if (is.function(call[[1]])) {
|
||||
"<Anonymous>"
|
||||
} else if (inherits(call[[1]], "call")) {
|
||||
paste0(format(call[[1]]), collapse = " ")
|
||||
} else if (typeof(call[[1]]) == "promise") {
|
||||
"<Promise>"
|
||||
} else {
|
||||
paste0(as.character(call[[1]]), collapse = " ")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getLocs <- function(calls) {
|
||||
sapply(calls, function(call) {
|
||||
srcref <- attr(call, "srcref", exact = TRUE)
|
||||
if (!is.null(srcref)) {
|
||||
srcfile <- attr(srcref, "srcfile", exact = TRUE)
|
||||
if (!is.null(srcfile) && !is.null(srcfile$filename)) {
|
||||
loc <- paste0(srcfile$filename, "#", srcref[[1]])
|
||||
return(paste0(" [", loc, "]"))
|
||||
}
|
||||
}
|
||||
return("")
|
||||
})
|
||||
}
|
||||
|
||||
#' @details \code{captureStackTraces} runs the given \code{expr} and if any
|
||||
#' \emph{uncaught} errors occur, annotates them with stack trace info for use
|
||||
#' by \code{printError} and \code{printStackTrace}. It is not necessary to use
|
||||
#' \code{captureStackTraces} around the same expression as
|
||||
#' \code{withLogErrors}, as the latter includes a call to the former. Note
|
||||
#' that if \code{expr} contains calls (either directly or indirectly) to
|
||||
#' \code{try}, or \code{tryCatch} with an error handler, stack traces therein
|
||||
#' cannot be captured unless another \code{captureStackTraces} call is
|
||||
#' inserted in the interior of the \code{try} or \code{tryCatch}. This is
|
||||
#' because these calls catch the error and prevent it from traveling up to the
|
||||
#' condition handler installed by \code{captureStackTraces}.
|
||||
#'
|
||||
#' @param expr The expression to wrap.
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
captureStackTraces <- function(expr) {
|
||||
withCallingHandlers(expr,
|
||||
error = function(e) {
|
||||
if (is.null(attr(e, "stack.trace", exact = TRUE))) {
|
||||
calls <- sys.calls()
|
||||
attr(e, "stack.trace") <- calls
|
||||
stop(e)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#' @details \code{withLogErrors} captures stack traces and logs errors that
|
||||
#' occur in \code{expr}, but does allow errors to propagate beyond this point
|
||||
#' (i.e. it doesn't catch the error). The same caveats that apply to
|
||||
#' \code{captureStackTraces} with regard to \code{try}/\code{tryCatch} apply
|
||||
#' to \code{withLogErrors}.
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
withLogErrors <- function(expr,
|
||||
full = getOption("shiny.fullstacktrace", FALSE),
|
||||
offset = getOption("shiny.stacktraceoffset", TRUE)) {
|
||||
|
||||
withCallingHandlers(
|
||||
captureStackTraces(expr),
|
||||
error = function(cond) {
|
||||
# Don't print shiny.silent.error (i.e. validation errors)
|
||||
if (inherits(cond, "shiny.silent.error")) return()
|
||||
if (isTRUE(getOption("show.error.messages"))) {
|
||||
printError(cond, full = full, offset = offset)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#' @details \code{printError} prints the error and stack trace (if any) using
|
||||
#' \code{warning(immediate.=TRUE)}. \code{printStackTrace} prints the stack
|
||||
#' trace only.
|
||||
#'
|
||||
#' @param cond An condition object (generally, an error).
|
||||
#' @param full If \code{TRUE}, then every element of \code{sys.calls()} will be
|
||||
#' included in the stack trace. By default (\code{FALSE}), calls that Shiny
|
||||
#' deems uninteresting will be hidden.
|
||||
#' @param offset If \code{TRUE} (the default), srcrefs will be reassigned from
|
||||
#' the calls they originated from, to the destinations of those calls. If
|
||||
#' you're used to stack traces from other languages, this feels more
|
||||
#' intuitive, as the definition of the function indicated in the call and the
|
||||
#' location specified by the srcref match up. If \code{FALSE}, srcrefs will be
|
||||
#' left alone (traditional R treatment where the srcref is of the callsite).
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
printError <- function(cond,
|
||||
full = getOption("shiny.fullstacktrace", FALSE),
|
||||
offset = getOption("shiny.stacktraceoffset", TRUE)) {
|
||||
|
||||
warning(call. = FALSE, immediate. = TRUE, sprintf("Error in %s: %s",
|
||||
getCallNames(list(conditionCall(cond))), conditionMessage(cond)))
|
||||
printStackTrace(cond, full = full, offset = offset)
|
||||
invisible()
|
||||
}
|
||||
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
printStackTrace <- function(cond,
|
||||
full = getOption("shiny.fullstacktrace", FALSE),
|
||||
offset = getOption("shiny.stacktraceoffset", TRUE)) {
|
||||
|
||||
stackTrace <- attr(cond, "stack.trace", exact = TRUE)
|
||||
tryCatch(
|
||||
if (!is.null(stackTrace)) {
|
||||
message(paste0(
|
||||
"Stack trace (innermost first):\n",
|
||||
paste0(collapse = "\n",
|
||||
formatStackTrace(stackTrace, full = full, offset = offset,
|
||||
indent = " ")
|
||||
)
|
||||
))
|
||||
} else {
|
||||
message("No stack trace available")
|
||||
},
|
||||
|
||||
error = function(cond) {
|
||||
warning("Failed to write stack trace: ", cond)
|
||||
}
|
||||
)
|
||||
invisible()
|
||||
}
|
||||
|
||||
#' @details \code{extractStackTrace} takes a list of calls (e.g. as returned
|
||||
#' from \code{conditionStackTrace(cond)}) and returns a data frame with one
|
||||
#' row for each stack frame and the columns \code{num} (stack frame number),
|
||||
#' \code{call} (a function name or similar), and \code{loc} (source file path
|
||||
#' and line number, if available).
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
extractStackTrace <- function(calls,
|
||||
full = getOption("shiny.fullstacktrace", FALSE),
|
||||
offset = getOption("shiny.stacktraceoffset", TRUE)) {
|
||||
|
||||
srcrefs <- getSrcRefs(calls)
|
||||
if (offset) {
|
||||
# Offset calls vs. srcrefs by 1 to make them more intuitive.
|
||||
# E.g. for "foo [bar.R:10]", line 10 of bar.R will be part of
|
||||
# the definition of foo().
|
||||
srcrefs <- c(utils::tail(srcrefs, -1), list(NULL))
|
||||
}
|
||||
calls <- setSrcRefs(calls, srcrefs)
|
||||
|
||||
callnames <- getCallNames(calls)
|
||||
|
||||
# Hide and show parts of the callstack based on ..stacktrace(on|off)..
|
||||
if (full) {
|
||||
toShow <- rep.int(TRUE, length(calls))
|
||||
} else {
|
||||
# Remove stop(), .handleSimpleError(), and h() calls from the end of
|
||||
# the calls--they don't add any helpful information. But only remove
|
||||
# the last *contiguous* block of them, and then, only if they are the
|
||||
# last thing in the calls list.
|
||||
hideable <- callnames %in% c("stop", ".handleSimpleError", "h")
|
||||
# What's the last that *didn't* match stop/.handleSimpleError/h?
|
||||
lastGoodCall <- max(which(!hideable))
|
||||
toRemove <- length(calls) - lastGoodCall
|
||||
# But don't remove more than 5 levels--that's an indication we might
|
||||
# have gotten it wrong, I guess
|
||||
if (toRemove > 0 && toRemove < 5) {
|
||||
calls <- utils::head(calls, -toRemove)
|
||||
callnames <- utils::head(callnames, -toRemove)
|
||||
}
|
||||
|
||||
# This uses a ref-counting scheme. It might make sense to switch this
|
||||
# to a toggling scheme, so the most recent ..stacktrace(on|off)..
|
||||
# directive wins, regardless of what came before it.
|
||||
# Also explicitly remove ..stacktraceon.. because it can appear with
|
||||
# score > 0 but still should never be shown.
|
||||
score <- rep.int(0, length(callnames))
|
||||
score[callnames == "..stacktraceoff.."] <- -1
|
||||
score[callnames == "..stacktraceon.."] <- 1
|
||||
toShow <- (1 + cumsum(score)) > 0 & !(callnames %in% c("..stacktraceon..", "..stacktraceoff.."))
|
||||
}
|
||||
calls <- calls[toShow]
|
||||
|
||||
calls <- rev(calls) # Show in traceback() order
|
||||
index <- rev(which(toShow))
|
||||
width <- floor(log10(max(index))) + 1
|
||||
|
||||
data.frame(
|
||||
num = index,
|
||||
call = getCallNames(calls),
|
||||
loc = getLocs(calls),
|
||||
stringsAsFactors = FALSE
|
||||
)
|
||||
}
|
||||
|
||||
#' @details \code{formatStackTrace} is similar to \code{extractStackTrace}, but
|
||||
#' it returns a preformatted character vector instead of a data frame.
|
||||
#' @param indent A string to prefix every line of the stack trace.
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
formatStackTrace <- function(calls, indent = " ",
|
||||
full = getOption("shiny.fullstacktrace", FALSE),
|
||||
offset = getOption("shiny.stacktraceoffset", TRUE)) {
|
||||
|
||||
st <- extractStackTrace(calls, full = full, offset = offset)
|
||||
if (nrow(st) == 0) {
|
||||
return(character(0))
|
||||
}
|
||||
|
||||
width <- floor(log10(max(st$num))) + 1
|
||||
paste0(
|
||||
indent,
|
||||
formatC(st$num, width = width),
|
||||
": ",
|
||||
st$call,
|
||||
st$loc
|
||||
)
|
||||
}
|
||||
|
||||
getSrcRefs <- function(calls) {
|
||||
lapply(calls, function(call) {
|
||||
attr(call, "srcref", exact = TRUE)
|
||||
})
|
||||
}
|
||||
|
||||
setSrcRefs <- function(calls, srcrefs) {
|
||||
mapply(function(call, srcref) {
|
||||
structure(call, srcref = srcref)
|
||||
}, calls, srcrefs)
|
||||
}
|
||||
|
||||
stripStackTrace <- function(cond) {
|
||||
conditionStackTrace(cond) <- NULL
|
||||
}
|
||||
|
||||
#' @details \code{conditionStackTrace} and \code{conditionStackTrace<-} are
|
||||
#' accessor functions for getting/setting stack traces on conditions.
|
||||
#'
|
||||
#' @param cond A condition that may have previously been annotated by
|
||||
#' \code{captureStackTraces} (or \code{withLogErrors}).
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
conditionStackTrace <- function(cond) {
|
||||
attr(cond, "stack.trace", exact = TRUE)
|
||||
}
|
||||
|
||||
#' @param value The stack trace value to assign to the condition.
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
`conditionStackTrace<-` <- function(cond, value) {
|
||||
attr(cond, "stack.trace") <- value
|
||||
invisible(cond)
|
||||
}
|
||||
|
||||
#' @details The two functions \code{..stacktraceon..} and
|
||||
#' \code{..stacktraceoff..} have no runtime behavior during normal execution;
|
||||
#' they exist only to create artifacts on the stack trace (sys.call()) that
|
||||
#' instruct the stack trace pretty printer what parts of the stack trace are
|
||||
#' interesting or not. The initial state is 1 and we walk from the outermost
|
||||
#' call inwards. Each ..stacktraceoff.. decrements the state by one, and each
|
||||
#' ..stacktraceon.. increments the state by one. Any stack trace frame whose
|
||||
#' value is less than 1 is hidden, and finally, the ..stacktraceon.. and
|
||||
#' ..stacktraceoff.. calls themselves are hidden too.
|
||||
#'
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
..stacktraceon.. <- function(expr) expr
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
..stacktraceoff.. <- function(expr) expr
|
||||
157
R/diagnose.R
Normal file
157
R/diagnose.R
Normal file
@@ -0,0 +1,157 @@
|
||||
# Analyze an R file for possible extra or missing commas. Returns FALSE if any
|
||||
# problems detected, TRUE otherwise.
|
||||
diagnoseCode <- function(path = NULL, text = NULL) {
|
||||
if (!xor(is.null(path), is.null(text))) {
|
||||
stop("Must specify `path` or `text`, but not both.")
|
||||
}
|
||||
|
||||
if (!is.null(path)) {
|
||||
tokens <- sourcetools::tokenize_file(path)
|
||||
} else {
|
||||
tokens <- sourcetools::tokenize_string(text)
|
||||
}
|
||||
|
||||
find_scopes <- function(tokens) {
|
||||
# Strip whitespace and comments
|
||||
tokens <- tokens[!(tokens$type %in% c("whitespace", "comment")),]
|
||||
|
||||
# Replace various types of things with "value"
|
||||
tokens$type[tokens$type %in% c("string", "number", "symbol", "keyword")] <- "value"
|
||||
|
||||
# Record types for close and open brace/bracket/parens, and commas
|
||||
brace_idx <- tokens$value %in% c("(", ")", "{", "}", "[", "]", ",")
|
||||
tokens$type[brace_idx] <- tokens$value[brace_idx]
|
||||
|
||||
# Stack-related function for recording scope. Starting scope is "{"
|
||||
stack <- "{"
|
||||
push <- function(x) {
|
||||
stack <<- c(stack, x)
|
||||
}
|
||||
pop <- function() {
|
||||
if (length(stack) == 1) {
|
||||
# Stack underflow, but we need to keep going
|
||||
return(NA_character_)
|
||||
}
|
||||
res <- stack[length(stack)]
|
||||
stack <<- stack[-length(stack)]
|
||||
res
|
||||
}
|
||||
peek <- function() {
|
||||
stack[length(stack)]
|
||||
}
|
||||
|
||||
# First, establish a scope for each token. For opening and closing
|
||||
# braces/brackets/parens, the scope at that location is the *surrounding*
|
||||
# scope, not the new scope created by the brace/bracket/paren.
|
||||
for (i in seq_len(nrow(tokens))) {
|
||||
value <- tokens$value[i]
|
||||
|
||||
tokens$scope[i] <- peek()
|
||||
if (value %in% c("{", "(", "[")) {
|
||||
push(value)
|
||||
|
||||
} else if (value == "}") {
|
||||
if (!identical(pop(), "{"))
|
||||
tokens$err[i] <- "unmatched_brace"
|
||||
# For closing brace/paren/bracket, get the scope after popping
|
||||
tokens$scope[i] <- peek()
|
||||
|
||||
} else if (value == ")") {
|
||||
if (!identical(pop(), "("))
|
||||
tokens$err[i] <- "unmatched_paren"
|
||||
tokens$scope[i] <- peek()
|
||||
|
||||
} else if (value == "]") {
|
||||
if (!identical(pop(), "["))
|
||||
tokens$err[i] <- "unmatched_bracket"
|
||||
tokens$scope[i] <- peek()
|
||||
}
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
check_commas <- function(tokens) {
|
||||
# Find extra and missing commas
|
||||
tokens$err <- mapply(
|
||||
tokens$type,
|
||||
c("", tokens$type[-length(tokens$type)]),
|
||||
c(tokens$type[-1], ""),
|
||||
tokens$scope,
|
||||
tokens$err,
|
||||
SIMPLIFY = FALSE,
|
||||
FUN = function(type, prevType, nextType, scope, err) {
|
||||
# If an error was already found, just return it. This could have
|
||||
# happened in the brace/paren/bracket matching phase.
|
||||
if (!is.na(err)) {
|
||||
return(err)
|
||||
}
|
||||
if (scope == "(") {
|
||||
if (type == "," &&
|
||||
(prevType == "(" || prevType == "," || nextType == ")"))
|
||||
{
|
||||
return("extra_comma")
|
||||
}
|
||||
|
||||
if ((prevType == ")" && type == "value") ||
|
||||
(prevType == "value" && type == "value")) {
|
||||
return("missing_comma")
|
||||
}
|
||||
}
|
||||
|
||||
NA_character_
|
||||
}
|
||||
)
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
|
||||
tokens$err <- NA_character_
|
||||
tokens <- find_scopes(tokens)
|
||||
tokens <- check_commas(tokens)
|
||||
|
||||
# No errors found
|
||||
if (all(is.na(tokens$err))) {
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
# If we got here, errors were found; print messages.
|
||||
if (!is.null(path)) {
|
||||
lines <- readLines(path)
|
||||
} else {
|
||||
lines <- strsplit(text, "\n")[[1]]
|
||||
}
|
||||
|
||||
# Print out the line of code with the error, and point to the column with
|
||||
# the error.
|
||||
show_code_error <- function(msg, lines, row, col) {
|
||||
message(paste0(
|
||||
msg, "\n",
|
||||
row, ":", lines[row], "\n",
|
||||
paste0(rep.int(" ", nchar(as.character(row)) + 1), collapse = ""),
|
||||
gsub(perl = TRUE, "[^\\s]", " ", substr(lines[row], 1, col-1)), "^"
|
||||
))
|
||||
}
|
||||
|
||||
err_idx <- which(!is.na(tokens$err))
|
||||
msg <- ""
|
||||
for (i in err_idx) {
|
||||
row <- tokens$row[i]
|
||||
col <- tokens$column[i]
|
||||
err <- tokens$err[i]
|
||||
|
||||
if (err == "missing_comma") {
|
||||
show_code_error("Possible missing comma at:", lines, row, col)
|
||||
} else if (err == "extra_comma") {
|
||||
show_code_error("Possible extra comma at:", lines, row, col)
|
||||
} else if (err == "unmatched_brace") {
|
||||
show_code_error("Possible unmatched '}' at:", lines, row, col)
|
||||
} else if (err == "unmatched_paren") {
|
||||
show_code_error("Possible unmatched ')' at:", lines, row, col)
|
||||
} else if (err == "unmatched_bracket") {
|
||||
show_code_error("Possible unmatched ']' at:", lines, row, col)
|
||||
}
|
||||
}
|
||||
return(FALSE)
|
||||
}
|
||||
@@ -20,18 +20,19 @@
|
||||
# form upload, i.e. traditional HTTP POST-based file upload) doesn't work with
|
||||
# the websockets package's HTTP server at the moment.
|
||||
|
||||
FileUploadOperation <- setRefClass(
|
||||
FileUploadOperation <- R6Class(
|
||||
'FileUploadOperation',
|
||||
fields = list(
|
||||
.parent = 'ANY',
|
||||
.id = 'character',
|
||||
.files = 'data.frame',
|
||||
.dir = 'character',
|
||||
.currentFileInfo = 'list',
|
||||
.currentFileData = 'ANY',
|
||||
.pendingFileInfos = 'list'
|
||||
),
|
||||
methods = list(
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.parent = NULL,
|
||||
.id = character(0),
|
||||
.files = data.frame(),
|
||||
.dir = character(0),
|
||||
.currentFileInfo = list(),
|
||||
.currentFileData = NULL,
|
||||
.pendingFileInfos = list(),
|
||||
|
||||
initialize = function(parent, id, dir, fileInfos) {
|
||||
.parent <<- parent
|
||||
.id <<- id
|
||||
@@ -78,33 +79,49 @@ FileUploadOperation <- setRefClass(
|
||||
)
|
||||
|
||||
#' @include map.R
|
||||
FileUploadContext <- setRefClass(
|
||||
FileUploadContext <- R6Class(
|
||||
'FileUploadContext',
|
||||
fields = list(
|
||||
.basedir = 'character',
|
||||
.operations = 'Map'
|
||||
class = FALSE,
|
||||
private = list(
|
||||
basedir = character(0),
|
||||
operations = 'Map',
|
||||
ids = character(0) # Keep track of all ids used for file uploads
|
||||
),
|
||||
methods = list(
|
||||
public = list(
|
||||
initialize = function(dir=tempdir()) {
|
||||
.basedir <<- dir
|
||||
private$basedir <- dir
|
||||
private$operations <- Map$new()
|
||||
},
|
||||
createUploadOperation = function(fileInfos) {
|
||||
while (TRUE) {
|
||||
id <- paste(as.raw(p_runif(12, min=0, max=0xFF)), collapse='')
|
||||
dir <- file.path(.basedir, id)
|
||||
private$ids <- c(private$ids, id)
|
||||
dir <- file.path(private$basedir, id)
|
||||
if (!dir.create(dir))
|
||||
next
|
||||
|
||||
op <- FileUploadOperation$new(.self, id, dir, fileInfos)
|
||||
.operations$set(id, op)
|
||||
op <- FileUploadOperation$new(self, id, dir, fileInfos)
|
||||
private$operations$set(id, op)
|
||||
return(id)
|
||||
}
|
||||
},
|
||||
getUploadOperation = function(jobId) {
|
||||
.operations$get(jobId)
|
||||
private$operations$get(jobId)
|
||||
},
|
||||
onJobFinished = function(jobId) {
|
||||
.operations$remove(jobId)
|
||||
private$operations$remove(jobId)
|
||||
},
|
||||
# Remove the directories containing file uploads; this is to be called when
|
||||
# a session ends.
|
||||
rmUploadDirs = function() {
|
||||
# Make sure all_paths is underneath the tempdir()
|
||||
if (!grepl(normalizePath(tempdir()), normalizePath(private$basedir), fixed = TRUE)) {
|
||||
stop("Won't remove upload path ", private$basedir,
|
||||
"because it is not under tempdir(): ", tempdir())
|
||||
}
|
||||
|
||||
all_paths <- file.path(private$basedir, private$ids)
|
||||
unlink(all_paths, recursive = TRUE)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
14
R/globals.R
14
R/globals.R
@@ -7,3 +7,17 @@
|
||||
# the private seed during load.
|
||||
withPrivateSeed(reinitializeSeed())
|
||||
}
|
||||
|
||||
.onAttach <- function(libname, pkgname) {
|
||||
# Check for htmlwidgets version, if installed. As of Shiny 0.12.0 and
|
||||
# htmlwidgets 0.4, both packages switched from RJSONIO to jsonlite. Because of
|
||||
# this change, Shiny 0.12.0 will work only with htmlwidgets >= 0.4, and vice
|
||||
# versa.
|
||||
if (system.file(package = "htmlwidgets") != "" &&
|
||||
utils::packageVersion("htmlwidgets") < "0.4") {
|
||||
packageStartupMessage(
|
||||
"This version of Shiny is designed to work with htmlwidgets >= 0.4. ",
|
||||
"Please upgrade your version of htmlwidgets."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
39
R/graph.R
39
R/graph.R
@@ -1,5 +1,11 @@
|
||||
writeReactLog <- function(file=stdout()) {
|
||||
cat(RJSONIO::toJSON(.graphEnv$log, pretty=TRUE), file=file)
|
||||
writeReactLog <- function(file=stdout(), sessionToken = NULL) {
|
||||
log <- .graphStack$as_list()
|
||||
if (!is.null(sessionToken)) {
|
||||
log <- Filter(function(x) {
|
||||
is.null(x$session) || identical(x$session, sessionToken)
|
||||
}, log)
|
||||
}
|
||||
cat(toJSON(log, pretty=TRUE), file=file)
|
||||
}
|
||||
|
||||
#' Reactive Log Visualizer
|
||||
@@ -35,28 +41,37 @@ writeReactLog <- function(file=stdout()) {
|
||||
#' enabled, it's possible for any user of your app to see at least some
|
||||
#' of the source code of your reactive expressions and observers.
|
||||
#'
|
||||
#' @param time A boolean that specifies whether or not to display the
|
||||
#' time that each reactive.
|
||||
#'
|
||||
#' @export
|
||||
showReactLog <- function() {
|
||||
browseURL(renderReactLog())
|
||||
showReactLog <- function(time = TRUE) {
|
||||
utils::browseURL(renderReactLog(time = as.logical(time)))
|
||||
}
|
||||
|
||||
renderReactLog <- function() {
|
||||
renderReactLog <- function(sessionToken = NULL, time = TRUE) {
|
||||
templateFile <- system.file('www/reactive-graph.html', package='shiny')
|
||||
html <- paste(readLines(templateFile, warn=FALSE), collapse='\r\n')
|
||||
tc <- textConnection(NULL, 'w')
|
||||
on.exit(close(tc))
|
||||
writeReactLog(tc)
|
||||
writeReactLog(tc, sessionToken)
|
||||
cat('\n', file=tc)
|
||||
flush(tc)
|
||||
html <- sub('__DATA__', paste(textConnectionValue(tc), collapse='\r\n'), html, fixed=TRUE)
|
||||
html <- sub('__TIME__', paste0('"', time, '"'), html, fixed=TRUE)
|
||||
file <- tempfile(fileext = '.html')
|
||||
writeLines(html, file)
|
||||
return(file)
|
||||
}
|
||||
|
||||
.graphAppend <- function(logEntry, domain = getDefaultReactiveDomain()) {
|
||||
if (isTRUE(getOption('shiny.reactlog')))
|
||||
.graphEnv$log <- c(.graphEnv$log, list(logEntry))
|
||||
if (isTRUE(getOption('shiny.reactlog'))) {
|
||||
sessionToken <- if (is.null(domain)) NULL else domain$token
|
||||
.graphStack$push(c(logEntry, list(
|
||||
session = sessionToken,
|
||||
time = as.numeric(Sys.time())
|
||||
)))
|
||||
}
|
||||
|
||||
if (!is.null(domain)) {
|
||||
domain$reactlog(logEntry)
|
||||
@@ -74,7 +89,7 @@ renderReactLog <- function() {
|
||||
.graphCreateContext <- function(id, label, type, prevId, domain) {
|
||||
.graphAppend(list(
|
||||
action='ctx', id=id, label=paste(label, collapse='\n'),
|
||||
srcref=attr(label, "srcref"), srcfile=attr(label, "srcfile"),
|
||||
srcref=as.vector(attr(label, "srcref")), srcfile=attr(label, "srcfile"),
|
||||
type=type, prevId=prevId
|
||||
), domain = domain)
|
||||
}
|
||||
@@ -91,7 +106,7 @@ renderReactLog <- function() {
|
||||
.graphAppend(list(
|
||||
action = 'valueChange',
|
||||
id = label,
|
||||
value = paste(capture.output(str(value)), collapse='\n')
|
||||
value = paste(utils::capture.output(utils::str(value)), collapse='\n')
|
||||
))
|
||||
}
|
||||
|
||||
@@ -99,5 +114,5 @@ renderReactLog <- function() {
|
||||
.graphAppend(list(action='invalidate', id=id), domain)
|
||||
}
|
||||
|
||||
.graphEnv <- new.env()
|
||||
.graphEnv$log <- list()
|
||||
#' @include stack.R
|
||||
.graphStack <- Stack$new()
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
#' Create a web dependency
|
||||
#'
|
||||
#' Ensure that a file-based HTML dependency (from the htmltools package) can be
|
||||
#' served over Shiny's HTTP server. This function works by using
|
||||
#' \code{\link{addResourcePath}} to map the HTML dependency's directory to a
|
||||
#' URL.
|
||||
#'
|
||||
#' @param dependency A single HTML dependency object, created using
|
||||
#' \code{\link{htmlDependency}}. If the \code{src} value is named, then
|
||||
#' \code{href} and/or \code{file} names must be present.
|
||||
#'
|
||||
#' @return A single HTML dependency object that has an \code{href}-named element
|
||||
#' in its \code{src}.
|
||||
#' @export
|
||||
createWebDependency <- function(dependency) {
|
||||
if (is.null(dependency))
|
||||
return(NULL)
|
||||
@@ -13,3 +27,21 @@ createWebDependency <- function(dependency) {
|
||||
|
||||
return(dependency)
|
||||
}
|
||||
|
||||
|
||||
# Given a Shiny tag object, process singletons and dependencies. Returns a list
|
||||
# with rendered HTML and dependency objects.
|
||||
processDeps <- function(tags, session) {
|
||||
ui <- takeSingletons(tags, session$singletons, desingleton=FALSE)$ui
|
||||
ui <- surroundSingletons(ui)
|
||||
dependencies <- lapply(
|
||||
resolveDependencies(findDependencies(ui)),
|
||||
createWebDependency
|
||||
)
|
||||
names(dependencies) <- NULL
|
||||
|
||||
list(
|
||||
html = doRenderTags(ui),
|
||||
deps = dependencies
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
#' @export tag tagAppendAttributes tagAppendChild tagAppendChildren tagList tags tagSetChildren withTags
|
||||
#' @export validateCssUnit
|
||||
#' @export knit_print.html knit_print.shiny.tag knit_print.shiny.tag.list
|
||||
#' @export htmlTemplate suppressDependencies
|
||||
NULL
|
||||
|
||||
139
R/image-interact-opts.R
Normal file
139
R/image-interact-opts.R
Normal file
@@ -0,0 +1,139 @@
|
||||
#' Create an object representing click options
|
||||
#'
|
||||
#' This generates an object representing click options, to be passed as the
|
||||
#' \code{click} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_click"},
|
||||
#' then the click coordinates will be available as \code{input$plot_click}.
|
||||
#' @param clip Should the click area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive click events even when the mouse is outside
|
||||
#' the plotting area, as long as it is still inside the image.
|
||||
#' @export
|
||||
clickOpts <- function(id = NULL, clip = TRUE) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
clip = clip
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#' Create an object representing double-click options
|
||||
#'
|
||||
#' This generates an object representing dobule-click options, to be passed as
|
||||
#' the \code{dblclick} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is
|
||||
#' \code{"plot_dblclick"}, then the click coordinates will be available as
|
||||
#' \code{input$plot_dblclick}.
|
||||
#' @param clip Should the click area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive double-click events even when the mouse is
|
||||
#' outside the plotting area, as long as it is still inside the image.
|
||||
#' @param delay Maximum delay (in ms) between a pair clicks for them to be
|
||||
#' counted as a double-click.
|
||||
#' @export
|
||||
dblclickOpts <- function(id = NULL, clip = TRUE, delay = 400) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
clip = clip,
|
||||
delay = delay
|
||||
)
|
||||
}
|
||||
|
||||
#' Create an object representing hover options
|
||||
#'
|
||||
#' This generates an object representing hovering options, to be passed as the
|
||||
#' \code{hover} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_hover"},
|
||||
#' then the hover coordinates will be available as \code{input$plot_hover}.
|
||||
#' @param delay How long to delay (in milliseconds) when debouncing or
|
||||
#' throttling, before sending the mouse location to the server.
|
||||
#' @param delayType The type of algorithm for limiting the number of hover
|
||||
#' events. Use \code{"throttle"} to limit the number of hover events to one
|
||||
#' every \code{delay} milliseconds. Use \code{"debounce"} to suspend events
|
||||
#' while the cursor is moving, and wait until the cursor has been at rest for
|
||||
#' \code{delay} milliseconds before sending an event.
|
||||
#' @param clip Should the hover area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive hover events even when the mouse is outside
|
||||
#' the plotting area, as long as it is still inside the image.
|
||||
#' @param nullOutside If \code{TRUE} (the default), the value will be set to
|
||||
#' \code{NULL} when the mouse exits the plotting area. If \code{FALSE}, the
|
||||
#' value will stop changing when the cursor exits the plotting area.
|
||||
#' @export
|
||||
hoverOpts <- function(id = NULL, delay = 300,
|
||||
delayType = c("debounce", "throttle"), clip = TRUE,
|
||||
nullOutside = TRUE) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
delay = delay,
|
||||
delayType = match.arg(delayType),
|
||||
clip = clip,
|
||||
nullOutside = nullOutside
|
||||
)
|
||||
}
|
||||
|
||||
#' Create an object representing brushing options
|
||||
#'
|
||||
#' This generates an object representing brushing options, to be passed as the
|
||||
#' \code{brush} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_brush"},
|
||||
#' then the coordinates will be available as \code{input$plot_brush}. Multiple
|
||||
#' \code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
|
||||
#' value; brushing one image or plot will cause any other brushes with the
|
||||
#' same \code{id} to disappear.
|
||||
#' @param fill Fill color of the brush.
|
||||
#' @param stroke Outline color of the brush.
|
||||
#' @param opacity Opacity of the brush
|
||||
#' @param delay How long to delay (in milliseconds) when debouncing or
|
||||
#' throttling, before sending the brush data to the server.
|
||||
#' @param delayType The type of algorithm for limiting the number of brush
|
||||
#' events. Use \code{"throttle"} to limit the number of brush events to one
|
||||
#' every \code{delay} milliseconds. Use \code{"debounce"} to suspend events
|
||||
#' while the cursor is moving, and wait until the cursor has been at rest for
|
||||
#' \code{delay} milliseconds before sending an event.
|
||||
#' @param clip Should the brush area be clipped to the plotting area? If FALSE,
|
||||
#' then the user will be able to brush outside the plotting area, as long as
|
||||
#' it is still inside the image.
|
||||
#' @param direction The direction for brushing. If \code{"xy"}, the brush can be
|
||||
#' drawn and moved in both x and y directions. If \code{"x"}, or \code{"y"},
|
||||
#' the brush wil work horizontally or vertically.
|
||||
#' @param resetOnNew When a new image is sent to the browser (via
|
||||
#' \code{\link{renderImage}}), should the brush be reset? The default,
|
||||
#' \code{FALSE}, is useful if you want to update the plot while keeping the
|
||||
#' brush. Using \code{TRUE} is useful if you want to clear the brush whenever
|
||||
#' the plot is updated.
|
||||
#' @export
|
||||
brushOpts <- function(id = NULL, fill = "#9cf", stroke = "#036",
|
||||
opacity = 0.25, delay = 300,
|
||||
delayType = c("debounce", "throttle"), clip = TRUE,
|
||||
direction = c("xy", "x", "y"),
|
||||
resetOnNew = FALSE) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
fill = fill,
|
||||
stroke = stroke,
|
||||
opacity = opacity,
|
||||
delay = delay,
|
||||
delayType = match.arg(delayType),
|
||||
clip = clip,
|
||||
direction = match.arg(direction),
|
||||
resetOnNew = resetOnNew
|
||||
)
|
||||
}
|
||||
437
R/image-interact.R
Normal file
437
R/image-interact.R
Normal file
@@ -0,0 +1,437 @@
|
||||
#' Find rows of data that are selected by a brush
|
||||
#'
|
||||
#' This function returns rows from a data frame which are under a brush used
|
||||
#' with \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' It is also possible for this function to return all rows from the input data
|
||||
#' frame, but with an additional column \code{selected_}, which indicates which
|
||||
#' rows of the input data frame are selected by the brush (\code{TRUE} for
|
||||
#' selected, \code{FALSE} for not-selected). This is enabled by setting
|
||||
#' \code{allRows=TRUE} option.
|
||||
#'
|
||||
#' The \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2}
|
||||
#' arguments specify which columns in the data correspond to the x variable, y
|
||||
#' variable, and panel variables of the plot. For example, if your plot is
|
||||
#' \code{plot(x=cars$speed, y=cars$dist)}, and your brush is named
|
||||
#' \code{"cars_brush"}, then you would use \code{brushedPoints(cars,
|
||||
#' input$cars_brush, "speed", "dist")}.
|
||||
#'
|
||||
#' For plots created with ggplot2, it should not be necessary to specify the
|
||||
#' column names; that information will already be contained in the brush,
|
||||
#' provided that variables are in the original data, and not computed. For
|
||||
#' example, with \code{ggplot(cars, aes(x=speed, y=dist)) + geom_point()}, you
|
||||
#' could use \code{brushedPoints(cars, input$cars_brush)}. If, however, you use
|
||||
#' a computed column, like \code{ggplot(cars, aes(x=speed/2, y=dist)) +
|
||||
#' geom_point()}, then it will not be able to automatically extract column names
|
||||
#' and filter on them. If you want to use this function to filter data, it is
|
||||
#' recommended that you not use computed columns; instead, modify the data
|
||||
#' first, and then make the plot with "raw" columns in the modified data.
|
||||
#'
|
||||
#' If a specified x or y column is a factor, then it will be coerced to an
|
||||
#' integer vector. If it is a character vector, then it will be coerced to a
|
||||
#' factor and then integer vector. This means that the brush will be considered
|
||||
#' to cover a given character/factor value when it covers the center value.
|
||||
#'
|
||||
#' If the brush is operating in just the x or y directions (e.g., with
|
||||
#' \code{brushOpts(direction = "x")}, then this function will filter out points
|
||||
#' using just the x or y variable, whichever is appropriate.
|
||||
#'
|
||||
#' @param brush The data from a brush, such as \code{input$plot_brush}.
|
||||
#' @param df A data frame from which to select rows.
|
||||
#' @param xvar,yvar A string with the name of the variable on the x or y axis.
|
||||
#' This must also be the name of a column in \code{df}. If absent, then this
|
||||
#' function will try to infer the variable from the brush (only works for
|
||||
#' ggplot2).
|
||||
#' @param panelvar1,panelvar2 Each of these is a string with the name of a panel
|
||||
#' variable. For example, if with ggplot2, you facet on a variable called
|
||||
#' \code{cyl}, then you can use \code{"cyl"} here. However, specifying the
|
||||
#' panel variable should not be necessary with ggplot2; Shiny should be able
|
||||
#' to auto-detect the panel variable.
|
||||
#' @param allRows If \code{FALSE} (the default) return a data frame containing
|
||||
#' the selected rows. If \code{TRUE}, the input data frame will have a new
|
||||
#' column, \code{selected_}, which indicates whether the row was inside the
|
||||
#' brush (\code{TRUE}) or outside the brush (\code{FALSE}).
|
||||
#'
|
||||
#' @seealso \code{\link{plotOutput}} for example usage.
|
||||
#' @export
|
||||
brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
|
||||
panelvar1 = NULL, panelvar2 = NULL,
|
||||
allRows = FALSE) {
|
||||
if (is.null(brush)) {
|
||||
if (allRows)
|
||||
df$selected_ <- FALSE
|
||||
else
|
||||
df <- df[0, , drop = FALSE]
|
||||
|
||||
return(df)
|
||||
}
|
||||
|
||||
if (is.null(brush$xmin)) {
|
||||
stop("brushedPoints requires a brush object with xmin, xmax, ymin, and ymax.")
|
||||
}
|
||||
|
||||
# Which direction(s) the brush is selecting over. Direction can be 'x', 'y',
|
||||
# or 'xy'.
|
||||
use_x <- grepl("x", brush$direction)
|
||||
use_y <- grepl("y", brush$direction)
|
||||
|
||||
# Try to extract vars from brush object
|
||||
xvar <- xvar %OR% brush$mapping$x
|
||||
yvar <- yvar %OR% brush$mapping$y
|
||||
panelvar1 <- panelvar1 %OR% brush$mapping$panelvar1
|
||||
panelvar2 <- panelvar2 %OR% brush$mapping$panelvar2
|
||||
|
||||
# Filter out x and y values
|
||||
keep_rows <- rep(TRUE, nrow(df))
|
||||
if (use_x) {
|
||||
if (is.null(xvar))
|
||||
stop("brushedPoints: not able to automatically infer `xvar` from brush")
|
||||
# Extract data values from the data frame
|
||||
x <- asNumber(df[[xvar]])
|
||||
keep_rows <- keep_rows & (x >= brush$xmin & x <= brush$xmax)
|
||||
}
|
||||
if (use_y) {
|
||||
if (is.null(yvar))
|
||||
stop("brushedPoints: not able to automatically infer `yvar` from brush")
|
||||
y <- asNumber(df[[yvar]])
|
||||
keep_rows <- keep_rows & (y >= brush$ymin & y <= brush$ymax)
|
||||
}
|
||||
|
||||
# Find which rows are matches for the panel vars (if present)
|
||||
if (!is.null(panelvar1))
|
||||
keep_rows <- keep_rows & panelMatch(brush$panelvar1, df[[panelvar1]])
|
||||
if (!is.null(panelvar2))
|
||||
keep_rows <- keep_rows & panelMatch(brush$panelvar2, df[[panelvar2]])
|
||||
|
||||
if (allRows) {
|
||||
df$selected_ <- keep_rows
|
||||
df
|
||||
} else {
|
||||
df[keep_rows, , drop = FALSE]
|
||||
}
|
||||
}
|
||||
|
||||
# The `brush` data structure will look something like the examples below.
|
||||
# For base graphics, `mapping` is empty, and there are no panelvars:
|
||||
# List of 8
|
||||
# $ xmin : num 3.73
|
||||
# $ xmax : num 4.22
|
||||
# $ ymin : num 13.9
|
||||
# $ ymax : num 19.8
|
||||
# $ mapping: Named list()
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.36
|
||||
# ..$ right : num 5.58
|
||||
# ..$ bottom: num 9.46
|
||||
# ..$ top : num 34.8
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 58
|
||||
# ..$ right : num 429
|
||||
# ..$ bottom: num 226
|
||||
# ..$ top : num 58
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ direction: chr "y"
|
||||
#
|
||||
# For ggplot2, the mapping vars usually will be included, and if faceting is
|
||||
# used, they will be listed as panelvars:
|
||||
# List of 10
|
||||
# $ xmin : num 3.18
|
||||
# $ xmax : num 3.78
|
||||
# $ ymin : num 17.1
|
||||
# $ ymax : num 20.4
|
||||
# $ panelvar1: int 6
|
||||
# $ panelvar2: int 0
|
||||
# $ mapping :List of 4
|
||||
# ..$ x : chr "wt"
|
||||
# ..$ y : chr "mpg"
|
||||
# ..$ panelvar1: chr "cyl"
|
||||
# ..$ panelvar2: chr "am"
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.32
|
||||
# ..$ right : num 5.62
|
||||
# ..$ bottom: num 9.22
|
||||
# ..$ top : num 35.1
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 172
|
||||
# ..$ right : num 300
|
||||
# ..$ bottom: num 144
|
||||
# ..$ top : num 28.5
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ direction: chr "y"
|
||||
|
||||
|
||||
#'Find rows of data that are near a click/hover/double-click
|
||||
#'
|
||||
#'This function returns rows from a data frame which are near a click, hover, or
|
||||
#'double-click, when used with \code{\link{plotOutput}}. The rows will be sorted
|
||||
#'by their distance to the mouse event.
|
||||
#'
|
||||
#'It is also possible for this function to return all rows from the input data
|
||||
#'frame, but with an additional column \code{selected_}, which indicates which
|
||||
#'rows of the input data frame are selected by the brush (\code{TRUE} for
|
||||
#'selected, \code{FALSE} for not-selected). This is enabled by setting
|
||||
#'\code{allRows=TRUE} option. If this is used, the resulting data frame will not
|
||||
#'be sorted by distance to the mouse event.
|
||||
#'
|
||||
#'The \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2} arguments
|
||||
#'specify which columns in the data correspond to the x variable, y variable,
|
||||
#'and panel variables of the plot. For example, if your plot is
|
||||
#'\code{plot(x=cars$speed, y=cars$dist)}, and your click variable is named
|
||||
#'\code{"cars_click"}, then you would use \code{nearPoints(cars,
|
||||
#'input$cars_brush, "speed", "dist")}.
|
||||
#'
|
||||
#'@inheritParams brushedPoints
|
||||
#'@param coordinfo The data from a mouse event, such as \code{input$plot_click}.
|
||||
#'@param threshold A maxmimum distance to the click point; rows in the data
|
||||
#' frame where the distance to the click is less than \code{threshold} will be
|
||||
#' returned.
|
||||
#'@param maxpoints Maximum number of rows to return. If NULL (the default),
|
||||
#' return all rows that are within the threshold distance.
|
||||
#'@param addDist If TRUE, add a column named \code{dist_} that contains the
|
||||
#' distance from the coordinate to the point, in pixels. When no mouse event
|
||||
#' has yet occured, the value of \code{dist_} will be \code{NA}.
|
||||
#'@param allRows If \code{FALSE} (the default) return a data frame containing
|
||||
#' the selected rows. If \code{TRUE}, the input data frame will have a new
|
||||
#' column, \code{selected_}, which indicates whether the row was inside the
|
||||
#' selected by the mouse event (\code{TRUE}) or not (\code{FALSE}).
|
||||
#'
|
||||
#'@seealso \code{\link{plotOutput}} for more examples.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Note that in practice, these examples would need to go in reactives
|
||||
#' # or observers.
|
||||
#'
|
||||
#' # This would select all points within 5 pixels of the click
|
||||
#' nearPoints(mtcars, input$plot_click)
|
||||
#'
|
||||
#' # Select just the nearest point within 10 pixels of the click
|
||||
#' nearPoints(mtcars, input$plot_click, threshold = 10, maxpoints = 1)
|
||||
#'
|
||||
#' }
|
||||
#'@export
|
||||
nearPoints <- function(df, coordinfo, xvar = NULL, yvar = NULL,
|
||||
panelvar1 = NULL, panelvar2 = NULL,
|
||||
threshold = 5, maxpoints = NULL, addDist = FALSE,
|
||||
allRows = FALSE) {
|
||||
if (is.null(coordinfo)) {
|
||||
if (addDist)
|
||||
df$dist_ <- NA_real_
|
||||
|
||||
if (allRows)
|
||||
df$selected_ <- FALSE
|
||||
else
|
||||
df <- df[0, , drop = FALSE]
|
||||
|
||||
return(df)
|
||||
}
|
||||
|
||||
if (is.null(coordinfo$x)) {
|
||||
stop("nearPoints requires a click/hover/double-click object with x and y values.")
|
||||
}
|
||||
|
||||
# Try to extract vars from coordinfo object
|
||||
xvar <- xvar %OR% coordinfo$mapping$x
|
||||
yvar <- yvar %OR% coordinfo$mapping$y
|
||||
panelvar1 <- panelvar1 %OR% coordinfo$mapping$panelvar1
|
||||
panelvar2 <- panelvar2 %OR% coordinfo$mapping$panelvar2
|
||||
|
||||
if (is.null(xvar))
|
||||
stop("nearPoints: not able to automatically infer `xvar` from coordinfo")
|
||||
if (is.null(yvar))
|
||||
stop("nearPoints: not able to automatically infer `yvar` from coordinfo")
|
||||
|
||||
# Extract data values from the data frame
|
||||
x <- asNumber(df[[xvar]])
|
||||
y <- asNumber(df[[yvar]])
|
||||
|
||||
# Get the pixel coordinates of the point
|
||||
coordPx <- scaleCoords(coordinfo$x, coordinfo$y, coordinfo)
|
||||
|
||||
# Get pixel coordinates of data points
|
||||
dataPx <- scaleCoords(x, y, coordinfo)
|
||||
|
||||
# Distances of data points to coordPx
|
||||
dists <- sqrt((dataPx$x - coordPx$x) ^ 2 + (dataPx$y - coordPx$y) ^ 2)
|
||||
|
||||
if (addDist)
|
||||
df$dist_ <- dists
|
||||
|
||||
keep_rows <- (dists <= threshold)
|
||||
|
||||
# Find which rows are matches for the panel vars (if present)
|
||||
if (!is.null(panelvar1))
|
||||
keep_rows <- keep_rows & panelMatch(coordinfo$panelvar1, df[[panelvar1]])
|
||||
if (!is.null(panelvar2))
|
||||
keep_rows <- keep_rows & panelMatch(coordinfo$panelvar2, df[[panelvar2]])
|
||||
|
||||
# Track the indices to keep
|
||||
keep_idx <- which(keep_rows)
|
||||
|
||||
# Order by distance
|
||||
dists <- dists[keep_idx]
|
||||
keep_idx <- keep_idx[order(dists)]
|
||||
|
||||
# Keep max number of rows
|
||||
if (!is.null(maxpoints) && length(keep_idx) > maxpoints) {
|
||||
keep_idx <- keep_idx[seq_len(maxpoints)]
|
||||
}
|
||||
|
||||
if (allRows) {
|
||||
# Add selected_ column if needed
|
||||
df$selected_ <- FALSE
|
||||
df$selected_[keep_idx] <- TRUE
|
||||
|
||||
} else {
|
||||
# If we don't keep all rows, return just the selected rows, sorted by
|
||||
# distance.
|
||||
df <- df[keep_idx, , drop = FALSE]
|
||||
}
|
||||
|
||||
df
|
||||
}
|
||||
|
||||
# The coordinfo data structure will look something like the examples below.
|
||||
# For base graphics, `mapping` is empty, and there are no panelvars:
|
||||
# List of 7
|
||||
# $ x : num 4.37
|
||||
# $ y : num 12
|
||||
# $ mapping: Named list()
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.36
|
||||
# ..$ right : num 5.58
|
||||
# ..$ bottom: num 9.46
|
||||
# ..$ top : num 34.8
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 58
|
||||
# ..$ right : num 429
|
||||
# ..$ bottom: num 226
|
||||
# ..$ top : num 58
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ .nonce : num 0.343
|
||||
#
|
||||
# For ggplot2, the mapping vars usually will be included, and if faceting is
|
||||
# used, they will be listed as panelvars:
|
||||
# List of 9
|
||||
# $ x : num 3.78
|
||||
# $ y : num 17.1
|
||||
# $ panelvar1: int 6
|
||||
# $ panelvar2: int 0
|
||||
# $ mapping :List of 4
|
||||
# ..$ x : chr "wt"
|
||||
# ..$ y : chr "mpg"
|
||||
# ..$ panelvar1: chr "cyl"
|
||||
# ..$ panelvar2: chr "am"
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.32
|
||||
# ..$ right : num 5.62
|
||||
# ..$ bottom: num 9.22
|
||||
# ..$ top : num 35.1
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 172
|
||||
# ..$ right : num 300
|
||||
# ..$ bottom: num 144
|
||||
# ..$ top : num 28.5
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ .nonce : num 0.603
|
||||
|
||||
|
||||
|
||||
# Coerce various types of variables to numbers. This works for Date, POSIXt,
|
||||
# characters, and factors. Used because the mouse coords are numeric.
|
||||
asNumber <- function(x) {
|
||||
if (is.character(x)) x <- as.factor(x)
|
||||
if (is.factor(x)) x <- as.integer(x)
|
||||
as.numeric(x)
|
||||
}
|
||||
|
||||
# Given a panelvar value and a vector x, return logical vector indicating which
|
||||
# items match the panelvar value. Because the panelvar value is always a
|
||||
# string but the vector could be numeric, it might be necessary to coerce the
|
||||
# panelvar to a number before comparing to the vector.
|
||||
panelMatch <- function(search_value, x) {
|
||||
if (is.numeric(x)) search_value <- as.numeric(search_value)
|
||||
x == search_value
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Scaling functions
|
||||
# These functions have direct analogs in Javascript code, except these are
|
||||
# vectorized for x and y.
|
||||
|
||||
# Map a value x from a domain to a range. If clip is true, clip it to the
|
||||
# range.
|
||||
mapLinear <- function(x, domainMin, domainMax, rangeMin, rangeMax, clip = TRUE) {
|
||||
factor <- (rangeMax - rangeMin) / (domainMax - domainMin)
|
||||
val <- x - domainMin
|
||||
newval <- (val * factor) + rangeMin
|
||||
|
||||
if (clip) {
|
||||
maxval <- max(rangeMax, rangeMin)
|
||||
minval <- min(rangeMax, rangeMin)
|
||||
newval[newval > maxval] <- maxval
|
||||
newval[newval < minval] <- minval
|
||||
}
|
||||
newval
|
||||
}
|
||||
|
||||
# Scale val from domain to range. If logbase is present, use log scaling.
|
||||
scale1D <- function(val, domainMin, domainMax, rangeMin, rangeMax,
|
||||
logbase = NULL, clip = TRUE) {
|
||||
if (!is.null(logbase))
|
||||
val <- log(val, logbase)
|
||||
mapLinear(val, domainMin, domainMax, rangeMin, rangeMax, clip)
|
||||
}
|
||||
|
||||
# Inverse scale val, from range to domain. If logbase is present, use inverse
|
||||
# log (power) transformation.
|
||||
scaleInv1D <- function(val, domainMin, domainMax, rangeMin, rangeMax,
|
||||
logbase = NULL, clip = TRUE) {
|
||||
res <- mapLinear(val, rangeMin, rangeMax, domainMin, domainMax, clip)
|
||||
if (!is.null(logbase))
|
||||
res <- logbase ^ res
|
||||
res
|
||||
}
|
||||
|
||||
# Scale x and y coordinates from domain to range, using information in
|
||||
# scaleinfo. scaleinfo must contain items $domain, $range, and $log. The
|
||||
# scaleinfo object corresponds to one element from the coordmap object generated
|
||||
# by getPrevPlotCoordmap or getGgplotCoordmap; it is the scaling information for
|
||||
# one panel in a plot.
|
||||
scaleCoords <- function(x, y, scaleinfo) {
|
||||
if (is.null(scaleinfo))
|
||||
return(NULL)
|
||||
|
||||
domain <- scaleinfo$domain
|
||||
range <- scaleinfo$range
|
||||
log <- scaleinfo$log
|
||||
|
||||
list(
|
||||
x = scale1D(x, domain$left, domain$right, range$left, range$right, log$x),
|
||||
y = scale1D(y, domain$bottom, domain$top, range$bottom, range$top, log$y)
|
||||
)
|
||||
}
|
||||
|
||||
# Inverse scale x and y coordinates from range to domain, using information in
|
||||
# scaleinfo.
|
||||
scaleInvCoords <- function(x, y, scaleinfo) {
|
||||
if (is.null(scaleinfo))
|
||||
return(NULL)
|
||||
|
||||
domain <- scaleinfo$domain
|
||||
range <- scaleinfo$range
|
||||
log <- scaleinfo$log
|
||||
|
||||
list(
|
||||
x = scaleInv1D(x, domain$left, domain$right, range$left, range$right, log$x),
|
||||
y = scaleInv1D(y, domain$bottom, domain$top, range$bottom, range$top, log$y)
|
||||
)
|
||||
}
|
||||
@@ -33,12 +33,12 @@ plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
||||
# Otherwise, if the Cairo package is installed, use CairoPNG().
|
||||
# Finally, if neither quartz nor Cairo, use png().
|
||||
if (capabilities("aqua")) {
|
||||
pngfun <- png
|
||||
pngfun <- grDevices::png
|
||||
} else if ((getOption('shiny.usecairo') %OR% TRUE) &&
|
||||
nchar(system.file(package = "Cairo"))) {
|
||||
pngfun <- Cairo::CairoPNG
|
||||
} else {
|
||||
pngfun <- png
|
||||
pngfun <- grDevices::png
|
||||
}
|
||||
|
||||
pngfun(filename=filename, width=width, height=height, res=res, ...)
|
||||
@@ -49,10 +49,15 @@ plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
||||
# by plot.new() with the default (large) margin. However, this does not
|
||||
# guarantee user's code in func() will not trigger the error -- they may have
|
||||
# to set par(mar = smaller_value) before they draw base graphics.
|
||||
op <- par(mar = rep(0, 4))
|
||||
tryCatch(plot.new(), finally = par(op))
|
||||
dv <- dev.cur()
|
||||
tryCatch(shinyCallingHandlers(func()), finally = dev.off(dv))
|
||||
op <- graphics::par(mar = rep(0, 4))
|
||||
tryCatch(
|
||||
graphics::plot.new(),
|
||||
finally = graphics::par(op)
|
||||
)
|
||||
|
||||
dv <- grDevices::dev.cur()
|
||||
on.exit(grDevices::dev.off(dv), add = TRUE)
|
||||
func()
|
||||
|
||||
filename
|
||||
}
|
||||
|
||||
80
R/input-action.R
Normal file
80
R/input-action.R
Normal file
@@ -0,0 +1,80 @@
|
||||
#' Action button/link
|
||||
#'
|
||||
#' Creates an action button or link whose value is initially zero, and increments by one
|
||||
#' each time it is pressed.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param label The contents of the button or link--usually a text label, but
|
||||
#' you could also use any other HTML, like an image.
|
||||
#' @param icon An optional \code{\link{icon}} to appear on the button.
|
||||
#' @param ... Named attributes to be applied to the button or link.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("obs", "Number of observations", 0, 1000, 500),
|
||||
#' actionButton("goButton", "Go!"),
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' output$distPlot <- renderPlot({
|
||||
#' # Take a dependency on input$goButton. This will run once initially,
|
||||
#' # because the value changes from NULL to 0.
|
||||
#' input$goButton
|
||||
#'
|
||||
#' # Use isolate() to avoid dependency on input$obs
|
||||
#' dist <- isolate(rnorm(input$obs))
|
||||
#' hist(dist)
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#' }
|
||||
#'
|
||||
#' @seealso \code{\link{observeEvent}} and \code{\link{eventReactive}}
|
||||
#'
|
||||
#' @export
|
||||
actionButton <- function(inputId, label, icon = NULL, width = NULL, ...) {
|
||||
tags$button(id=inputId,
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
type="button",
|
||||
class="btn btn-default action-button",
|
||||
list(validateIcon(icon), label),
|
||||
...
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname actionButton
|
||||
#' @export
|
||||
actionLink <- function(inputId, label, icon = NULL, ...) {
|
||||
tags$a(id=inputId,
|
||||
href="#",
|
||||
class="action-button",
|
||||
list(validateIcon(icon), label),
|
||||
...
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
# Check that the icon parameter is valid:
|
||||
# 1) Check if the user wants to actually add an icon:
|
||||
# -- if icon=NULL, it means leave the icon unchanged
|
||||
# -- if icon=character(0), it means don't add an icon or, more usefully,
|
||||
# remove the previous icon
|
||||
# 2) If so, check that the icon has the right format (this does not check whether
|
||||
# it is a *real* icon - currently that would require a massive cross reference
|
||||
# with the "font-awesome" and the "glyphicon" libraries)
|
||||
validateIcon <- function(icon) {
|
||||
if (is.null(icon) || identical(icon, character(0))) {
|
||||
return(icon)
|
||||
} else if (inherits(icon, "shiny.tag") && icon$name == "i") {
|
||||
return(icon)
|
||||
} else {
|
||||
stop("Invalid icon. Use Shiny's 'icon()' function to generate a valid icon")
|
||||
}
|
||||
}
|
||||
37
R/input-checkbox.R
Normal file
37
R/input-checkbox.R
Normal file
@@ -0,0 +1,37 @@
|
||||
#' Checkbox Input Control
|
||||
#'
|
||||
#' Create a checkbox that can be used to specify logical values.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param value Initial value (\code{TRUE} or \code{FALSE}).
|
||||
#' @return A checkbox control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{checkboxGroupInput}}, \code{\link{updateCheckboxInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' checkboxInput("somevalue", "Some value", FALSE),
|
||||
#' verbatimTextOutput("value")
|
||||
#' )
|
||||
#' server <- function(input, output) {
|
||||
#' output$value <- renderText({ input$somevalue })
|
||||
#' }
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
checkboxInput <- function(inputId, label, value = FALSE, width = NULL) {
|
||||
inputTag <- tags$input(id = inputId, type="checkbox")
|
||||
if (!is.null(value) && value)
|
||||
inputTag$attribs$checked <- "checked"
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
div(class = "checkbox",
|
||||
tags$label(inputTag, tags$span(label))
|
||||
)
|
||||
)
|
||||
}
|
||||
59
R/input-checkboxgroup.R
Normal file
59
R/input-checkboxgroup.R
Normal file
@@ -0,0 +1,59 @@
|
||||
#' Checkbox Group Input Control
|
||||
#'
|
||||
#' Create a group of checkboxes that can be used to toggle multiple choices
|
||||
#' independently. The server will receive the input as a character vector of the
|
||||
#' selected values.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param choices List of values to show checkboxes for. If elements of the list
|
||||
#' are named then that name rather than the value is displayed to the user.
|
||||
#' @param selected The values that should be initially selected, if any.
|
||||
#' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
|
||||
#' @return A list of HTML elements that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{checkboxInput}}, \code{\link{updateCheckboxGroupInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' checkboxGroupInput("variable", "Variables to show:",
|
||||
#' c("Cylinders" = "cyl",
|
||||
#' "Transmission" = "am",
|
||||
#' "Gears" = "gear")),
|
||||
#' tableOutput("data")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' output$data <- renderTable({
|
||||
#' mtcars[, c("mpg", input$variable), drop = FALSE]
|
||||
#' }, rownames = TRUE)
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
checkboxGroupInput <- function(inputId, label, choices, selected = NULL,
|
||||
inline = FALSE, width = NULL) {
|
||||
|
||||
# resolve names
|
||||
choices <- choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
|
||||
options <- generateOptions(inputId, choices, selected, inline)
|
||||
|
||||
divClass <- "form-group shiny-input-checkboxgroup shiny-input-container"
|
||||
if (inline)
|
||||
divClass <- paste(divClass, "shiny-input-container-inline")
|
||||
|
||||
# return label and select tag
|
||||
tags$div(id = inputId,
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
class = divClass,
|
||||
controlLabel(inputId, label),
|
||||
options
|
||||
)
|
||||
}
|
||||
109
R/input-date.R
Normal file
109
R/input-date.R
Normal file
@@ -0,0 +1,109 @@
|
||||
#' Create date input
|
||||
#'
|
||||
#' Creates a text input which, when clicked on, brings up a calendar that
|
||||
#' the user can click on to select dates.
|
||||
#'
|
||||
#' The date \code{format} string specifies how the date will be displayed in
|
||||
#' the browser. It allows the following values:
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \item \code{yy} Year without century (12)
|
||||
#' \item \code{yyyy} Year with century (2012)
|
||||
#' \item \code{mm} Month number, with leading zero (01-12)
|
||||
#' \item \code{m} Month number, without leading zero (01-12)
|
||||
#' \item \code{M} Abbreviated month name
|
||||
#' \item \code{MM} Full month name
|
||||
#' \item \code{dd} Day of month with leading zero
|
||||
#' \item \code{d} Day of month without leading zero
|
||||
#' \item \code{D} Abbreviated weekday name
|
||||
#' \item \code{DD} Full weekday name
|
||||
#' }
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param value The starting date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
|
||||
#' date in the client's time zone.
|
||||
#' @param min The minimum allowed date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @param max The maximum allowed date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @param format The format of the date to display in the browser. Defaults to
|
||||
#' \code{"yyyy-mm-dd"}.
|
||||
#' @param startview The date range shown when the input object is first
|
||||
#' clicked. Can be "month" (the default), "year", or "decade".
|
||||
#' @param weekstart Which day is the start of the week. Should be an integer
|
||||
#' from 0 (Sunday) to 6 (Saturday).
|
||||
#' @param language The language used for month and day names. Default is "en".
|
||||
#' Other valid values include "bg", "ca", "cs", "da", "de", "el", "es", "fi",
|
||||
#' "fr", "he", "hr", "hu", "id", "is", "it", "ja", "kr", "lt", "lv", "ms",
|
||||
#' "nb", "nl", "pl", "pt", "pt-BR", "ro", "rs", "rs-latin", "ru", "sk", "sl",
|
||||
#' "sv", "sw", "th", "tr", "uk", "zh-CN", and "zh-TW".
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{dateRangeInput}}, \code{\link{updateDateInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' dateInput("date1", "Date:", value = "2012-02-29"),
|
||||
#'
|
||||
#' # Default value is the date in client's time zone
|
||||
#' dateInput("date2", "Date:"),
|
||||
#'
|
||||
#' # value is always yyyy-mm-dd, even if the display format is different
|
||||
#' dateInput("date3", "Date:", value = "2012-02-29", format = "mm/dd/yy"),
|
||||
#'
|
||||
#' # Pass in a Date object
|
||||
#' dateInput("date4", "Date:", value = Sys.Date()-10),
|
||||
#'
|
||||
#' # Use different language and different first day of week
|
||||
#' dateInput("date5", "Date:",
|
||||
#' language = "de",
|
||||
#' weekstart = 1),
|
||||
#'
|
||||
#' # Start with decade view instead of default month view
|
||||
#' dateInput("date6", "Date:",
|
||||
#' startview = "decade")
|
||||
#' )
|
||||
#'
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#' @export
|
||||
dateInput <- function(inputId, label, value = NULL, min = NULL, max = NULL,
|
||||
format = "yyyy-mm-dd", startview = "month", weekstart = 0, language = "en",
|
||||
width = NULL) {
|
||||
|
||||
# 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")
|
||||
|
||||
attachDependencies(
|
||||
tags$div(id = inputId,
|
||||
class = "shiny-date-input form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
|
||||
controlLabel(inputId, label),
|
||||
tags$input(type = "text",
|
||||
# datepicker class necessary for dropdown to display correctly
|
||||
class = "form-control datepicker",
|
||||
`data-date-language` = language,
|
||||
`data-date-weekstart` = weekstart,
|
||||
`data-date-format` = format,
|
||||
`data-date-start-view` = startview,
|
||||
`data-min-date` = min,
|
||||
`data-max-date` = max,
|
||||
`data-initial-date` = value
|
||||
)
|
||||
),
|
||||
datePickerDependency
|
||||
)
|
||||
}
|
||||
|
||||
datePickerDependency <- htmlDependency(
|
||||
"bootstrap-datepicker", "1.0.2", c(href = "shared/datepicker"),
|
||||
script = "js/bootstrap-datepicker.min.js",
|
||||
stylesheet = "css/datepicker.css")
|
||||
120
R/input-daterange.R
Normal file
120
R/input-daterange.R
Normal file
@@ -0,0 +1,120 @@
|
||||
#' Create date range input
|
||||
#'
|
||||
#' Creates a pair of text inputs which, when clicked on, bring up calendars that
|
||||
#' the user can click on to select dates.
|
||||
#'
|
||||
#' The date \code{format} string specifies how the date will be displayed in
|
||||
#' the browser. It allows the following values:
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \item \code{yy} Year without century (12)
|
||||
#' \item \code{yyyy} Year with century (2012)
|
||||
#' \item \code{mm} Month number, with leading zero (01-12)
|
||||
#' \item \code{m} Month number, without leading zero (01-12)
|
||||
#' \item \code{M} Abbreviated month name
|
||||
#' \item \code{MM} Full month name
|
||||
#' \item \code{dd} Day of month with leading zero
|
||||
#' \item \code{d} Day of month without leading zero
|
||||
#' \item \code{D} Abbreviated weekday name
|
||||
#' \item \code{DD} Full weekday name
|
||||
#' }
|
||||
#'
|
||||
#' @inheritParams dateInput
|
||||
#' @param start The initial start date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
|
||||
#' date in the client's time zone.
|
||||
#' @param end The initial end date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
|
||||
#' date in the client's time zone.
|
||||
#' @param separator String to display between the start and end input boxes.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{dateInput}}, \code{\link{updateDateRangeInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' dateRangeInput("daterange1", "Date range:",
|
||||
#' start = "2001-01-01",
|
||||
#' end = "2010-12-31"),
|
||||
#'
|
||||
#' # Default start and end is the current date in the client's time zone
|
||||
#' dateRangeInput("daterange2", "Date range:"),
|
||||
#'
|
||||
#' # start and end are always specified in yyyy-mm-dd, even if the display
|
||||
#' # format is different
|
||||
#' dateRangeInput("daterange3", "Date range:",
|
||||
#' start = "2001-01-01",
|
||||
#' end = "2010-12-31",
|
||||
#' min = "2001-01-01",
|
||||
#' max = "2012-12-21",
|
||||
#' format = "mm/dd/yy",
|
||||
#' separator = " - "),
|
||||
#'
|
||||
#' # Pass in Date objects
|
||||
#' dateRangeInput("daterange4", "Date range:",
|
||||
#' start = Sys.Date()-10,
|
||||
#' end = Sys.Date()+10),
|
||||
#'
|
||||
#' # Use different language and different first day of week
|
||||
#' dateRangeInput("daterange5", "Date range:",
|
||||
#' language = "de",
|
||||
#' weekstart = 1),
|
||||
#'
|
||||
#' # Start with decade view instead of default month view
|
||||
#' dateRangeInput("daterange6", "Date range:",
|
||||
#' startview = "decade")
|
||||
#' )
|
||||
#'
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#' @export
|
||||
dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
|
||||
min = NULL, max = NULL, format = "yyyy-mm-dd", startview = "month",
|
||||
weekstart = 0, language = "en", separator = " to ", width = NULL) {
|
||||
|
||||
# If start and end are date objects, convert to a string with yyyy-mm-dd format
|
||||
# Same for min and max
|
||||
if (inherits(start, "Date")) start <- format(start, "%Y-%m-%d")
|
||||
if (inherits(end, "Date")) end <- format(end, "%Y-%m-%d")
|
||||
if (inherits(min, "Date")) min <- format(min, "%Y-%m-%d")
|
||||
if (inherits(max, "Date")) max <- format(max, "%Y-%m-%d")
|
||||
|
||||
attachDependencies(
|
||||
div(id = inputId,
|
||||
class = "shiny-date-range-input form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
|
||||
controlLabel(inputId, label),
|
||||
# input-daterange class is needed for dropdown behavior
|
||||
div(class = "input-daterange input-group",
|
||||
tags$input(
|
||||
class = "input-sm form-control",
|
||||
type = "text",
|
||||
`data-date-language` = language,
|
||||
`data-date-weekstart` = weekstart,
|
||||
`data-date-format` = format,
|
||||
`data-date-start-view` = startview,
|
||||
`data-min-date` = min,
|
||||
`data-max-date` = max,
|
||||
`data-initial-date` = start
|
||||
),
|
||||
span(class = "input-group-addon", separator),
|
||||
tags$input(
|
||||
class = "input-sm form-control",
|
||||
type = "text",
|
||||
`data-date-language` = language,
|
||||
`data-date-weekstart` = weekstart,
|
||||
`data-date-format` = format,
|
||||
`data-date-start-view` = startview,
|
||||
`data-min-date` = min,
|
||||
`data-max-date` = max,
|
||||
`data-initial-date` = end
|
||||
)
|
||||
)
|
||||
),
|
||||
datePickerDependency
|
||||
)
|
||||
}
|
||||
91
R/input-file.R
Normal file
91
R/input-file.R
Normal file
@@ -0,0 +1,91 @@
|
||||
#' File Upload Control
|
||||
#'
|
||||
#' Create a file upload control that can be used to upload one or more files.
|
||||
#'
|
||||
#' Whenever a file upload completes, the corresponding input variable is set
|
||||
#' to a dataframe. This dataframe contains one row for each selected file, and
|
||||
#' the following columns:
|
||||
#' \describe{
|
||||
#' \item{\code{name}}{The filename provided by the web browser. This is
|
||||
#' \strong{not} the path to read to get at the actual data that was uploaded
|
||||
#' (see
|
||||
#' \code{datapath} column).}
|
||||
#' \item{\code{size}}{The size of the uploaded data, in
|
||||
#' bytes.}
|
||||
#' \item{\code{type}}{The MIME type reported by the browser (for example,
|
||||
#' \code{text/plain}), or empty string if the browser didn't know.}
|
||||
#' \item{\code{datapath}}{The path to a temp file that contains the data that was
|
||||
#' uploaded. This file may be deleted if the user performs another upload
|
||||
#' operation.}
|
||||
#' }
|
||||
#'
|
||||
#' @family input elements
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param multiple Whether the user should be allowed to select and upload
|
||||
#' multiple files at once. \bold{Does not work on older browsers, including
|
||||
#' Internet Explorer 9 and earlier.}
|
||||
#' @param accept A character vector of MIME types; gives the browser a hint of
|
||||
#' what kind of files the server is expecting.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' sidebarLayout(
|
||||
#' sidebarPanel(
|
||||
#' fileInput("file1", "Choose CSV File",
|
||||
#' accept = c(
|
||||
#' "text/csv",
|
||||
#' "text/comma-separated-values,text/plain",
|
||||
#' ".csv")
|
||||
#' ),
|
||||
#' tags$hr(),
|
||||
#' checkboxInput("header", "Header", TRUE)
|
||||
#' ),
|
||||
#' mainPanel(
|
||||
#' tableOutput("contents")
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' output$contents <- renderTable({
|
||||
#' # input$file1 will be NULL initially. After the user selects
|
||||
#' # and uploads a file, it will be a data frame with 'name',
|
||||
#' # 'size', 'type', and 'datapath' columns. The 'datapath'
|
||||
#' # column will contain the local filenames where the data can
|
||||
#' # be found.
|
||||
#' inFile <- input$file1
|
||||
#'
|
||||
#' if (is.null(inFile))
|
||||
#' return(NULL)
|
||||
#'
|
||||
#' read.csv(inFile$datapath, header = input$header)
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
|
||||
width = NULL) {
|
||||
|
||||
inputTag <- tags$input(id = inputId, name = inputId, type = "file")
|
||||
if (multiple)
|
||||
inputTag$attribs$multiple <- "multiple"
|
||||
if (length(accept) > 0)
|
||||
inputTag$attribs$accept <- paste(accept, collapse=',')
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label),
|
||||
inputTag,
|
||||
tags$div(
|
||||
id=paste(inputId, "_progress", sep=""),
|
||||
class="progress progress-striped active shiny-file-input-progress",
|
||||
tags$div(class="progress-bar")
|
||||
)
|
||||
)
|
||||
}
|
||||
47
R/input-numeric.R
Normal file
47
R/input-numeric.R
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
#' Create a numeric input control
|
||||
#'
|
||||
#' Create an input control for entry of numeric values
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param min Minimum allowed value
|
||||
#' @param max Maximum allowed value
|
||||
#' @param step Interval to use when stepping between min and max
|
||||
#' @return A numeric input control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateNumericInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' numericInput("obs", "Observations:", 10, min = 1, max = 100),
|
||||
#' verbatimTextOutput("value")
|
||||
#' )
|
||||
#' server <- function(input, output) {
|
||||
#' output$value <- renderText({ input$obs })
|
||||
#' }
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA,
|
||||
width = NULL) {
|
||||
|
||||
# build input tag
|
||||
inputTag <- tags$input(id = inputId, type = "number", class="form-control",
|
||||
value = formatNoSci(value))
|
||||
if (!is.na(min))
|
||||
inputTag$attribs$min = min
|
||||
if (!is.na(max))
|
||||
inputTag$attribs$max = max
|
||||
if (!is.na(step))
|
||||
inputTag$attribs$step = step
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label, `for` = inputId),
|
||||
inputTag
|
||||
)
|
||||
}
|
||||
37
R/input-password.R
Normal file
37
R/input-password.R
Normal file
@@ -0,0 +1,37 @@
|
||||
#' Create a password input control
|
||||
#'
|
||||
#' Create an password control for entry of passwords.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @return A text input control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateTextInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' passwordInput("password", "Password:"),
|
||||
#' actionButton("go", "Go"),
|
||||
#' verbatimTextOutput("value")
|
||||
#' )
|
||||
#' server <- function(input, output) {
|
||||
#' output$value <- renderText({
|
||||
#' req(input$go)
|
||||
#' isolate(input$password)
|
||||
#' })
|
||||
#' }
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
passwordInput <- function(inputId, label, value = "", width = NULL,
|
||||
placeholder = NULL) {
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label, `for` = inputId),
|
||||
tags$input(id = inputId, type="password", class="form-control", value=value,
|
||||
placeholder = placeholder)
|
||||
)
|
||||
}
|
||||
76
R/input-radiobuttons.R
Normal file
76
R/input-radiobuttons.R
Normal file
@@ -0,0 +1,76 @@
|
||||
#' Create radio buttons
|
||||
#'
|
||||
#' Create a set of radio buttons used to select an item from a list.
|
||||
#'
|
||||
#' If you need to represent a "None selected" state, it's possible to default
|
||||
#' the radio buttons to have no options selected by using
|
||||
#' \code{selected = character(0)}. However, this is not recommended, as it gives
|
||||
#' the user no way to return to that state once they've made a selection.
|
||||
#' Instead, consider having the first of your choices be \code{c("None selected"
|
||||
#' = "")}.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param choices List of values to select from (if elements of the list are
|
||||
#' named then that name rather than the value is displayed to the user)
|
||||
#' @param selected The initially selected value (if not specified then
|
||||
#' defaults to the first value)
|
||||
#' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
|
||||
#' @return A set of radio buttons that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateRadioButtons}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' radioButtons("dist", "Distribution type:",
|
||||
#' c("Normal" = "norm",
|
||||
#' "Uniform" = "unif",
|
||||
#' "Log-normal" = "lnorm",
|
||||
#' "Exponential" = "exp")),
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' output$distPlot <- renderPlot({
|
||||
#' dist <- switch(input$dist,
|
||||
#' norm = rnorm,
|
||||
#' unif = runif,
|
||||
#' lnorm = rlnorm,
|
||||
#' exp = rexp,
|
||||
#' rnorm)
|
||||
#'
|
||||
#' hist(dist(500))
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
radioButtons <- function(inputId, label, choices, selected = NULL,
|
||||
inline = FALSE, width = NULL) {
|
||||
|
||||
# resolve names
|
||||
choices <- choicesWithNames(choices)
|
||||
|
||||
# default value if it's not specified
|
||||
selected <- if (is.null(selected)) choices[[1]] else {
|
||||
validateSelected(selected, choices, inputId)
|
||||
}
|
||||
if (length(selected) > 1) stop("The 'selected' argument must be of length 1")
|
||||
|
||||
options <- generateOptions(inputId, choices, selected, inline, type = 'radio')
|
||||
|
||||
divClass <- "form-group shiny-input-radiogroup shiny-input-container"
|
||||
if (inline)
|
||||
divClass <- paste(divClass, "shiny-input-container-inline")
|
||||
|
||||
tags$div(id = inputId,
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
class = divClass,
|
||||
controlLabel(inputId, label),
|
||||
options
|
||||
)
|
||||
}
|
||||
189
R/input-select.R
Normal file
189
R/input-select.R
Normal file
@@ -0,0 +1,189 @@
|
||||
#' Create a select list input control
|
||||
#'
|
||||
#' Create a select list that can be used to choose a single or multiple items
|
||||
#' from a list of values.
|
||||
#'
|
||||
#' By default, \code{selectInput()} and \code{selectizeInput()} use the
|
||||
#' JavaScript library \pkg{selectize.js}
|
||||
#' (\url{https://github.com/brianreavis/selectize.js}) to instead of the basic
|
||||
#' select input element. To use the standard HTML select input element, use
|
||||
#' \code{selectInput()} with \code{selectize=FALSE}.
|
||||
#'
|
||||
#' In selectize mode, if the first element in \code{choices} has a value of
|
||||
#' \code{""}, its name will be treated as a placeholder prompt. For example:
|
||||
#' \code{selectInput("letter", "Letter", c("Choose one" = "", LETTERS))}
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param choices List of values to select from. If elements of the list are
|
||||
#' named then that name rather than the value is displayed to the user.
|
||||
#' @param selected The initially selected value (or multiple values if
|
||||
#' \code{multiple = TRUE}). If not specified then defaults to the first value
|
||||
#' for single-select lists and no values for multiple select lists.
|
||||
#' @param multiple Is selection of multiple items allowed?
|
||||
#' @param selectize Whether to use \pkg{selectize.js} or not.
|
||||
#' @param size Number of items to show in the selection box; a larger number
|
||||
#' will result in a taller box. Not compatible with \code{selectize=TRUE}.
|
||||
#' Normally, when \code{multiple=FALSE}, a select input will be a drop-down
|
||||
#' list, but when \code{size} is set, it will be a box instead.
|
||||
#' @return A select list control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateSelectInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' selectInput("variable", "Variable:",
|
||||
#' c("Cylinders" = "cyl",
|
||||
#' "Transmission" = "am",
|
||||
#' "Gears" = "gear")),
|
||||
#' tableOutput("data")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' output$data <- renderTable({
|
||||
#' mtcars[, c("mpg", input$variable), drop = FALSE]
|
||||
#' }, rownames = TRUE)
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
selectInput <- function(inputId, label, choices, selected = NULL,
|
||||
multiple = FALSE, selectize = TRUE, width = NULL,
|
||||
size = NULL) {
|
||||
# resolve names
|
||||
choices <- choicesWithNames(choices)
|
||||
|
||||
# default value if it's not specified
|
||||
if (is.null(selected)) {
|
||||
if (!multiple) selected <- firstChoice(choices)
|
||||
} else selected <- validateSelected(selected, choices, inputId)
|
||||
|
||||
if (!is.null(size) && selectize) {
|
||||
stop("'size' argument is incompatible with 'selectize=TRUE'.")
|
||||
}
|
||||
|
||||
# create select tag and add options
|
||||
selectTag <- tags$select(
|
||||
id = inputId,
|
||||
class = if (!selectize) "form-control",
|
||||
size = size,
|
||||
selectOptions(choices, selected)
|
||||
)
|
||||
if (multiple)
|
||||
selectTag$attribs$multiple <- "multiple"
|
||||
|
||||
# return label and select tag
|
||||
res <- div(
|
||||
class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
controlLabel(inputId, label),
|
||||
div(selectTag)
|
||||
)
|
||||
|
||||
if (!selectize) return(res)
|
||||
|
||||
selectizeIt(inputId, res, NULL, nonempty = !multiple && !("" %in% choices))
|
||||
}
|
||||
|
||||
firstChoice <- function(choices) {
|
||||
if (length(choices) == 0L) return()
|
||||
choice <- choices[[1]]
|
||||
if (is.list(choice)) firstChoice(choice) else choice
|
||||
}
|
||||
|
||||
# Create tags for each of the options; use <optgroup> if necessary.
|
||||
# This returns a HTML string instead of tags, because of the 'selected'
|
||||
# attribute.
|
||||
selectOptions <- function(choices, selected = NULL) {
|
||||
html <- mapply(choices, names(choices), FUN = function(choice, label) {
|
||||
if (is.list(choice)) {
|
||||
# If sub-list, create an optgroup and recurse into the sublist
|
||||
sprintf(
|
||||
'<optgroup label="%s">\n%s\n</optgroup>',
|
||||
htmlEscape(label, TRUE),
|
||||
selectOptions(choice, selected)
|
||||
)
|
||||
|
||||
} else {
|
||||
# If single item, just return option string
|
||||
sprintf(
|
||||
'<option value="%s"%s>%s</option>',
|
||||
htmlEscape(choice, TRUE),
|
||||
if (choice %in% selected) ' selected' else '',
|
||||
htmlEscape(label)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
HTML(paste(html, collapse = '\n'))
|
||||
}
|
||||
|
||||
# need <optgroup> when choices contains sub-lists
|
||||
needOptgroup <- function(choices) {
|
||||
any(vapply(choices, is.list, logical(1)))
|
||||
}
|
||||
|
||||
#' @rdname selectInput
|
||||
#' @param ... Arguments passed to \code{selectInput()}.
|
||||
#' @param options A list of options. See the documentation of \pkg{selectize.js}
|
||||
#' for possible options (character option values inside \code{\link{I}()} will
|
||||
#' be treated as literal JavaScript code; see \code{\link{renderDataTable}()}
|
||||
#' for details).
|
||||
#' @param width The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
#' see \code{\link{validateCssUnit}}.
|
||||
#' @note The selectize input created from \code{selectizeInput()} allows
|
||||
#' deletion of the selected option even in a single select input, which will
|
||||
#' return an empty string as its value. This is the default behavior of
|
||||
#' \pkg{selectize.js}. However, the selectize input created from
|
||||
#' \code{selectInput(..., selectize = TRUE)} will ignore the empty string
|
||||
#' value when it is a single choice input and the empty string is not in the
|
||||
#' \code{choices} argument. This is to keep compatibility with
|
||||
#' \code{selectInput(..., selectize = FALSE)}.
|
||||
#' @export
|
||||
selectizeInput <- function(inputId, ..., options = NULL, width = NULL) {
|
||||
selectizeIt(
|
||||
inputId,
|
||||
selectInput(inputId, ..., selectize = FALSE, width = width),
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
# given a select input and its id, selectize it
|
||||
selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
|
||||
res <- checkAsIs(options)
|
||||
|
||||
selectizeDep <- htmlDependency(
|
||||
"selectize", "0.11.2", c(href = "shared/selectize"),
|
||||
stylesheet = "css/selectize.bootstrap3.css",
|
||||
head = format(tagList(
|
||||
HTML('<!--[if lt IE 9]>'),
|
||||
tags$script(src = 'shared/selectize/js/es5-shim.min.js'),
|
||||
HTML('<![endif]-->'),
|
||||
tags$script(src = 'shared/selectize/js/selectize.min.js')
|
||||
))
|
||||
)
|
||||
|
||||
if ('drag_drop' %in% options$plugins) {
|
||||
selectizeDep <- list(selectizeDep, htmlDependency(
|
||||
'jqueryui', '1.11.4', c(href = 'shared/jqueryui'),
|
||||
script = 'jquery-ui.min.js'
|
||||
))
|
||||
}
|
||||
|
||||
# Insert script on same level as <select> tag
|
||||
select$children[[2]] <- tagAppendChild(
|
||||
select$children[[2]],
|
||||
tags$script(
|
||||
type = 'application/json',
|
||||
`data-for` = inputId, `data-nonempty` = if (nonempty) '',
|
||||
`data-eval` = if (length(res$eval)) HTML(toJSON(res$eval)),
|
||||
if (length(res$options)) HTML(toJSON(res$options)) else '{}'
|
||||
)
|
||||
)
|
||||
|
||||
attachDependencies(select, selectizeDep)
|
||||
}
|
||||
255
R/input-slider.R
Normal file
255
R/input-slider.R
Normal file
@@ -0,0 +1,255 @@
|
||||
#' Slider Input Widget
|
||||
#'
|
||||
#' Constructs a slider widget to select a numeric value from a range.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param min The minimum value (inclusive) that can be selected.
|
||||
#' @param max The maximum value (inclusive) that can be selected.
|
||||
#' @param value The initial value of the slider. A numeric vector of length one
|
||||
#' will create a regular slider; a numeric vector of length two will create a
|
||||
#' double-ended range slider. A warning will be issued if the value doesn't
|
||||
#' fit between \code{min} and \code{max}.
|
||||
#' @param step Specifies the interval between each selectable value on the
|
||||
#' slider (if \code{NULL}, a heuristic is used to determine the step size). If
|
||||
#' the values are dates, \code{step} is in days; if the values are times
|
||||
#' (POSIXt), \code{step} is in seconds.
|
||||
#' @param round \code{TRUE} to round all values to the nearest integer;
|
||||
#' \code{FALSE} if no rounding is desired; or an integer to round to that
|
||||
#' number of digits (for example, 1 will round to the nearest 10, and -2 will
|
||||
#' round to the nearest .01). Any rounding will be applied after snapping to
|
||||
#' the nearest step.
|
||||
#' @param format Deprecated.
|
||||
#' @param locale Deprecated.
|
||||
#' @param ticks \code{FALSE} to hide tick marks, \code{TRUE} to show them
|
||||
#' according to some simple heuristics.
|
||||
#' @param animate \code{TRUE} to show simple animation controls with default
|
||||
#' settings; \code{FALSE} not to; or a custom settings list, such as those
|
||||
#' created using \code{\link{animationOptions}}.
|
||||
#' @param sep Separator between thousands places in numbers.
|
||||
#' @param pre A prefix string to put in front of the value.
|
||||
#' @param post A suffix string to put after the value.
|
||||
#' @param dragRange This option is used only if it is a range slider (with two
|
||||
#' values). If \code{TRUE} (the default), the range can be dragged. In other
|
||||
#' words, the min and max can be dragged together. If \code{FALSE}, the range
|
||||
#' cannot be dragged.
|
||||
#' @param timeFormat Only used if the values are Date or POSIXt objects. A time
|
||||
#' format string, to be passed to the Javascript strftime library. See
|
||||
#' \url{https://github.com/samsonjs/strftime} for more details. The allowed
|
||||
#' format specifications are very similar, but not identical, to those for R's
|
||||
#' \code{\link{strftime}} function. For Dates, the default is \code{"\%F"}
|
||||
#' (like \code{"2015-07-01"}), and for POSIXt, the default is \code{"\%F \%T"}
|
||||
#' (like \code{"2015-07-01 15:32:10"}).
|
||||
#' @param timezone Only used if the values are POSIXt objects. A string
|
||||
#' specifying the time zone offset for the displayed times, in the format
|
||||
#' \code{"+HHMM"} or \code{"-HHMM"}. If \code{NULL} (the default), times will
|
||||
#' be displayed in the browser's time zone. The value \code{"+0000"} will
|
||||
#' result in UTC time.
|
||||
#' @inheritParams selectizeInput
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateSliderInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("obs", "Number of observations:",
|
||||
#' min = 0, max = 1000, value = 500
|
||||
#' ),
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#'
|
||||
#' # Server logic
|
||||
#' server <- function(input, output) {
|
||||
#' output$distPlot <- renderPlot({
|
||||
#' hist(rnorm(input$obs))
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' # Complete app with UI and server components
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
sliderInput <- function(inputId, label, min, max, value, step = NULL,
|
||||
round = FALSE, format = NULL, locale = NULL,
|
||||
ticks = TRUE, animate = FALSE, width = NULL, sep = ",",
|
||||
pre = NULL, post = NULL, timeFormat = NULL,
|
||||
timezone = NULL, dragRange = TRUE)
|
||||
{
|
||||
if (!missing(format)) {
|
||||
shinyDeprecated(msg = "The `format` argument to sliderInput is deprecated. Use `sep`, `pre`, and `post` instead.",
|
||||
version = "0.10.2.2")
|
||||
}
|
||||
if (!missing(locale)) {
|
||||
shinyDeprecated(msg = "The `locale` argument to sliderInput is deprecated. Use `sep`, `pre`, and `post` instead.",
|
||||
version = "0.10.2.2")
|
||||
}
|
||||
|
||||
# If step is NULL, use heuristic to set the step size.
|
||||
findStepSize <- function(min, max, step) {
|
||||
if (!is.null(step)) return(step)
|
||||
|
||||
range <- max - min
|
||||
# If short range or decimals, use continuous decimal with ~100 points
|
||||
if (range < 2 || hasDecimals(min) || hasDecimals(max)) {
|
||||
step <- pretty(c(min, max), n = 100)
|
||||
step[2] - step[1]
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
if (inherits(min, "Date")) {
|
||||
if (!inherits(max, "Date") || !inherits(value, "Date"))
|
||||
stop("`min`, `max`, and `value must all be Date or non-Date objects")
|
||||
dataType <- "date"
|
||||
|
||||
if (is.null(timeFormat))
|
||||
timeFormat <- "%F"
|
||||
|
||||
} else if (inherits(min, "POSIXt")) {
|
||||
if (!inherits(max, "POSIXt") || !inherits(value, "POSIXt"))
|
||||
stop("`min`, `max`, and `value must all be POSIXt or non-POSIXt objects")
|
||||
dataType <- "datetime"
|
||||
|
||||
if (is.null(timeFormat))
|
||||
timeFormat <- "%F %T"
|
||||
|
||||
} else {
|
||||
dataType <- "number"
|
||||
}
|
||||
|
||||
step <- findStepSize(min, max, step)
|
||||
|
||||
if (dataType %in% c("date", "datetime")) {
|
||||
# For Dates, this conversion uses midnight on that date in UTC
|
||||
to_ms <- function(x) 1000 * as.numeric(as.POSIXct(x))
|
||||
|
||||
# Convert values to milliseconds since epoch (this is the value JS uses)
|
||||
# Find step size in ms
|
||||
step <- to_ms(max) - to_ms(max - step)
|
||||
min <- to_ms(min)
|
||||
max <- to_ms(max)
|
||||
value <- to_ms(value)
|
||||
}
|
||||
|
||||
range <- max - min
|
||||
|
||||
# Try to get a sane number of tick marks
|
||||
if (ticks) {
|
||||
n_steps <- range / step
|
||||
|
||||
# Make sure there are <= 10 steps.
|
||||
# n_ticks can be a noninteger, which is good when the range is not an
|
||||
# integer multiple of the step size, e.g., min=1, max=10, step=4
|
||||
scale_factor <- ceiling(n_steps / 10)
|
||||
n_ticks <- n_steps / scale_factor
|
||||
|
||||
} else {
|
||||
n_ticks <- NULL
|
||||
}
|
||||
|
||||
sliderProps <- dropNulls(list(
|
||||
class = "js-range-slider",
|
||||
id = inputId,
|
||||
`data-type` = if (length(value) > 1) "double",
|
||||
`data-min` = formatNoSci(min),
|
||||
`data-max` = formatNoSci(max),
|
||||
`data-from` = formatNoSci(value[1]),
|
||||
`data-to` = if (length(value) > 1) formatNoSci(value[2]),
|
||||
`data-step` = formatNoSci(step),
|
||||
`data-grid` = ticks,
|
||||
`data-grid-num` = n_ticks,
|
||||
`data-grid-snap` = FALSE,
|
||||
`data-prettify-separator` = sep,
|
||||
`data-prefix` = pre,
|
||||
`data-postfix` = post,
|
||||
`data-keyboard` = TRUE,
|
||||
`data-keyboard-step` = step / (max - min) * 100,
|
||||
`data-drag-interval` = dragRange,
|
||||
# The following are ignored by the ion.rangeSlider, but are used by Shiny.
|
||||
`data-data-type` = dataType,
|
||||
`data-time-format` = timeFormat,
|
||||
`data-timezone` = timezone
|
||||
))
|
||||
|
||||
# Replace any TRUE and FALSE with "true" and "false"
|
||||
sliderProps <- lapply(sliderProps, function(x) {
|
||||
if (identical(x, TRUE)) "true"
|
||||
else if (identical(x, FALSE)) "false"
|
||||
else x
|
||||
})
|
||||
|
||||
sliderTag <- div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
if (!is.null(label)) controlLabel(inputId, label),
|
||||
do.call(tags$input, sliderProps)
|
||||
)
|
||||
|
||||
# Add animation buttons
|
||||
if (identical(animate, TRUE))
|
||||
animate <- animationOptions()
|
||||
|
||||
if (!is.null(animate) && !identical(animate, FALSE)) {
|
||||
if (is.null(animate$playButton))
|
||||
animate$playButton <- icon('play', lib = 'glyphicon')
|
||||
if (is.null(animate$pauseButton))
|
||||
animate$pauseButton <- icon('pause', lib = 'glyphicon')
|
||||
|
||||
sliderTag <- tagAppendChild(
|
||||
sliderTag,
|
||||
tags$div(class='slider-animate-container',
|
||||
tags$a(href='#',
|
||||
class='slider-animate-button',
|
||||
'data-target-id'=inputId,
|
||||
'data-interval'=animate$interval,
|
||||
'data-loop'=animate$loop,
|
||||
span(class = 'play', animate$playButton),
|
||||
span(class = 'pause', animate$pauseButton)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
dep <- list(
|
||||
htmlDependency("ionrangeslider", "2.1.2", c(href="shared/ionrangeslider"),
|
||||
script = "js/ion.rangeSlider.min.js",
|
||||
# ion.rangeSlider also needs normalize.css, which is already included in
|
||||
# Bootstrap.
|
||||
stylesheet = c("css/ion.rangeSlider.css",
|
||||
"css/ion.rangeSlider.skinShiny.css")
|
||||
),
|
||||
htmlDependency("strftime", "0.9.2", c(href="shared/strftime"),
|
||||
script = "strftime-min.js"
|
||||
)
|
||||
)
|
||||
|
||||
attachDependencies(sliderTag, dep)
|
||||
}
|
||||
|
||||
hasDecimals <- function(value) {
|
||||
truncatedValue <- round(value)
|
||||
return (!identical(value, truncatedValue))
|
||||
}
|
||||
|
||||
#' @rdname sliderInput
|
||||
#'
|
||||
#' @param interval The interval, in milliseconds, between each animation step.
|
||||
#' @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
|
||||
#' \code{\link{HTML}}).
|
||||
#' @param pauseButton Similar to \code{playButton}, but for the pause button.
|
||||
#'
|
||||
#' @export
|
||||
animationOptions <- function(interval=1000,
|
||||
loop=FALSE,
|
||||
playButton=NULL,
|
||||
pauseButton=NULL) {
|
||||
list(interval=interval,
|
||||
loop=loop,
|
||||
playButton=playButton,
|
||||
pauseButton=pauseButton)
|
||||
}
|
||||
28
R/input-submit.R
Normal file
28
R/input-submit.R
Normal file
@@ -0,0 +1,28 @@
|
||||
#' Create a submit button
|
||||
#'
|
||||
#' Create a submit button for an input form. Forms that include a submit
|
||||
#' button do not automatically update their outputs when inputs change,
|
||||
#' rather they wait until the user explicitly clicks the submit button.
|
||||
#'
|
||||
#' @param text Button caption
|
||||
#' @param icon Optional \code{\link{icon}} to appear on the button
|
||||
#' @param width The width of the button, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
#' see \code{\link{validateCssUnit}}.
|
||||
#' @return A submit button that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#'
|
||||
#' @examples
|
||||
#' submitButton("Update View")
|
||||
#' submitButton("Update View", icon("refresh"))
|
||||
#' @export
|
||||
submitButton <- function(text = "Apply Changes", icon = NULL, width = NULL) {
|
||||
div(
|
||||
tags$button(
|
||||
type="submit",
|
||||
class="btn btn-primary",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
list(icon, text)
|
||||
)
|
||||
)
|
||||
}
|
||||
41
R/input-text.R
Normal file
41
R/input-text.R
Normal file
@@ -0,0 +1,41 @@
|
||||
#' Create a text input control
|
||||
#'
|
||||
#' Create an input control for entry of unstructured text values
|
||||
#'
|
||||
#' @param inputId The \code{input} slot that will be used to access the value.
|
||||
#' @param label Display label for the control, or \code{NULL} for no label.
|
||||
#' @param value Initial value.
|
||||
#' @param width The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
#' see \code{\link{validateCssUnit}}.
|
||||
#' @param placeholder A character string giving the user a hint as to what can
|
||||
#' be entered into the control. Internet Explorer 8 and 9 do not support this
|
||||
#' option.
|
||||
#' @return A text input control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateTextInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' textInput("caption", "Caption", "Data Summary"),
|
||||
#' verbatimTextOutput("value")
|
||||
#' )
|
||||
#' server <- function(input, output) {
|
||||
#' output$value <- renderText({ input$caption })
|
||||
#' }
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
textInput <- function(inputId, label, value = "", width = NULL,
|
||||
placeholder = NULL) {
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label, `for` = inputId),
|
||||
tags$input(id = inputId, type="text", class="form-control", value=value,
|
||||
placeholder = placeholder)
|
||||
)
|
||||
}
|
||||
105
R/input-utils.R
Normal file
105
R/input-utils.R
Normal file
@@ -0,0 +1,105 @@
|
||||
controlLabel <- function(controlName, label) {
|
||||
label %AND% tags$label(class = "control-label", `for` = controlName, label)
|
||||
}
|
||||
|
||||
|
||||
# Before shiny 0.9, `selected` refers to names/labels of `choices`; now it
|
||||
# refers to values. Below is a function for backward compatibility.
|
||||
validateSelected <- function(selected, choices, inputId) {
|
||||
# drop names, otherwise toJSON() keeps them too
|
||||
selected <- unname(selected)
|
||||
# if you are using optgroups, you're using shiny > 0.10.0, and you should
|
||||
# already know that `selected` must be a value instead of a label
|
||||
if (needOptgroup(choices)) return(selected)
|
||||
|
||||
if (is.list(choices)) choices <- unlist(choices)
|
||||
|
||||
nms <- names(choices)
|
||||
# labels and values are identical, no need to validate
|
||||
if (identical(nms, unname(choices))) return(selected)
|
||||
# when selected labels instead of values
|
||||
i <- (selected %in% nms) & !(selected %in% choices)
|
||||
if (any(i)) {
|
||||
warnFun <- if (all(i)) {
|
||||
# replace names with values
|
||||
selected <- unname(choices[selected])
|
||||
warning
|
||||
} else stop # stop when it is ambiguous (some labels == values)
|
||||
warnFun("'selected' must be the values instead of names of 'choices' ",
|
||||
"for the input '", inputId, "'")
|
||||
}
|
||||
selected
|
||||
}
|
||||
|
||||
|
||||
# generate options for radio buttons and checkbox groups (type = 'checkbox' or
|
||||
# 'radio')
|
||||
generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox') {
|
||||
# generate a list of <input type=? [checked] />
|
||||
options <- mapply(
|
||||
choices, names(choices),
|
||||
FUN = function(value, name) {
|
||||
inputTag <- tags$input(
|
||||
type = type, name = inputId, value = value
|
||||
)
|
||||
if (value %in% selected)
|
||||
inputTag$attribs$checked <- "checked"
|
||||
|
||||
# If inline, there's no wrapper div, and the label needs a class like
|
||||
# checkbox-inline.
|
||||
if (inline) {
|
||||
tags$label(class = paste0(type, "-inline"), inputTag, tags$span(name))
|
||||
} else {
|
||||
tags$div(class = type,
|
||||
tags$label(inputTag, tags$span(name))
|
||||
)
|
||||
}
|
||||
},
|
||||
SIMPLIFY = FALSE, USE.NAMES = FALSE
|
||||
)
|
||||
|
||||
div(class = "shiny-options-group", options)
|
||||
}
|
||||
|
||||
|
||||
# Takes a vector or list, and adds names (same as the value) to any entries
|
||||
# without names.
|
||||
choicesWithNames <- function(choices) {
|
||||
# Take a vector or list, and convert to list. Also, if any children are
|
||||
# vectors with length > 1, convert those to list. If the list is unnamed,
|
||||
# convert it to a named list with blank names.
|
||||
listify <- function(obj) {
|
||||
# If a list/vector is unnamed, give it blank names
|
||||
makeNamed <- function(x) {
|
||||
if (is.null(names(x))) names(x) <- character(length(x))
|
||||
x
|
||||
}
|
||||
|
||||
res <- lapply(obj, function(val) {
|
||||
if (is.list(val))
|
||||
listify(val)
|
||||
else if (length(val) == 1 && is.null(names(val)))
|
||||
val
|
||||
else
|
||||
makeNamed(as.list(val))
|
||||
})
|
||||
|
||||
makeNamed(res)
|
||||
}
|
||||
|
||||
choices <- listify(choices)
|
||||
if (length(choices) == 0) return(choices)
|
||||
|
||||
# Recurse into any subgroups
|
||||
choices <- mapply(choices, names(choices), FUN = function(choice, name) {
|
||||
if (!is.list(choice)) return(choice)
|
||||
if (name == "") stop('All sub-lists in "choices" must be named.')
|
||||
choicesWithNames(choice)
|
||||
}, SIMPLIFY = FALSE)
|
||||
|
||||
# default missing names to choice values
|
||||
missing <- names(choices) == ""
|
||||
names(choices)[missing] <- as.character(choices)[missing]
|
||||
|
||||
choices
|
||||
}
|
||||
176
R/insert-ui.R
Normal file
176
R/insert-ui.R
Normal file
@@ -0,0 +1,176 @@
|
||||
#' Insert UI objects
|
||||
#'
|
||||
#' Insert a UI object into the app.
|
||||
#'
|
||||
#' This function allows you to dynamically add an arbitrarily large UI
|
||||
#' object into your app, whenever you want, as many times as you want.
|
||||
#' Unlike \code{\link{renderUI}}, the UI generated with \code{insertUI}
|
||||
#' is not updatable as a whole: once it's created, it stays there. Each
|
||||
#' new call to \code{insertUI} creates more UI objects, in addition to
|
||||
#' the ones already there (all independent from one another). To
|
||||
#' update a part of the UI (ex: an input object), you must use the
|
||||
#' appropriate \code{render} function or a customized \code{reactive}
|
||||
#' function. To remove any part of your UI, use \code{\link{removeUI}}.
|
||||
#'
|
||||
#' @param selector A string that is accepted by jQuery's selector (i.e. the
|
||||
#' string \code{s} to be placed in a \code{$(s)} jQuery call). This selector
|
||||
#' will determine the element(s) relative to which you want to insert your
|
||||
#' UI object.
|
||||
#'
|
||||
#' @param where Where your UI object should go relative to the selector:
|
||||
#' \describe{
|
||||
#' \item{\code{beforeBegin}}{Before the selector element itself}
|
||||
#' \item{\code{afterBegin}}{Just inside the selector element, before its
|
||||
#' first child}
|
||||
#' \item{\code{beforeEnd}}{Just inside the selector element, after its
|
||||
#' last child (default)}
|
||||
#' \item{\code{afterEnd}}{After the selector element itself}
|
||||
#' }
|
||||
#' Adapted from
|
||||
#' \href{https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML}{here}.
|
||||
#'
|
||||
#' @param ui The UI object you want to insert. This can be anything that
|
||||
#' you usually put inside your apps's \code{ui} function. If you're inserting
|
||||
#' multiple elements in one call, make sure to wrap them in either a
|
||||
#' \code{tagList()} or a \code{tags$div()} (the latter option has the
|
||||
#' advantage that you can give it an \code{id} to make it easier to
|
||||
#' reference or remove it later on). If you want to insert raw html, use
|
||||
#' \code{ui = HTML()}.
|
||||
#'
|
||||
#' @param multiple In case your selector matches more than one element,
|
||||
#' \code{multiple} determines whether Shiny should insert the UI object
|
||||
#' relative to all matched elements or just relative to the first
|
||||
#' matched element (default).
|
||||
#'
|
||||
#' @param immediate Whether the UI object should be immediately inserted into
|
||||
#' the app when you call \code{insertUI}, or whether Shiny should wait until
|
||||
#' all outputs have been updated and all observers have been run (default).
|
||||
#'
|
||||
#' @param session The shiny session within which to call \code{insertUI}.
|
||||
#'
|
||||
#' @seealso \code{\link{removeUI}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # Define UI
|
||||
#' ui <- fluidPage(
|
||||
#' actionButton("add", "Add UI")
|
||||
#' )
|
||||
#'
|
||||
#' # Server logic
|
||||
#' server <- function(input, output, session) {
|
||||
#' observeEvent(input$add, {
|
||||
#' insertUI(
|
||||
#' selector = "#add",
|
||||
#' where = "afterEnd",
|
||||
#' ui = textInput(paste0("txt", input$add),
|
||||
#' "Insert some text")
|
||||
#' )
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' # Complete app with UI and server components
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
insertUI <- function(selector,
|
||||
where = c("beforeBegin", "afterBegin", "beforeEnd", "afterEnd"),
|
||||
ui,
|
||||
multiple = FALSE,
|
||||
immediate = FALSE,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
force(selector)
|
||||
force(ui)
|
||||
force(session)
|
||||
force(multiple)
|
||||
if (missing(where)) where <- "beforeEnd"
|
||||
where <- match.arg(where)
|
||||
|
||||
callback <- function() {
|
||||
session$sendInsertUI(selector = selector,
|
||||
multiple = multiple,
|
||||
where = where,
|
||||
content = processDeps(ui, session))
|
||||
}
|
||||
|
||||
if (!immediate) session$onFlushed(callback, once = TRUE)
|
||||
else callback()
|
||||
}
|
||||
|
||||
|
||||
#' Remove UI objects
|
||||
#'
|
||||
#' Remove a UI object from the app.
|
||||
#'
|
||||
#' This function allows you to remove any part of your UI. Once \code{removeUI}
|
||||
#' is executed on some element, it is gone forever.
|
||||
#'
|
||||
#' While it may be a particularly useful pattern to pair this with
|
||||
#' \code{\link{insertUI}} (to remove some UI you had previously inserted),
|
||||
#' there is no restriction on what you can use \code{removeUI} on. Any
|
||||
#' element that can be selected through a jQuery selector can be removed
|
||||
#' through this function.
|
||||
#'
|
||||
#' @param selector A string that is accepted by jQuery's selector (i.e. the
|
||||
#' string \code{s} to be placed in a \code{$(s)} jQuery call). This selector
|
||||
#' will determine the element(s) to be removed. If you want to remove a
|
||||
#' Shiny input or output, note that many of these are wrapped in \code{div}s,
|
||||
#' so you may need to use a somewhat complex selector -- see the Examples below.
|
||||
#' (Alternatively, you could also wrap the inputs/outputs that you want to be
|
||||
#' able to remove easily in a \code{div} with an id.)
|
||||
#'
|
||||
#' @param multiple In case your selector matches more than one element,
|
||||
#' \code{multiple} determines whether Shiny should remove all the matched
|
||||
#' elements or just the first matched element (default).
|
||||
#'
|
||||
#' @param immediate Whether the element(s) should be immediately removed from
|
||||
#' the app when you call \code{removeUI}, or whether Shiny should wait until
|
||||
#' all outputs have been updated and all observers have been run (default).
|
||||
#'
|
||||
#' @param session The shiny session within which to call \code{removeUI}.
|
||||
#'
|
||||
#' @seealso \code{\link{insertUI}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # Define UI
|
||||
#' ui <- fluidPage(
|
||||
#' actionButton("rmv", "Remove UI"),
|
||||
#' textInput("txt", "This is no longer useful")
|
||||
#' )
|
||||
#'
|
||||
#' # Server logic
|
||||
#' server <- function(input, output, session) {
|
||||
#' observeEvent(input$rmv, {
|
||||
#' removeUI(
|
||||
#' selector = "div:has(> #txt)"
|
||||
#' )
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' # Complete app with UI and server components
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
removeUI <- function(selector,
|
||||
multiple = FALSE,
|
||||
immediate = FALSE,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
force(selector)
|
||||
force(multiple)
|
||||
force(session)
|
||||
|
||||
callback <- function() {
|
||||
session$sendRemoveUI(selector = selector,
|
||||
multiple = multiple)
|
||||
}
|
||||
|
||||
if (!immediate) session$onFlushed(callback, once = TRUE)
|
||||
else callback()
|
||||
}
|
||||
@@ -82,7 +82,7 @@ absolutePanel <- function(...,
|
||||
return(tagList(
|
||||
# IMPORTANT NOTE: If you update jqueryui, make sure you DON'T include the datepicker,
|
||||
# as it collides with our bootstrap datepicker!
|
||||
singleton(tags$head(tags$script(src='shared/jqueryui/1.10.4/jquery-ui.min.js'))),
|
||||
singleton(tags$head(tags$script(src='shared/jqueryui/jquery-ui.min.js'))),
|
||||
divTag,
|
||||
tags$script('$(".draggable").draggable();')
|
||||
))
|
||||
@@ -97,8 +97,8 @@ fixedPanel <- function(...,
|
||||
top = NULL, left = NULL, right = NULL, bottom = NULL,
|
||||
width = NULL, height = NULL,
|
||||
draggable = FALSE,
|
||||
cursor = c('move', 'default', 'inherit')) {
|
||||
cursor = c('auto', 'move', 'default', 'inherit')) {
|
||||
absolutePanel(..., top=top, left=left, right=right, bottom=bottom,
|
||||
width=width, height=height, draggable=draggable, cursor=cursor,
|
||||
width=width, height=height, draggable=draggable, cursor=match.arg(cursor),
|
||||
fixed=TRUE)
|
||||
}
|
||||
|
||||
58
R/map.R
58
R/map.R
@@ -9,58 +9,68 @@
|
||||
# Remove of unknown key does nothing
|
||||
# Setting a key twice always results in last-one-wins
|
||||
# /TESTS
|
||||
Map <- setRefClass(
|
||||
Map <- R6Class(
|
||||
'Map',
|
||||
fields = list(
|
||||
.env = 'environment'
|
||||
),
|
||||
methods = list(
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
initialize = function() {
|
||||
.env <<- new.env(parent=emptyenv())
|
||||
private$env <- new.env(parent=emptyenv())
|
||||
},
|
||||
get = function(key) {
|
||||
if (.self$containsKey(key))
|
||||
base::get(key, pos=.env, inherits=FALSE)
|
||||
env[[key]]
|
||||
},
|
||||
set = function(key, value) {
|
||||
assign(key, value, pos=.env, inherits=FALSE)
|
||||
env[[key]] <- value
|
||||
value
|
||||
},
|
||||
mget = function(keys) {
|
||||
base::mget(keys, env)
|
||||
},
|
||||
mset = function(...) {
|
||||
args <- list(...)
|
||||
for (key in names(args))
|
||||
set(key, args[[key]])
|
||||
if (length(args) == 0)
|
||||
return()
|
||||
|
||||
arg_names <- names(args)
|
||||
if (is.null(arg_names) || any(!nzchar(arg_names)))
|
||||
stop("All elements must be named")
|
||||
|
||||
list2env(args, envir = env)
|
||||
},
|
||||
remove = function(key) {
|
||||
if (.self$containsKey(key)) {
|
||||
result <- .self$get(key)
|
||||
rm(list = key, pos=.env, inherits=FALSE)
|
||||
result
|
||||
}
|
||||
if (!self$containsKey(key))
|
||||
return(NULL)
|
||||
|
||||
result <- env[[key]]
|
||||
rm(list=key, envir=env, inherits=FALSE)
|
||||
result
|
||||
},
|
||||
containsKey = function(key) {
|
||||
exists(key, where=.env, inherits=FALSE)
|
||||
exists(key, envir=env, inherits=FALSE)
|
||||
},
|
||||
keys = function() {
|
||||
ls(envir=.env, all.names=TRUE)
|
||||
# Sadly, this is much faster than ls(), because it doesn't sort the keys.
|
||||
names(as.list(env, all.names=TRUE))
|
||||
},
|
||||
values = function() {
|
||||
mget(.self$keys(), envir=.env, inherits=FALSE)
|
||||
as.list(env, all.names=TRUE)
|
||||
},
|
||||
clear = function() {
|
||||
.env <<- new.env(parent=emptyenv())
|
||||
private$env <- new.env(parent=emptyenv())
|
||||
invisible(NULL)
|
||||
},
|
||||
size = function() {
|
||||
length(.env)
|
||||
length(env)
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
env = 'environment'
|
||||
)
|
||||
)
|
||||
|
||||
as.list.Map <- function(map) {
|
||||
sapply(map$keys(),
|
||||
map$get,
|
||||
simplify=FALSE)
|
||||
map$values()
|
||||
}
|
||||
length.Map <- function(map) {
|
||||
map$size()
|
||||
|
||||
@@ -9,9 +9,11 @@ reactLogHandler <- function(req) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
sessionToken <- parseQueryString(req$QUERY_STRING)$s
|
||||
|
||||
return(httpResponse(
|
||||
status=200,
|
||||
content=list(file=renderReactLog(), owned=TRUE)
|
||||
content=list(file=renderReactLog(sessionToken), owned=TRUE)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -35,37 +37,7 @@ sessionHandler <- function(req) {
|
||||
subreq$PATH_INFO <- subpath
|
||||
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, matches[[1]][2], sep='')
|
||||
|
||||
return(shinysession$handleRequest(subreq))
|
||||
}
|
||||
|
||||
dynamicHandler <- function(filePath, dependencyFiles=filePath) {
|
||||
lastKnownTimestamps <- NA
|
||||
metaHandler <- function(req) NULL
|
||||
|
||||
if (!file.exists(filePath))
|
||||
return(metaHandler)
|
||||
|
||||
cacheContext <- CacheContext$new()
|
||||
|
||||
return (function(req) {
|
||||
# Check if we need to rebuild
|
||||
if (cacheContext$isDirty()) {
|
||||
cacheContext$reset()
|
||||
for (dep in dependencyFiles)
|
||||
cacheContext$addDependencyFile(dep)
|
||||
|
||||
clearClients()
|
||||
if (file.exists(filePath)) {
|
||||
local({
|
||||
cacheContext$with(function() {
|
||||
sys.source(filePath, envir=new.env(parent=globalenv()), keep.source=TRUE)
|
||||
})
|
||||
})
|
||||
}
|
||||
metaHandler <<- joinHandlers(.globals$clients)
|
||||
clearClients()
|
||||
}
|
||||
|
||||
return(metaHandler(req))
|
||||
withReactiveDomain(shinysession, {
|
||||
shinysession$handleRequest(subreq)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ httpResponse <- function(status = 200,
|
||||
# Make sure it's a list, not a vector
|
||||
headers <- as.list(headers)
|
||||
if (is.null(headers$`X-UA-Compatible`))
|
||||
headers$`X-UA-Compatible` <- "chrome=1"
|
||||
headers$`X-UA-Compatible` <- "IE=edge,chrome=1"
|
||||
resp <- list(status = status, content_type = content_type, content = content,
|
||||
headers = headers)
|
||||
class(resp) <- 'httpResponse'
|
||||
@@ -203,8 +203,7 @@ staticHandler <- function(root) {
|
||||
if (is.null(abs.path))
|
||||
return(NULL)
|
||||
|
||||
ext <- tools::file_ext(abs.path)
|
||||
content.type <- getContentType(ext)
|
||||
content.type <- getContentType(abs.path)
|
||||
response.content <- readBin(abs.path, 'raw', n=file.info(abs.path)$size)
|
||||
return(httpResponse(200, content.type, response.content))
|
||||
})
|
||||
@@ -222,11 +221,12 @@ staticHandler <- function(root) {
|
||||
# return value of `createHttpuvApp` to httpuv's `startServer` function.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
HandlerList <- setRefClass("HandlerList",
|
||||
fields = list(
|
||||
handlers = "list"
|
||||
),
|
||||
methods = list(
|
||||
HandlerList <- R6Class("HandlerList",
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
handlers = list(),
|
||||
|
||||
add = function(handler, key, tail = FALSE) {
|
||||
if (!is.null(handlers[[key]]))
|
||||
stop("Key ", key, " already in use")
|
||||
@@ -256,12 +256,18 @@ HandlerList <- setRefClass("HandlerList",
|
||||
)
|
||||
)
|
||||
|
||||
HandlerManager <- setRefClass("HandlerManager",
|
||||
fields = list(
|
||||
HandlerManager <- R6Class("HandlerManager",
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
handlers = "HandlerList",
|
||||
wsHandlers = "HandlerList"
|
||||
),
|
||||
methods = list(
|
||||
wsHandlers = "HandlerList",
|
||||
|
||||
initialize = function() {
|
||||
handlers <<- HandlerList$new()
|
||||
wsHandlers <<- HandlerList$new()
|
||||
},
|
||||
|
||||
addHandler = function(handler, key, tail = FALSE) {
|
||||
handlers$add(handler, key, tail)
|
||||
},
|
||||
@@ -293,9 +299,7 @@ HandlerManager <- setRefClass("HandlerManager",
|
||||
|
||||
if (reqSize > maxSize) {
|
||||
return(list(status = 413L,
|
||||
headers = list(
|
||||
'Content-Type' = 'text/plain'
|
||||
),
|
||||
headers = list('Content-Type' = 'text/plain'),
|
||||
body = 'Maximum upload size exceeded'))
|
||||
}
|
||||
else {
|
||||
@@ -304,7 +308,18 @@ HandlerManager <- setRefClass("HandlerManager",
|
||||
},
|
||||
call = .httpServer(
|
||||
function (req) {
|
||||
return(handlers$invoke(req))
|
||||
withCallingHandlers(withLogErrors(handlers$invoke(req)),
|
||||
error = function(cond) {
|
||||
sanitizeErrors <- getOption('shiny.sanitize.errors', FALSE)
|
||||
if (inherits(cond, 'shiny.custom.error') || !sanitizeErrors) {
|
||||
stop(cond$message, call. = FALSE)
|
||||
} else {
|
||||
stop(paste("An error has occurred. Check your logs or",
|
||||
"contact the app author for clarification."),
|
||||
call. = FALSE)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
getOption('shiny.sharedSecret')
|
||||
),
|
||||
@@ -326,6 +341,15 @@ HandlerManager <- setRefClass("HandlerManager",
|
||||
headers=list('Content-Type' = 'text/html')))
|
||||
}
|
||||
|
||||
# Catch HEAD requests. For the purposes of handler functions, they
|
||||
# should be treated like GET. The difference is that they shouldn't
|
||||
# return a body in the http response.
|
||||
head_request <- FALSE
|
||||
if (identical(req$REQUEST_METHOD, "HEAD")) {
|
||||
head_request <- TRUE
|
||||
req$REQUEST_METHOD <- "GET"
|
||||
}
|
||||
|
||||
response <- handler(req)
|
||||
if (is.null(response))
|
||||
response <- httpResponse(404, content="<h1>Not Found</h1>")
|
||||
@@ -335,9 +359,21 @@ HandlerManager <- setRefClass("HandlerManager",
|
||||
headers$'Content-Type' <- response$content_type
|
||||
|
||||
response <- filter(req, response)
|
||||
return(list(status=response$status,
|
||||
body=response$content,
|
||||
headers=headers))
|
||||
if (head_request) {
|
||||
headers$`Content-Length` <- nchar(response$content, type = "bytes")
|
||||
return(list(
|
||||
status = response$status,
|
||||
body = "",
|
||||
headers = headers
|
||||
))
|
||||
} else {
|
||||
return(list(
|
||||
status = response$status,
|
||||
body = response$content,
|
||||
headers = headers
|
||||
))
|
||||
}
|
||||
|
||||
} else {
|
||||
# Assume it's a Rook-compatible response
|
||||
return(response)
|
||||
|
||||
173
R/modal.R
Normal file
173
R/modal.R
Normal file
@@ -0,0 +1,173 @@
|
||||
#' Show or remove a modal dialog
|
||||
#'
|
||||
#' This causes a modal dialog to be displayed in the client browser, and is
|
||||
#' typically used with \code{\link{modalDialog}}.
|
||||
#'
|
||||
#' @param ui UI content to show in the modal.
|
||||
#' @param session The \code{session} object passed to function given to
|
||||
#' \code{shinyServer}.
|
||||
#'
|
||||
#' @seealso \code{\link{modalDialog}} for examples.
|
||||
#' @export
|
||||
showModal <- function(ui, session = getDefaultReactiveDomain()) {
|
||||
res <- processDeps(ui, session)
|
||||
|
||||
session$sendModal("show",
|
||||
list(
|
||||
html = res$html,
|
||||
deps = res$deps
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname showModal
|
||||
#' @export
|
||||
removeModal <- function(session = getDefaultReactiveDomain()) {
|
||||
session$sendModal("remove", NULL)
|
||||
}
|
||||
|
||||
|
||||
#' Create a modal dialog UI
|
||||
#'
|
||||
#' This creates the UI for a modal dialog, using Bootstrap's modal class. Modals
|
||||
#' are typically used for showing important messages, or for presenting UI that
|
||||
#' requires input from the user, such as a username and password input.
|
||||
#'
|
||||
#' @param ... UI elements for the body of the modal dialog box.
|
||||
#' @param title An optional title for the dialog.
|
||||
#' @param footer UI for footer. Use \code{NULL} for no footer.
|
||||
#' @param easyClose If \code{TRUE}, the modal dialog can be dismissed by
|
||||
#' clicking outside the dialog box, or be pressing the Escape key. If
|
||||
#' \code{FALSE} (the default), the modal dialog can't be dismissed in those
|
||||
#' ways; instead it must be dismissed by clicking on the dismiss button, or
|
||||
#' from a call to \code{\link{removeModal}} on the server.
|
||||
#'
|
||||
#' @examples
|
||||
#' if (interactive()) {
|
||||
#' # Display an important message that can be dismissed only by clicking the
|
||||
#' # dismiss button.
|
||||
#' shinyApp(
|
||||
#' ui = basicPage(
|
||||
#' actionButton("show", "Show modal dialog")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' observeEvent(input$show, {
|
||||
#' showModal(modalDialog(
|
||||
#' title = "Important message",
|
||||
#' "This is an important message!"
|
||||
#' ))
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#'
|
||||
#' # Display a message that can be dismissed by clicking outside the modal dialog,
|
||||
#' # or by pressing Esc.
|
||||
#' shinyApp(
|
||||
#' ui = basicPage(
|
||||
#' actionButton("show", "Show modal dialog")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' observeEvent(input$show, {
|
||||
#' showModal(modalDialog(
|
||||
#' title = "Somewhat important message",
|
||||
#' "This is a somewhat important message.",
|
||||
#' easyClose = TRUE,
|
||||
#' footer = NULL
|
||||
#' ))
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#'
|
||||
# Display a modal that requires valid input before continuing.
|
||||
#' shinyApp(
|
||||
#' ui = basicPage(
|
||||
#' actionButton("show", "Show modal dialog"),
|
||||
#' verbatimTextOutput("dataInfo")
|
||||
#' ),
|
||||
#'
|
||||
#' server = function(input, output) {
|
||||
#' # reactiveValues object for storing current data set.
|
||||
#' vals <- reactiveValues(data = NULL)
|
||||
#'
|
||||
#' # Return the UI for a modal dialog with data selection input. If 'failed' is
|
||||
#' # TRUE, then display a message that the previous value was invalid.
|
||||
#' dataModal <- function(failed = FALSE) {
|
||||
#' modalDialog(
|
||||
#' textInput("dataset", "Choose data set",
|
||||
#' placeholder = 'Try "mtcars" or "abc"'
|
||||
#' ),
|
||||
#' span('(Try the name of a valid data object like "mtcars", ',
|
||||
#' 'then a name of a non-existent object like "abc")'),
|
||||
#' if (failed)
|
||||
#' div(tags$b("Invalid name of data object", style = "color: red;")),
|
||||
#'
|
||||
#' footer = tagList(
|
||||
#' modalButton("Cancel"),
|
||||
#' actionButton("ok", "OK")
|
||||
#' )
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
#' # Show modal when button is clicked.
|
||||
#' observeEvent(input$show, {
|
||||
#' showModal(dataModal())
|
||||
#' })
|
||||
#'
|
||||
#' # When OK button is pressed, attempt to load the data set. If successful,
|
||||
#' # remove the modal. If not show another modal, but this time with a failure
|
||||
#' # message.
|
||||
#' observeEvent(input$ok, {
|
||||
#' # Check that data object exists and is data frame.
|
||||
#' if (exists(input$dataset) && is.data.frame(get(input$dataset))) {
|
||||
#' vals$data <- get(input$dataset)
|
||||
#' removeModal()
|
||||
#' } else {
|
||||
#' showModal(dataModal(failed = TRUE))
|
||||
#' }
|
||||
#' })
|
||||
#'
|
||||
#' # Display information about selected data
|
||||
#' output$dataInfo <- renderPrint({
|
||||
#' if (is.null(vals$data))
|
||||
#' "No data selected"
|
||||
#' else
|
||||
#' summary(vals$data)
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#' @export
|
||||
modalDialog <- function(..., title = NULL, footer = modalButton("Dismiss"),
|
||||
easyClose = FALSE) {
|
||||
|
||||
div(id = "shiny-modal", class = "modal fade", tabindex = "-1",
|
||||
`data-backdrop` = if (!easyClose) "static",
|
||||
`data-keyboard` = if (!easyClose) "false",
|
||||
|
||||
div(class = "modal-dialog",
|
||||
div(class = "modal-content",
|
||||
if (!is.null(title)) div(class = "modal-header",
|
||||
tags$h4(class = "modal-title", title)
|
||||
),
|
||||
div(class = "modal-body", ...),
|
||||
if (!is.null(footer)) div(class = "modal-footer", footer)
|
||||
)
|
||||
),
|
||||
tags$script("$('#shiny-modal').modal().focus();")
|
||||
)
|
||||
}
|
||||
|
||||
#' Create a button for a modal dialog
|
||||
#'
|
||||
#' When clicked, a \code{modalButton} will dismiss the modal dialog.
|
||||
#'
|
||||
#' @inheritParams actionButton
|
||||
#' @seealso \code{\link{modalDialog}} for examples.
|
||||
#' @export
|
||||
modalButton <- function(label, icon = NULL) {
|
||||
tags$button(type = "button", class = "btn btn-default",
|
||||
`data-dismiss` = "modal", validateIcon(icon), label
|
||||
)
|
||||
}
|
||||
57
R/modules.R
Normal file
57
R/modules.R
Normal file
@@ -0,0 +1,57 @@
|
||||
# Creates an object whose $ and $<- pass through to the parent
|
||||
# session, unless the name is matched in ..., in which case
|
||||
# that value is returned instead. (See Decorator pattern.)
|
||||
createSessionProxy <- function(parentSession, ...) {
|
||||
e <- new.env(parent = emptyenv())
|
||||
e$parent <- parentSession
|
||||
e$overrides <- list(...)
|
||||
|
||||
structure(
|
||||
e,
|
||||
class = "session_proxy"
|
||||
)
|
||||
}
|
||||
|
||||
#' @export
|
||||
`$.session_proxy` <- function(x, name) {
|
||||
if (name %in% names(x[["overrides"]]))
|
||||
x[["overrides"]][[name]]
|
||||
else
|
||||
x[["parent"]][[name]]
|
||||
}
|
||||
|
||||
#' @export
|
||||
`$<-.session_proxy` <- function(x, name, value) {
|
||||
x[["parent"]][[name]] <- value
|
||||
x
|
||||
}
|
||||
|
||||
#' Invoke a Shiny module
|
||||
#'
|
||||
#' Shiny's module feature lets you break complicated UI and server logic into
|
||||
#' smaller, self-contained pieces. Compared to large monolithic Shiny apps,
|
||||
#' modules are easier to reuse and easier to reason about. See the article at
|
||||
#' \url{http://shiny.rstudio.com/articles/modules.html} to learn more.
|
||||
#'
|
||||
#' @param module A Shiny module server function
|
||||
#' @param id An ID string that corresponds with the ID used to call the module's
|
||||
#' UI function
|
||||
#' @param ... Additional parameters to pass to module server function
|
||||
#' @param session Session from which to make a child scope (the default should
|
||||
#' almost always be used)
|
||||
#'
|
||||
#' @return The return value, if any, from executing the module server function
|
||||
#' @seealso \url{http://shiny.rstudio.com/articles/modules.html}
|
||||
#'
|
||||
#' @export
|
||||
callModule <- function(module, id, ..., session = getDefaultReactiveDomain()) {
|
||||
childScope <- session$makeScope(id)
|
||||
|
||||
withReactiveDomain(childScope, {
|
||||
if (!is.function(module)) {
|
||||
stop("module argument must be a function")
|
||||
}
|
||||
|
||||
module(childScope$input, childScope$output, childScope, ...)
|
||||
})
|
||||
}
|
||||
106
R/notifications.R
Normal file
106
R/notifications.R
Normal file
@@ -0,0 +1,106 @@
|
||||
#' Show or remove a notification
|
||||
#'
|
||||
#' These functions show and remove notifications in a Shiny application.
|
||||
#'
|
||||
#' @param ui Content of message.
|
||||
#' @param action Message content that represents an action. For example, this
|
||||
#' could be a link that the user can click on. This is separate from \code{ui}
|
||||
#' so customized layouts can handle the main notification content separately
|
||||
#' from action content.
|
||||
#' @param duration Number of seconds to display the message before it
|
||||
#' disappears. Use \code{NULL} to make the message not automatically
|
||||
#' disappear.
|
||||
#' @param closeButton If \code{TRUE}, display a button which will make the
|
||||
#' notification disappear when clicked. If \code{FALSE} do not display.
|
||||
#' @param id An ID string. This can be used to change the contents of an
|
||||
#' existing message with \code{showNotification}, or to remove it with
|
||||
#' \code{removeNotification}. If not provided, one will be generated
|
||||
#' automatically. If an ID is provided and there does not currently exist a
|
||||
#' notification with that ID, a new notification will be created with that ID.
|
||||
#' @param type A string which controls the color of the notification. One of
|
||||
#' "default" (gray), "message" (blue), "warning" (yellow), or "error" (red).
|
||||
#' @param session Session object to send notification to.
|
||||
#'
|
||||
#' @return An ID for the notification.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # Show a message when button is clicked
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' actionButton("show", "Show")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' observeEvent(input$show, {
|
||||
#' showNotification("Message text",
|
||||
#' action = a(href = "javascript:location.reload();", "Reload page")
|
||||
#' )
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' # App with show and remove buttons
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' actionButton("show", "Show"),
|
||||
#' actionButton("remove", "Remove")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' # A queue of notification IDs
|
||||
#' ids <- character(0)
|
||||
#' # A counter
|
||||
#' n <- 0
|
||||
#'
|
||||
#' observeEvent(input$show, {
|
||||
#' # Save the ID for removal later
|
||||
#' id <- showNotification(paste("Message", n), duration = NULL)
|
||||
#' ids <<- c(ids, id)
|
||||
#' n <<- n + 1
|
||||
#' })
|
||||
#'
|
||||
#' observeEvent(input$remove, {
|
||||
#' if (length(ids) > 0)
|
||||
#' removeNotification(ids[1])
|
||||
#' ids <<- ids[-1]
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#' @export
|
||||
showNotification <- function(ui, action = NULL, duration = 5,
|
||||
closeButton = TRUE, id = NULL,
|
||||
type = c("default", "message", "warning", "error"),
|
||||
session = getDefaultReactiveDomain())
|
||||
{
|
||||
|
||||
if (is.null(id))
|
||||
id <- randomID()
|
||||
|
||||
res <- processDeps(ui, session)
|
||||
actionRes <- processDeps(action, session)
|
||||
|
||||
session$sendNotification("show",
|
||||
list(
|
||||
html = res$html,
|
||||
action = actionRes$html,
|
||||
deps = c(res$deps, actionRes$deps),
|
||||
duration = if (!is.null(duration)) duration * 1000,
|
||||
closeButton = closeButton,
|
||||
id = id,
|
||||
type = match.arg(type)
|
||||
)
|
||||
)
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
#' @rdname showNotification
|
||||
#' @export
|
||||
removeNotification <- function(id = NULL, session = getDefaultReactiveDomain()) {
|
||||
if (is.null(id)) {
|
||||
stop("id is required.")
|
||||
}
|
||||
session$sendNotification("remove", id)
|
||||
id
|
||||
}
|
||||
@@ -4,15 +4,19 @@
|
||||
# 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 <- R6Class(
|
||||
'PriorityQueue',
|
||||
fields = list(
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
# Keys are priorities, values are subqueues (implemented as list)
|
||||
.itemsByPriority = 'Map',
|
||||
# Sorted vector (largest first)
|
||||
.priorities = 'numeric'
|
||||
),
|
||||
methods = list(
|
||||
.priorities = numeric(0),
|
||||
|
||||
initialize = function() {
|
||||
.itemsByPriority <<- Map$new()
|
||||
},
|
||||
# Enqueue an item, with the given priority level (must be integer). Higher
|
||||
# priority numbers are dequeued earlier than lower.
|
||||
enqueue = function(item, priority) {
|
||||
|
||||
297
R/progress.R
Normal file
297
R/progress.R
Normal file
@@ -0,0 +1,297 @@
|
||||
#' Reporting progress (object-oriented API)
|
||||
#'
|
||||
#' Reports progress to the user during long-running operations.
|
||||
#'
|
||||
#' This package exposes two distinct programming APIs for working with
|
||||
#' progress. \code{\link{withProgress}} and \code{\link{setProgress}}
|
||||
#' together provide a simple function-based interface, while the
|
||||
#' \code{Progress} reference class provides an object-oriented API.
|
||||
#'
|
||||
#' Instantiating a \code{Progress} object causes a progress panel to be
|
||||
#' created, and it will be displayed the first time the \code{set}
|
||||
#' method is called. Calling \code{close} will cause the progress panel
|
||||
#' to be removed.
|
||||
#'
|
||||
#' \strong{Methods}
|
||||
#' \describe{
|
||||
#' \item{\code{initialize(session, min = 0, max = 1)}}{
|
||||
#' Creates a new progress panel (but does not display it).
|
||||
#' }
|
||||
#' \item{\code{set(value = NULL, message = NULL, detail = NULL)}}{
|
||||
#' Updates the progress panel. When called the first time, the
|
||||
#' progress panel is displayed.
|
||||
#' }
|
||||
#' \item{\code{inc(amount = 0.1, message = NULL, detail = NULL)}}{
|
||||
#' Like \code{set}, this updates the progress panel. The difference is
|
||||
#' that \code{inc} increases the progress bar by \code{amount}, instead
|
||||
#' of setting it to a specific value.
|
||||
#' }
|
||||
#' \item{\code{close()}}{
|
||||
#' Removes the progress panel. Future calls to \code{set} and
|
||||
#' \code{close} will be ignored.
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#' @param session The Shiny session object, as provided by
|
||||
#' \code{shinyServer} to the server function.
|
||||
#' @param min The value that represents the starting point of the
|
||||
#' progress bar. Must be less tham \code{max}.
|
||||
#' @param max The value that represents the end of the progress bar.
|
||||
#' Must be greater than \code{min}.
|
||||
#' @param message A single-element character vector; the message to be
|
||||
#' displayed to the user, or \code{NULL} to hide the current message
|
||||
#' (if any).
|
||||
#' @param detail A single-element character vector; the detail message
|
||||
#' to be displayed to the user, or \code{NULL} to hide the current
|
||||
#' detail message (if any). The detail message will be shown with a
|
||||
#' de-emphasized appearance relative to \code{message}.
|
||||
#' @param value A numeric value at which to set
|
||||
#' the progress bar, relative to \code{min} and \code{max}.
|
||||
#' \code{NULL} hides the progress bar, if it is currently visible.
|
||||
#' @param amount Single-element numeric vector; the value at which to set
|
||||
#' the progress bar, relative to \code{min} and \code{max}.
|
||||
#' \code{NULL} hides the progress bar, if it is currently visible.
|
||||
#' @param amount For the \code{inc()} method, a numeric value to increment the
|
||||
#' progress bar.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' plotOutput("plot")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' output$plot <- renderPlot({
|
||||
#' progress <- Progress$new(session, min=1, max=15)
|
||||
#' on.exit(progress$close())
|
||||
#'
|
||||
#' progress$set(message = 'Calculation in progress',
|
||||
#' detail = 'This may take a while...')
|
||||
#'
|
||||
#' for (i in 1:15) {
|
||||
#' progress$set(value = i)
|
||||
#' Sys.sleep(0.5)
|
||||
#' }
|
||||
#' plot(cars)
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @seealso \code{\link{withProgress}}
|
||||
#' @format NULL
|
||||
#' @usage NULL
|
||||
#' @export
|
||||
Progress <- R6Class(
|
||||
'Progress',
|
||||
portable = TRUE,
|
||||
public = list(
|
||||
|
||||
initialize = function(session = getDefaultReactiveDomain(), min = 0, max = 1) {
|
||||
if (is.null(session$progressStack))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
private$session <- session
|
||||
private$id <- randomID()
|
||||
private$min <- min
|
||||
private$max <- max
|
||||
private$value <- NULL
|
||||
private$closed <- FALSE
|
||||
|
||||
session$sendProgress('open', list(id = private$id))
|
||||
},
|
||||
|
||||
set = function(value = NULL, message = NULL, detail = NULL) {
|
||||
if (private$closed) {
|
||||
warning("Attempting to set progress, but progress already closed.")
|
||||
return()
|
||||
}
|
||||
|
||||
if (is.null(value) || is.na(value)) {
|
||||
value <- NULL
|
||||
} else {
|
||||
# Normalize value to number between 0 and 1
|
||||
value <- min(1, max(0, (value - private$min) / (private$max - private$min)))
|
||||
}
|
||||
|
||||
private$value <- value
|
||||
|
||||
data <- dropNulls(list(
|
||||
id = private$id,
|
||||
message = message,
|
||||
detail = detail,
|
||||
value = value
|
||||
))
|
||||
|
||||
private$session$sendProgress('update', data)
|
||||
},
|
||||
|
||||
inc = function(amount = 0.1, message = NULL, detail = NULL) {
|
||||
value <- min(self$getValue() + amount, private$max)
|
||||
self$set(value, message, detail)
|
||||
},
|
||||
|
||||
getMin = function() private$min,
|
||||
|
||||
getMax = function() private$max,
|
||||
|
||||
# Return value (not the normalized 0-1 value, but in the original range)
|
||||
getValue = function() {
|
||||
private$value * (private$max - private$min) + private$min
|
||||
},
|
||||
|
||||
close = function() {
|
||||
if (private$closed) {
|
||||
warning("Attempting to close progress, but progress already closed.")
|
||||
return()
|
||||
}
|
||||
|
||||
private$session$sendProgress('close', list(id = private$id))
|
||||
private$closed <- TRUE
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
session = 'environment',
|
||||
id = character(0),
|
||||
min = numeric(0),
|
||||
max = numeric(0),
|
||||
value = NULL,
|
||||
closed = logical(0)
|
||||
)
|
||||
)
|
||||
|
||||
#' Reporting progress (functional API)
|
||||
#'
|
||||
#' Reports progress to the user during long-running operations.
|
||||
#'
|
||||
#' This package exposes two distinct programming APIs for working with progress.
|
||||
#' Using \code{withProgress} with \code{incProgress} or \code{setProgress}
|
||||
#' provide a simple function-based interface, while the \code{\link{Progress}}
|
||||
#' reference class provides an object-oriented API.
|
||||
#'
|
||||
#' Use \code{withProgress} to wrap the scope of your work; doing so will cause a
|
||||
#' new progress panel to be created, and it will be displayed the first time
|
||||
#' \code{incProgress} or \code{setProgress} are called. When \code{withProgress}
|
||||
#' exits, the corresponding progress panel will be removed.
|
||||
#'
|
||||
#' The \code{incProgress} function increments the status bar by a specified
|
||||
#' amount, whereas the \code{setProgress} function sets it to a specific value,
|
||||
#' and can also set the text displayed.
|
||||
#'
|
||||
#' Generally, \code{withProgress}/\code{incProgress}/\code{setProgress} should
|
||||
#' be sufficient; the exception is if the work to be done is asynchronous (this
|
||||
#' is not common) or otherwise cannot be encapsulated by a single scope. In that
|
||||
#' case, you can use the \code{Progress} reference class.
|
||||
#'
|
||||
#' @param session The Shiny session object, as provided by \code{shinyServer} to
|
||||
#' the server function. The default is to automatically find the session by
|
||||
#' using the current reactive domain.
|
||||
#' @param expr The work to be done. This expression should contain calls to
|
||||
#' \code{setProgress}.
|
||||
#' @param min The value that represents the starting point of the progress bar.
|
||||
#' Must be less tham \code{max}. Default is 0.
|
||||
#' @param max The value that represents the end of the progress bar. Must be
|
||||
#' greater than \code{min}. Default is 1.
|
||||
#' @param amount For \code{incProgress}, the amount to increment the status bar.
|
||||
#' Default is 0.1.
|
||||
#' @param env The environment in which \code{expr} should be evaluated.
|
||||
#' @param quoted Whether \code{expr} is a quoted expression (this is not
|
||||
#' common).
|
||||
#' @param message A single-element character vector; the message to be displayed
|
||||
#' to the user, or \code{NULL} to hide the current message (if any).
|
||||
#' @param detail A single-element character vector; the detail message to be
|
||||
#' displayed to the user, or \code{NULL} to hide the current detail message
|
||||
#' (if any). The detail message will be shown with a de-emphasized appearance
|
||||
#' relative to \code{message}.
|
||||
#' @param value Single-element numeric vector; the value at which to set the
|
||||
#' progress bar, relative to \code{min} and \code{max}. \code{NULL} hides the
|
||||
#' progress bar, if it is currently visible.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' plotOutput("plot")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' output$plot <- renderPlot({
|
||||
#' withProgress(message = 'Calculation in progress',
|
||||
#' detail = 'This may take a while...', value = 0, {
|
||||
#' for (i in 1:15) {
|
||||
#' incProgress(1/15)
|
||||
#' Sys.sleep(0.25)
|
||||
#' }
|
||||
#' })
|
||||
#' plot(cars)
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @seealso \code{\link{Progress}}
|
||||
#' @rdname withProgress
|
||||
#' @export
|
||||
withProgress <- function(expr, min = 0, max = 1,
|
||||
value = min + (max - min) * 0.1,
|
||||
message = NULL, detail = NULL,
|
||||
session = getDefaultReactiveDomain(),
|
||||
env = parent.frame(), quoted = FALSE) {
|
||||
|
||||
if (!quoted)
|
||||
expr <- substitute(expr)
|
||||
|
||||
if (is.null(session$progressStack))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
p <- Progress$new(session, min = min, max = max)
|
||||
|
||||
session$progressStack$push(p)
|
||||
on.exit({
|
||||
session$progressStack$pop()
|
||||
p$close()
|
||||
})
|
||||
|
||||
p$set(value, message, detail)
|
||||
|
||||
eval(expr, env)
|
||||
}
|
||||
|
||||
#' @rdname withProgress
|
||||
#' @export
|
||||
setProgress <- function(value = NULL, message = NULL, detail = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
if (is.null(session$progressStack))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
if (session$progressStack$size() == 0) {
|
||||
warning('setProgress was called outside of withProgress; ignoring')
|
||||
return()
|
||||
}
|
||||
|
||||
session$progressStack$peek()$set(value, message, detail)
|
||||
invisible()
|
||||
}
|
||||
|
||||
#' @rdname withProgress
|
||||
#' @export
|
||||
incProgress <- function(amount = 0.1, message = NULL, detail = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
if (is.null(session$progressStack))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
if (session$progressStack$size() == 0) {
|
||||
warning('incProgress was called outside of withProgress; ignoring')
|
||||
return()
|
||||
}
|
||||
|
||||
p <- session$progressStack$peek()
|
||||
p$inc(amount, message, detail)
|
||||
invisible()
|
||||
}
|
||||
70
R/react.R
70
R/react.R
@@ -1,19 +1,17 @@
|
||||
Context <- setRefClass(
|
||||
Context <- R6Class(
|
||||
'Context',
|
||||
fields = list(
|
||||
id = 'character',
|
||||
.label = 'character', # For debug purposes
|
||||
.invalidated = 'logical',
|
||||
.invalidateCallbacks = 'list',
|
||||
.flushCallbacks = 'list',
|
||||
.domain = 'ANY'
|
||||
),
|
||||
methods = list(
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
id = character(0),
|
||||
.label = character(0), # For debug purposes
|
||||
.invalidated = FALSE,
|
||||
.invalidateCallbacks = list(),
|
||||
.flushCallbacks = list(),
|
||||
.domain = NULL,
|
||||
|
||||
initialize = function(domain, label='', type='other', prevId='') {
|
||||
id <<- .getReactiveEnvironment()$nextId()
|
||||
.invalidated <<- FALSE
|
||||
.invalidateCallbacks <<- list()
|
||||
.flushCallbacks <<- list()
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.graphCreateContext(id, label, type, prevId, domain)
|
||||
@@ -24,7 +22,7 @@ Context <- setRefClass(
|
||||
env <- .getReactiveEnvironment()
|
||||
.graphEnterContext(id)
|
||||
tryCatch(
|
||||
env$runWith(.self, func),
|
||||
env$runWith(self, func),
|
||||
finally = .graphExitContext(id)
|
||||
)
|
||||
})
|
||||
@@ -56,7 +54,10 @@ Context <- setRefClass(
|
||||
addPendingFlush = function(priority) {
|
||||
"Tell the reactive environment that this context should be flushed the
|
||||
next time flushReact() called."
|
||||
.getReactiveEnvironment()$addPendingFlush(.self, priority)
|
||||
if (!is.null(.domain)) {
|
||||
.domain$incrementBusyCount()
|
||||
}
|
||||
.getReactiveEnvironment()$addPendingFlush(self, priority)
|
||||
},
|
||||
onFlush = function(func) {
|
||||
"Register a function to be called when this context is flushed."
|
||||
@@ -64,33 +65,32 @@ Context <- setRefClass(
|
||||
},
|
||||
executeFlushCallbacks = function() {
|
||||
"For internal use only."
|
||||
lapply(.flushCallbacks, function(func) {
|
||||
withCallingHandlers({
|
||||
func()
|
||||
}, warning = function(e) {
|
||||
# TODO: Callbacks in app
|
||||
}, error = function(e) {
|
||||
# TODO: Callbacks in app
|
||||
})
|
||||
|
||||
on.exit({
|
||||
if (!is.null(.domain)) {
|
||||
.domain$decrementBusyCount()
|
||||
}
|
||||
}, add = TRUE)
|
||||
|
||||
lapply(.flushCallbacks, function(flushCallback) {
|
||||
flushCallback()
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
ReactiveEnvironment <- setRefClass(
|
||||
ReactiveEnvironment <- R6Class(
|
||||
'ReactiveEnvironment',
|
||||
fields = list(
|
||||
.currentContext = 'ANY',
|
||||
.nextId = 'integer',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.currentContext = NULL,
|
||||
.nextId = 0L,
|
||||
.pendingFlush = 'PriorityQueue',
|
||||
.inFlush = 'logical'
|
||||
),
|
||||
methods = list(
|
||||
.inFlush = FALSE,
|
||||
|
||||
initialize = function() {
|
||||
.currentContext <<- NULL
|
||||
.nextId <<- 0L
|
||||
.pendingFlush <<- PriorityQueue$new()
|
||||
.inFlush <<- FALSE
|
||||
},
|
||||
nextId = function() {
|
||||
.nextId <<- .nextId + 1L
|
||||
@@ -108,11 +108,11 @@ ReactiveEnvironment <- setRefClass(
|
||||
}
|
||||
return(.currentContext)
|
||||
},
|
||||
runWith = function(ctx, func) {
|
||||
runWith = function(ctx, contextFunc) {
|
||||
old.ctx <- .currentContext
|
||||
.currentContext <<- ctx
|
||||
on.exit(.currentContext <<- old.ctx)
|
||||
shinyCallingHandlers(func())
|
||||
contextFunc()
|
||||
},
|
||||
addPendingFlush = function(ctx, priority) {
|
||||
.pendingFlush$enqueue(ctx, priority)
|
||||
|
||||
@@ -41,12 +41,13 @@ NULL
|
||||
# `reactlog(logEntry)`.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
createMockDomain <- function() {
|
||||
callbacks <- list()
|
||||
createMockDomain <- function(token = createUniqueId(4)) {
|
||||
callbacks <- Callbacks$new()
|
||||
ended <- FALSE
|
||||
domain <- new.env(parent = emptyenv())
|
||||
domain$token <- token
|
||||
domain$onEnded <- function(callback) {
|
||||
callbacks <<- c(callbacks, callback)
|
||||
return(callbacks$register(callback))
|
||||
}
|
||||
domain$isEnded <- function() {
|
||||
ended
|
||||
@@ -55,10 +56,12 @@ createMockDomain <- function() {
|
||||
domain$end <- function() {
|
||||
if (!ended) {
|
||||
ended <<- TRUE
|
||||
lapply(callbacks, do.call, list())
|
||||
callbacks$invoke()
|
||||
}
|
||||
invisible()
|
||||
}
|
||||
domain$incrementBusyCount <- function() NULL
|
||||
domain$decrementBusyCount <- function() NULL
|
||||
return(domain)
|
||||
}
|
||||
|
||||
|
||||
633
R/reactives.R
633
R/reactives.R
@@ -1,12 +1,16 @@
|
||||
#' @include utils.R
|
||||
NULL
|
||||
|
||||
Dependents <- setRefClass(
|
||||
Dependents <- R6Class(
|
||||
'Dependents',
|
||||
fields = list(
|
||||
.dependents = 'Map'
|
||||
),
|
||||
methods = list(
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.dependents = 'Map',
|
||||
|
||||
initialize = function() {
|
||||
.dependents <<- Map$new()
|
||||
},
|
||||
register = function(depId=NULL, depLabel=NULL) {
|
||||
ctx <- .getReactiveEnvironment()$currentContext()
|
||||
if (!.dependents$containsKey(ctx$id)) {
|
||||
@@ -36,11 +40,12 @@ Dependents <- setRefClass(
|
||||
|
||||
# ReactiveValues ------------------------------------------------------------
|
||||
|
||||
ReactiveValues <- setRefClass(
|
||||
ReactiveValues <- R6Class(
|
||||
'ReactiveValues',
|
||||
fields = list(
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
# For debug purposes
|
||||
.label = 'character',
|
||||
.label = character(0),
|
||||
.values = 'environment',
|
||||
.dependents = 'environment',
|
||||
# Dependents for the list of all names, including hidden
|
||||
@@ -48,15 +53,17 @@ ReactiveValues <- setRefClass(
|
||||
# Dependents for all values, including hidden
|
||||
.allValuesDeps = 'Dependents',
|
||||
# Dependents for all values
|
||||
.valuesDeps = 'Dependents'
|
||||
),
|
||||
methods = list(
|
||||
.valuesDeps = 'Dependents',
|
||||
|
||||
initialize = function() {
|
||||
.label <<- paste('reactiveValues',
|
||||
p_randomInt(1000, 10000),
|
||||
sep="")
|
||||
.values <<- new.env(parent=emptyenv())
|
||||
.dependents <<- new.env(parent=emptyenv())
|
||||
.namesDeps <<- Dependents$new()
|
||||
.allValuesDeps <<- Dependents$new()
|
||||
.valuesDeps <<- Dependents$new()
|
||||
},
|
||||
get = function(key) {
|
||||
ctx <- .getReactiveEnvironment()$currentContext()
|
||||
@@ -114,7 +121,7 @@ ReactiveValues <- setRefClass(
|
||||
mset = function(lst) {
|
||||
lapply(base::names(lst),
|
||||
function(name) {
|
||||
.self$set(name, lst[[name]])
|
||||
self$set(name, lst[[name]])
|
||||
})
|
||||
},
|
||||
names = function() {
|
||||
@@ -145,11 +152,12 @@ ReactiveValues <- setRefClass(
|
||||
|
||||
#' Create an object for storing reactive values
|
||||
#'
|
||||
#' This function returns an object for storing reactive values. It is similar
|
||||
#' to a list, but with special capabilities for reactive programming. When you
|
||||
#' read a value from it, the calling reactive expression takes a reactive
|
||||
#' dependency on that value, and when you write to it, it notifies any reactive
|
||||
#' functions that depend on that value.
|
||||
#' This function returns an object for storing reactive values. It is similar to
|
||||
#' a list, but with special capabilities for reactive programming. When you read
|
||||
#' a value from it, the calling reactive expression takes a reactive dependency
|
||||
#' on that value, and when you write to it, it notifies any reactive functions
|
||||
#' that depend on that value. Note that values taken from the reactiveValues
|
||||
#' object are reactive, but the reactiveValues object itself is not.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Create the object with no values
|
||||
@@ -192,15 +200,28 @@ reactiveValues <- function(...) {
|
||||
values
|
||||
}
|
||||
|
||||
# Register the S3 class so that it can be used for a field in a Reference Class
|
||||
setOldClass("reactivevalues")
|
||||
checkName <- function(x) {
|
||||
if (!is.character(x) || length(x) != 1) {
|
||||
stop("Must use single string to index into reactivevalues")
|
||||
}
|
||||
}
|
||||
|
||||
# Create a reactivevalues object
|
||||
#
|
||||
# @param values A ReactiveValues object
|
||||
# @param readonly Should this object be read-only?
|
||||
.createReactiveValues <- function(values = NULL, readonly = FALSE) {
|
||||
structure(list(impl=values), class='reactivevalues', readonly=readonly)
|
||||
# @param ns A namespace function (either `identity` or `NS(namespace)`)
|
||||
.createReactiveValues <- function(values = NULL, readonly = FALSE,
|
||||
ns = identity) {
|
||||
|
||||
structure(
|
||||
list(
|
||||
impl = values,
|
||||
readonly = readonly,
|
||||
ns = ns
|
||||
),
|
||||
class='reactivevalues'
|
||||
)
|
||||
}
|
||||
|
||||
#' Checks whether an object is a reactivevalues object
|
||||
@@ -214,7 +235,8 @@ is.reactivevalues <- function(x) inherits(x, 'reactivevalues')
|
||||
|
||||
#' @export
|
||||
`$.reactivevalues` <- function(x, name) {
|
||||
.subset2(x, 'impl')$get(name)
|
||||
checkName(name)
|
||||
.subset2(x, 'impl')$get(.subset2(x, 'ns')(name))
|
||||
}
|
||||
|
||||
#' @export
|
||||
@@ -222,14 +244,12 @@ is.reactivevalues <- function(x) inherits(x, 'reactivevalues')
|
||||
|
||||
#' @export
|
||||
`$<-.reactivevalues` <- function(x, name, value) {
|
||||
if (attr(x, 'readonly')) {
|
||||
if (.subset2(x, 'readonly')) {
|
||||
stop("Attempted to assign value to a read-only reactivevalues object")
|
||||
} else if (length(name) != 1 || !is.character(name)) {
|
||||
stop("Must use single string to index into reactivevalues")
|
||||
} else {
|
||||
.subset2(x, 'impl')$set(name, value)
|
||||
x
|
||||
}
|
||||
checkName(name)
|
||||
.subset2(x, 'impl')$set(.subset2(x, 'ns')(name), value)
|
||||
x
|
||||
}
|
||||
|
||||
#' @export
|
||||
@@ -247,7 +267,13 @@ is.reactivevalues <- function(x) inherits(x, 'reactivevalues')
|
||||
|
||||
#' @export
|
||||
names.reactivevalues <- function(x) {
|
||||
.subset2(x, 'impl')$names()
|
||||
prefix <- .subset2(x, 'ns')("")
|
||||
results <- .subset2(x, 'impl')$names()
|
||||
if (nzchar(prefix)) {
|
||||
results <- results[substring(results, 1, nchar(prefix)) == prefix]
|
||||
results <- substring(results, nchar(prefix) + 1)
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
#' @export
|
||||
@@ -302,40 +328,54 @@ reactiveValuesToList <- function(x, all.names=FALSE) {
|
||||
# x[['impl']].
|
||||
#' @export
|
||||
str.reactivevalues <- function(object, indent.str = " ", ...) {
|
||||
str(unclass(object), indent.str = indent.str, ...)
|
||||
utils::str(unclass(object), indent.str = indent.str, ...)
|
||||
# Need to manually print out the class field,
|
||||
cat(indent.str, '- attr(*, "class")=', sep = "")
|
||||
str(class(object))
|
||||
utils::str(class(object))
|
||||
}
|
||||
|
||||
# Observable ----------------------------------------------------------------
|
||||
|
||||
Observable <- setRefClass(
|
||||
Observable <- R6Class(
|
||||
'Observable',
|
||||
fields = list(
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
.func = 'function',
|
||||
.label = 'character',
|
||||
.domain = 'ANY',
|
||||
.label = character(0),
|
||||
.domain = NULL,
|
||||
.dependents = 'Dependents',
|
||||
.invalidated = 'logical',
|
||||
.running = 'logical',
|
||||
.value = 'ANY',
|
||||
.visible = 'logical',
|
||||
.execCount = 'integer',
|
||||
.mostRecentCtxId = 'character'
|
||||
),
|
||||
methods = list(
|
||||
.invalidated = logical(0),
|
||||
.running = logical(0),
|
||||
.value = NULL,
|
||||
.error = FALSE,
|
||||
.visible = logical(0),
|
||||
.execCount = integer(0),
|
||||
.mostRecentCtxId = character(0),
|
||||
|
||||
initialize = function(func, label = deparse(substitute(func)),
|
||||
domain = getDefaultReactiveDomain()) {
|
||||
domain = getDefaultReactiveDomain(),
|
||||
..stacktraceon = TRUE) {
|
||||
if (length(formals(func)) > 0)
|
||||
stop("Can't make a reactive expression from a function that takes one ",
|
||||
"or more parameters; only functions without parameters can be ",
|
||||
"reactive.")
|
||||
.func <<- func
|
||||
.invalidated <<- TRUE
|
||||
.running <<- FALSE
|
||||
|
||||
# This is to make sure that the function labels that show in the profiler
|
||||
# and in stack traces doesn't contain whitespace. See
|
||||
# https://github.com/rstudio/profvis/issues/58
|
||||
if (grepl("\\s", label, perl = TRUE)) {
|
||||
funcLabel <- "<reactive>"
|
||||
} else {
|
||||
funcLabel <- paste0("<reactive:", label, ">")
|
||||
}
|
||||
|
||||
.func <<- wrapFunctionLabel(func, funcLabel,
|
||||
..stacktraceon = ..stacktraceon)
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.dependents <<- Dependents$new()
|
||||
.invalidated <<- TRUE
|
||||
.running <<- FALSE
|
||||
.execCount <<- 0L
|
||||
.mostRecentCtxId <<- ""
|
||||
},
|
||||
@@ -343,13 +383,16 @@ Observable <- setRefClass(
|
||||
.dependents$register()
|
||||
|
||||
if (.invalidated || .running) {
|
||||
.self$.updateValue()
|
||||
..stacktraceoff..(
|
||||
self$.updateValue()
|
||||
)
|
||||
}
|
||||
|
||||
.graphDependsOnId(getCurrentContext()$id, .mostRecentCtxId)
|
||||
|
||||
if (identical(class(.value), 'try-error'))
|
||||
stop(attr(.value, 'condition'))
|
||||
if (.error) {
|
||||
stop(.value)
|
||||
}
|
||||
|
||||
if (.visible)
|
||||
.value
|
||||
@@ -373,9 +416,36 @@ Observable <- setRefClass(
|
||||
on.exit(.running <<- wasRunning)
|
||||
|
||||
ctx$run(function() {
|
||||
result <- withVisible(try(shinyCallingHandlers(.func()), silent=TRUE))
|
||||
.visible <<- result$visible
|
||||
result <- withCallingHandlers(
|
||||
|
||||
{
|
||||
.error <<- FALSE
|
||||
withVisible(.func())
|
||||
},
|
||||
|
||||
error = function(cond) {
|
||||
# If an error occurs, we want to propagate the error, but we also
|
||||
# want to save a copy of it, so future callers of this reactive will
|
||||
# get the same error (i.e. the error is cached).
|
||||
|
||||
# We stripStackTrace in the next line, just in case someone
|
||||
# downstream of us (i.e. deeper into the call stack) used
|
||||
# captureStackTraces; otherwise the entire stack would always be the
|
||||
# same (i.e. you'd always see the whole stack trace of the *first*
|
||||
# time the code was run and the condition raised; there'd be no way
|
||||
# to see the stack trace of the call site that caused the cached
|
||||
# exception to be re-raised, and you need that information to figure
|
||||
# out what's triggering the re-raise).
|
||||
#
|
||||
# We use try(stop()) as an easy way to generate a try-error object
|
||||
# out of this condition.
|
||||
.value <<- cond
|
||||
.error <<- TRUE
|
||||
.visible <<- FALSE
|
||||
}
|
||||
)
|
||||
.value <<- result$value
|
||||
.visible <<- result$visible
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -386,27 +456,29 @@ Observable <- setRefClass(
|
||||
#' Wraps a normal expression to create a reactive expression. Conceptually, a
|
||||
#' reactive expression is a expression whose result will change over time.
|
||||
#'
|
||||
#' Reactive expressions are expressions that can read reactive values and call other
|
||||
#' reactive expressions. Whenever a reactive value changes, any reactive expressions
|
||||
#' that depended on it are marked as "invalidated" and will automatically
|
||||
#' re-execute if necessary. If a reactive expression is marked as invalidated, any
|
||||
#' other reactive expressions that recently called it are also marked as
|
||||
#' invalidated. In this way, invalidations ripple through the expressions that
|
||||
#' depend on each other.
|
||||
#' Reactive expressions are expressions that can read reactive values and call
|
||||
#' other reactive expressions. Whenever a reactive value changes, any reactive
|
||||
#' expressions that depended on it are marked as "invalidated" and will
|
||||
#' automatically re-execute if necessary. If a reactive expression is marked as
|
||||
#' invalidated, any other reactive expressions that recently called it are also
|
||||
#' marked as invalidated. In this way, invalidations ripple through the
|
||||
#' expressions that depend on each other.
|
||||
#'
|
||||
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{Shiny tutorial} for
|
||||
#' more information about reactive expressions.
|
||||
#'
|
||||
#' @param x For \code{reactive}, an expression (quoted or unquoted). For
|
||||
#' \code{is.reactive}, an object to test.
|
||||
#' @param env The parent environment for the reactive expression. By default, this
|
||||
#' is the calling environment, the same as when defining an ordinary
|
||||
#' @param env The parent environment for the reactive expression. By default,
|
||||
#' this is the calling environment, the same as when defining an ordinary
|
||||
#' non-reactive expression.
|
||||
#' @param quoted Is the expression quoted? By default, this is \code{FALSE}.
|
||||
#' This is useful when you want to use an expression that is stored in a
|
||||
#' variable; to do so, it must be quoted with `quote()`.
|
||||
#' variable; to do so, it must be quoted with \code{quote()}.
|
||||
#' @param label A label for the reactive expression, useful for debugging.
|
||||
#' @param domain See \link{domains}.
|
||||
#' @param ..stacktraceon Advanced use only. For stack manipulation purposes; see
|
||||
#' \code{\link{stacktrace}}.
|
||||
#' @return a function, wrapped in a S3 class "reactive"
|
||||
#'
|
||||
#' @examples
|
||||
@@ -430,17 +502,62 @@ Observable <- setRefClass(
|
||||
#'
|
||||
#' @export
|
||||
reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL,
|
||||
domain = getDefaultReactiveDomain()) {
|
||||
domain = getDefaultReactiveDomain(),
|
||||
..stacktraceon = TRUE) {
|
||||
fun <- exprToFunction(x, env, quoted)
|
||||
# Attach a label and a reference to the original user source for debugging
|
||||
if (is.null(label))
|
||||
label <- sprintf('reactive(%s)', paste(deparse(body(fun)), collapse='\n'))
|
||||
srcref <- attr(substitute(x), "srcref")
|
||||
srcref <- attr(substitute(x), "srcref", exact = TRUE)
|
||||
if (is.null(label)) {
|
||||
label <- srcrefToLabel(srcref[[1]],
|
||||
sprintf('reactive(%s)', paste(deparse(body(fun)), collapse='\n')))
|
||||
}
|
||||
if (length(srcref) >= 2) attr(label, "srcref") <- srcref[[2]]
|
||||
attr(label, "srcfile") <- srcFileOfRef(srcref[[1]])
|
||||
o <- Observable$new(fun, label, domain)
|
||||
registerDebugHook(".func", o, "Reactive")
|
||||
structure(o$getValue@.Data, observable = o, class = "reactive")
|
||||
o <- Observable$new(fun, label, domain, ..stacktraceon = ..stacktraceon)
|
||||
structure(o$getValue, observable = o, class = "reactive")
|
||||
}
|
||||
|
||||
# Given the srcref to a reactive expression, attempts to figure out what the
|
||||
# name of the reactive expression is. This isn't foolproof, as it literally
|
||||
# scans the line of code that started the reactive block and looks for something
|
||||
# that looks like assignment. If we fail, fall back to a default value (likely
|
||||
# the block of code in the body of the reactive).
|
||||
srcrefToLabel <- function(srcref, defaultLabel) {
|
||||
if (is.null(srcref))
|
||||
return(defaultLabel)
|
||||
|
||||
srcfile <- attr(srcref, "srcfile", exact = TRUE)
|
||||
if (is.null(srcfile))
|
||||
return(defaultLabel)
|
||||
|
||||
if (is.null(srcfile$lines))
|
||||
return(defaultLabel)
|
||||
|
||||
lines <- srcfile$lines
|
||||
# When pasting at the Console, srcfile$lines is not split
|
||||
if (length(lines) == 1) {
|
||||
lines <- strsplit(lines, "\n")[[1]]
|
||||
}
|
||||
|
||||
if (length(lines) < srcref[1]) {
|
||||
return(defaultLabel)
|
||||
}
|
||||
|
||||
firstLine <- substring(lines[srcref[1]], 1, srcref[2] - 1)
|
||||
|
||||
m <- regexec("(.*)(<-|=)\\s*reactive\\s*\\($", firstLine)
|
||||
if (m[[1]][1] == -1) {
|
||||
return(defaultLabel)
|
||||
}
|
||||
sym <- regmatches(firstLine, m)[[1]][2]
|
||||
res <- try(parse(text = sym), silent = TRUE)
|
||||
if (inherits(res, "try-error"))
|
||||
return(defaultLabel)
|
||||
|
||||
if (length(res) != 1)
|
||||
return(defaultLabel)
|
||||
|
||||
return(as.character(res))
|
||||
}
|
||||
|
||||
#' @export
|
||||
@@ -455,8 +572,8 @@ is.reactive <- function(x) inherits(x, "reactive")
|
||||
|
||||
# Return the number of times that a reactive expression or observer has been run
|
||||
execCount <- function(x) {
|
||||
if (is.function(x))
|
||||
return(environment(x)$.execCount)
|
||||
if (is.reactive(x))
|
||||
return(attr(x, "observable")$.execCount)
|
||||
else if (inherits(x, 'Observer'))
|
||||
return(x$.execCount)
|
||||
else
|
||||
@@ -465,33 +582,45 @@ execCount <- function(x) {
|
||||
|
||||
# Observer ------------------------------------------------------------------
|
||||
|
||||
Observer <- setRefClass(
|
||||
Observer <- R6Class(
|
||||
'Observer',
|
||||
fields = list(
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
.func = 'function',
|
||||
.label = 'character',
|
||||
.label = character(0),
|
||||
.domain = 'ANY',
|
||||
.priority = 'numeric',
|
||||
.autoDestroy = 'logical',
|
||||
.invalidateCallbacks = 'list',
|
||||
.execCount = 'integer',
|
||||
.priority = numeric(0),
|
||||
.autoDestroy = logical(0),
|
||||
.autoDestroyHandle = 'ANY',
|
||||
.invalidateCallbacks = list(),
|
||||
.execCount = integer(0),
|
||||
.onResume = 'function',
|
||||
.suspended = 'logical',
|
||||
.destroyed = 'logical',
|
||||
.prevId = 'character'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function(func, label, suspended = FALSE, priority = 0,
|
||||
.suspended = logical(0),
|
||||
.destroyed = logical(0),
|
||||
.prevId = character(0),
|
||||
.ctx = NULL,
|
||||
|
||||
initialize = function(observerFunc, label, suspended = FALSE, priority = 0,
|
||||
domain = getDefaultReactiveDomain(),
|
||||
autoDestroy = TRUE) {
|
||||
if (length(formals(func)) > 0)
|
||||
autoDestroy = TRUE, ..stacktraceon = TRUE) {
|
||||
if (length(formals(observerFunc)) > 0)
|
||||
stop("Can't make an observer from a function that takes parameters; ",
|
||||
"only functions without parameters can be reactive.")
|
||||
|
||||
.func <<- func
|
||||
registerDebugHook("observerFunc", environment(), label)
|
||||
.func <<- function() {
|
||||
tryCatch(
|
||||
if (..stacktraceon)
|
||||
..stacktraceon..(observerFunc())
|
||||
else
|
||||
observerFunc(),
|
||||
validation = function(e) {
|
||||
# It's OK for a validation error to cause an observer to stop
|
||||
# running
|
||||
}
|
||||
)
|
||||
}
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.autoDestroy <<- autoDestroy
|
||||
.priority <<- normalizePriority(priority)
|
||||
.execCount <<- 0L
|
||||
.suspended <<- suspended
|
||||
@@ -499,7 +628,9 @@ Observer <- setRefClass(
|
||||
.destroyed <<- FALSE
|
||||
.prevId <<- ''
|
||||
|
||||
onReactiveDomainEnded(.domain, .self$.onDomainEnded)
|
||||
.autoDestroy <<- FALSE
|
||||
.autoDestroyHandle <<- NULL
|
||||
setAutoDestroy(autoDestroy)
|
||||
|
||||
# Defer the first running of this until flushReact is called
|
||||
.createContext()$invalidate()
|
||||
@@ -508,9 +639,25 @@ Observer <- setRefClass(
|
||||
ctx <- Context$new(.domain, .label, type='observer', prevId=.prevId)
|
||||
.prevId <<- ctx$id
|
||||
|
||||
if (!is.null(.ctx)) {
|
||||
# If this happens, something went wrong.
|
||||
warning("Created a new context without invalidating previous context.")
|
||||
}
|
||||
# Store the context explicitly in the Observer object. This is necessary
|
||||
# to make sure that when the observer is destroyed, it also gets
|
||||
# invalidated. Otherwise the upstream reactive (on which the observer
|
||||
# depends) will hold a (indirect) reference to this context until the
|
||||
# reactive is invalidated, which may not happen immediately or at all.
|
||||
# This can lead to a memory leak (#1253).
|
||||
.ctx <<- ctx
|
||||
|
||||
ctx$onInvalidate(function() {
|
||||
lapply(.invalidateCallbacks, function(func) {
|
||||
func()
|
||||
# Context is invalidated, so we don't need to store a reference to it
|
||||
# anymore.
|
||||
.ctx <<- NULL
|
||||
|
||||
lapply(.invalidateCallbacks, function(invalidateCallback) {
|
||||
invalidateCallback()
|
||||
NULL
|
||||
})
|
||||
|
||||
@@ -525,8 +672,16 @@ Observer <- setRefClass(
|
||||
})
|
||||
|
||||
ctx$onFlush(function() {
|
||||
if (!.destroyed)
|
||||
run()
|
||||
tryCatch({
|
||||
if (!.destroyed)
|
||||
shinyCallingHandlers(run())
|
||||
|
||||
}, error = function(e) {
|
||||
printError(e)
|
||||
if (!is.null(.domain)) {
|
||||
.domain$unhandledError(e)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return(ctx)
|
||||
@@ -553,11 +708,28 @@ Observer <- setRefClass(
|
||||
"Sets whether this observer should be automatically destroyed when its
|
||||
domain (if any) ends. If autoDestroy is TRUE and the domain already
|
||||
ended, then destroy() is called immediately."
|
||||
|
||||
if (.autoDestroy == autoDestroy) {
|
||||
return(.autoDestroy)
|
||||
}
|
||||
|
||||
oldValue <- .autoDestroy
|
||||
.autoDestroy <<- autoDestroy
|
||||
if (!is.null(.domain) && .domain$isEnded()) {
|
||||
destroy()
|
||||
|
||||
if (autoDestroy) {
|
||||
if (!.destroyed && !is.null(.domain)) { # Make sure to not try to destroy twice.
|
||||
if (.domain$isEnded()) {
|
||||
destroy()
|
||||
} else {
|
||||
.autoDestroyHandle <<- onReactiveDomainEnded(.domain, .onDomainEnded)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!is.null(.autoDestroyHandle))
|
||||
.autoDestroyHandle()
|
||||
.autoDestroyHandle <<- NULL
|
||||
}
|
||||
|
||||
invisible(oldValue)
|
||||
},
|
||||
suspend = function() {
|
||||
@@ -583,8 +755,21 @@ Observer <- setRefClass(
|
||||
"Prevents this observer from ever executing again (even if a flush has
|
||||
already been scheduled)."
|
||||
|
||||
# Make sure to not try to destory twice.
|
||||
if (.destroyed)
|
||||
return()
|
||||
|
||||
suspend()
|
||||
.destroyed <<- TRUE
|
||||
|
||||
if (!is.null(.autoDestroyHandle)) {
|
||||
.autoDestroyHandle()
|
||||
}
|
||||
.autoDestroyHandle <<- NULL
|
||||
|
||||
if (!is.null(.ctx)) {
|
||||
.ctx$invalidate()
|
||||
}
|
||||
},
|
||||
.onDomainEnded = function() {
|
||||
if (isTRUE(.autoDestroy)) {
|
||||
@@ -623,7 +808,7 @@ Observer <- setRefClass(
|
||||
#' non-reactive expression.
|
||||
#' @param quoted Is the expression quoted? By default, this is \code{FALSE}.
|
||||
#' This is useful when you want to use an expression that is stored in a
|
||||
#' variable; to do so, it must be quoted with `quote()`.
|
||||
#' variable; to do so, it must be quoted with \code{quote()}.
|
||||
#' @param label A label for the observer, useful for debugging.
|
||||
#' @param suspended If \code{TRUE}, start the observer in a suspended state.
|
||||
#' If \code{FALSE} (the default), start in a non-suspended state.
|
||||
@@ -634,6 +819,8 @@ Observer <- setRefClass(
|
||||
#' @param domain See \link{domains}.
|
||||
#' @param autoDestroy If \code{TRUE} (the default), the observer will be
|
||||
#' automatically destroyed when its domain (if any) ends.
|
||||
#' @param ..stacktraceon Advanced use only. For stack manipulation purposes; see
|
||||
#' \code{\link{stacktrace}}.
|
||||
#' @return An observer reference class object. This object has the following
|
||||
#' methods:
|
||||
#' \describe{
|
||||
@@ -691,15 +878,16 @@ Observer <- setRefClass(
|
||||
#' @export
|
||||
observe <- function(x, env=parent.frame(), quoted=FALSE, label=NULL,
|
||||
suspended=FALSE, priority=0,
|
||||
domain=getDefaultReactiveDomain(), autoDestroy = TRUE) {
|
||||
domain=getDefaultReactiveDomain(), autoDestroy = TRUE,
|
||||
..stacktraceon = TRUE) {
|
||||
|
||||
fun <- exprToFunction(x, env, quoted)
|
||||
if (is.null(label))
|
||||
label <- sprintf('observe(%s)', paste(deparse(body(fun)), collapse='\n'))
|
||||
|
||||
o <- Observer$new(fun, label=label, suspended=suspended, priority=priority,
|
||||
domain=domain, autoDestroy=autoDestroy)
|
||||
registerDebugHook(".func", o, "Observer")
|
||||
domain=domain, autoDestroy=autoDestroy,
|
||||
..stacktraceon=..stacktraceon)
|
||||
invisible(o)
|
||||
}
|
||||
|
||||
@@ -801,12 +989,19 @@ setAutoflush <- local({
|
||||
#' @seealso \code{\link{invalidateLater}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("n", "Number of observations", 2, 1000, 500),
|
||||
#' plotOutput("plot")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#'
|
||||
#' # Anything that calls autoInvalidate will automatically invalidate
|
||||
#' # every 2 seconds.
|
||||
#' autoInvalidate <- reactiveTimer(2000, session)
|
||||
#' autoInvalidate <- reactiveTimer(2000)
|
||||
#'
|
||||
#' observe({
|
||||
#' # Invalidate and re-execute this reactive expression every time the
|
||||
@@ -823,18 +1018,15 @@ setAutoflush <- local({
|
||||
#' # input$n changes.
|
||||
#' output$plot <- renderPlot({
|
||||
#' autoInvalidate()
|
||||
#' hist(isolate(input$n))
|
||||
#' hist(rnorm(isolate(input$n)))
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
reactiveTimer <- function(intervalMs=1000, session) {
|
||||
if (missing(session)) {
|
||||
warning("reactiveTimer should be passed a session object or NULL")
|
||||
session <- NULL
|
||||
}
|
||||
|
||||
reactiveTimer <- function(intervalMs=1000, session = getDefaultReactiveDomain()) {
|
||||
dependents <- Map$new()
|
||||
timerCallbacks$schedule(intervalMs, function() {
|
||||
# Quit if the session is closed
|
||||
@@ -884,8 +1076,15 @@ reactiveTimer <- function(intervalMs=1000, session) {
|
||||
#' @seealso \code{\link{reactiveTimer}} is a slightly less safe alternative.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("n", "Number of observations", 2, 1000, 500),
|
||||
#' plotOutput("plot")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # Re-execute this reactive expression after 1000 milliseconds
|
||||
@@ -901,19 +1100,15 @@ reactiveTimer <- function(intervalMs=1000, session) {
|
||||
#' # input$n changes.
|
||||
#' output$plot <- renderPlot({
|
||||
#' # Re-execute this reactive expression after 2000 milliseconds
|
||||
#' invalidateLater(2000, session)
|
||||
#' hist(isolate(input$n))
|
||||
#' invalidateLater(2000)
|
||||
#' hist(rnorm(isolate(input$n)))
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
invalidateLater <- function(millis, session) {
|
||||
if (missing(session)) {
|
||||
warning("invalidateLater should be passed a session object or NULL")
|
||||
session <- NULL
|
||||
}
|
||||
|
||||
invalidateLater <- function(millis, session = getDefaultReactiveDomain()) {
|
||||
ctx <- .getReactiveEnvironment()$currentContext()
|
||||
timerCallbacks$schedule(millis, function() {
|
||||
# Quit if the session is closed
|
||||
@@ -981,14 +1176,12 @@ coerceToFunc <- function(x) {
|
||||
#' @seealso \code{\link{reactiveFileReader}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Assume the existence of readTimestamp and readValue functions
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' function(input, output, session) {
|
||||
#' data <- reactivePoll(1000, session, readTimestamp, readValue)
|
||||
#' output$dataTable <- renderTable({
|
||||
#' data()
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
@@ -1050,7 +1243,7 @@ reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Per-session reactive file reader
|
||||
#' shinyServer(function(input, output, session)) {
|
||||
#' function(input, output, session) {
|
||||
#' fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
|
||||
#'
|
||||
#' output$data <- renderTable({
|
||||
@@ -1062,7 +1255,7 @@ reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
|
||||
#' # the same reader, so read.csv only gets executed once no matter how many
|
||||
#' # user sessions are connected.
|
||||
#' fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
|
||||
#' shinyServer(function(input, output, session)) {
|
||||
#' function(input, output, session) {
|
||||
#' output$data <- renderTable({
|
||||
#' fileData()
|
||||
#' })
|
||||
@@ -1161,9 +1354,10 @@ reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...)
|
||||
isolate <- function(expr) {
|
||||
ctx <- Context$new(getDefaultReactiveDomain(), '[isolate]', type='isolate')
|
||||
on.exit(ctx$invalidate())
|
||||
ctx$run(function() {
|
||||
expr
|
||||
})
|
||||
# Matching ..stacktraceon../..stacktraceoff.. pair
|
||||
..stacktraceoff..(ctx$run(function() {
|
||||
..stacktraceon..(expr)
|
||||
}))
|
||||
}
|
||||
|
||||
#' Evaluate an expression without a reactive context
|
||||
@@ -1184,3 +1378,182 @@ maskReactiveContext <- function(expr) {
|
||||
expr
|
||||
})
|
||||
}
|
||||
|
||||
#' Event handler
|
||||
#'
|
||||
#' Respond to "event-like" reactive inputs, values, and expressions.
|
||||
#'
|
||||
#' Shiny's reactive programming framework is primarily designed for calculated
|
||||
#' values (reactive expressions) and side-effect-causing actions (observers)
|
||||
#' that respond to \emph{any} of their inputs changing. That's often what is
|
||||
#' desired in Shiny apps, but not always: sometimes you want to wait for a
|
||||
#' specific action to be taken from the user, like clicking an
|
||||
#' \code{\link{actionButton}}, before calculating an expression or taking an
|
||||
#' action. A reactive value or expression that is used to trigger other
|
||||
#' calculations in this way is called an \emph{event}.
|
||||
#'
|
||||
#' These situations demand a more imperative, "event handling" style of
|
||||
#' programming that is possible--but not particularly intuitive--using the
|
||||
#' reactive programming primitives \code{\link{observe}} and
|
||||
#' \code{\link{isolate}}. \code{observeEvent} and \code{eventReactive} provide
|
||||
#' straightforward APIs for event handling that wrap \code{observe} and
|
||||
#' \code{isolate}.
|
||||
#'
|
||||
#' Use \code{observeEvent} whenever you want to \emph{perform an action} in
|
||||
#' response to an event. (Note that "recalculate a value" does not generally
|
||||
#' count as performing an action--see \code{eventReactive} for that.) The first
|
||||
#' argument is the event you want to respond to, and the second argument is a
|
||||
#' function that should be called whenever the event occurs.
|
||||
#'
|
||||
#' Use \code{eventReactive} to create a \emph{calculated value} that only
|
||||
#' updates in response to an event. This is just like a normal
|
||||
#' \link[=reactive]{reactive expression} except it ignores all the usual
|
||||
#' invalidations that come from its reactive dependencies; it only invalidates
|
||||
#' in response to the given event.
|
||||
#'
|
||||
#' Both \code{observeEvent} and \code{eventReactive} take an \code{ignoreNULL}
|
||||
#' parameter that affects behavior when the \code{eventExpr} evaluates to
|
||||
#' \code{NULL} (or in the special case of an \code{\link{actionButton}},
|
||||
#' \code{0}). In these cases, if \code{ignoreNULL} is \code{TRUE}, then an
|
||||
#' \code{observeEvent} will not execute and an \code{eventReactive} will raise a
|
||||
#' silent \link[=validate]{validation} error. This is useful behavior if you
|
||||
#' don't want to do the action or calculation when your app first starts, but
|
||||
#' wait for the user to initiate the action first (like a "Submit" button);
|
||||
#' whereas \code{ignoreNULL=FALSE} is desirable if you want to initially perform
|
||||
#' the action/calculation and just let the user re-initiate it (like a
|
||||
#' "Recalculate" button).
|
||||
#'
|
||||
#' @param eventExpr A (quoted or unquoted) expression that represents the event;
|
||||
#' this can be a simple reactive value like \code{input$click}, a call to a
|
||||
#' reactive expression like \code{dataset()}, or even a complex expression
|
||||
#' inside curly braces
|
||||
#' @param handlerExpr The expression to call whenever \code{eventExpr} is
|
||||
#' invalidated. This should be a side-effect-producing action (the return
|
||||
#' value will be ignored). It will be executed within an \code{\link{isolate}}
|
||||
#' scope.
|
||||
#' @param valueExpr The expression that produces the return value of the
|
||||
#' \code{eventReactive}. It will be executed within an \code{\link{isolate}}
|
||||
#' scope.
|
||||
#' @param event.env The parent environment for \code{eventExpr}. By default,
|
||||
#' this is the calling environment.
|
||||
#' @param event.quoted Is the \code{eventExpr} expression quoted? By default,
|
||||
#' this is \code{FALSE}. This is useful when you want to use an expression
|
||||
#' that is stored in a variable; to do so, it must be quoted with
|
||||
#' \code{quote()}.
|
||||
#' @param handler.env The parent environment for \code{handlerExpr}. By default,
|
||||
#' this is the calling environment.
|
||||
#' @param handler.quoted Is the \code{handlerExpr} expression quoted? By
|
||||
#' default, this is \code{FALSE}. This is useful when you want to use an
|
||||
#' expression that is stored in a variable; to do so, it must be quoted with
|
||||
#' \code{quote()}.
|
||||
#' @param value.env The parent environment for \code{valueExpr}. By default,
|
||||
#' this is the calling environment.
|
||||
#' @param value.quoted Is the \code{valueExpr} expression quoted? By default,
|
||||
#' this is \code{FALSE}. This is useful when you want to use an expression
|
||||
#' that is stored in a variable; to do so, it must be quoted with \code{quote()}.
|
||||
#' @param label A label for the observer or reactive, useful for debugging.
|
||||
#' @param suspended If \code{TRUE}, start the observer in a suspended state. If
|
||||
#' \code{FALSE} (the default), start in a non-suspended state.
|
||||
#' @param priority An integer or numeric that controls the priority with which
|
||||
#' this observer should be executed. An observer with a given priority level
|
||||
#' will always execute sooner than all observers with a lower priority level.
|
||||
#' Positive, negative, and zero values are allowed.
|
||||
#' @param domain See \link{domains}.
|
||||
#' @param autoDestroy If \code{TRUE} (the default), the observer will be
|
||||
#' automatically destroyed when its domain (if any) ends.
|
||||
#' @param ignoreNULL Whether the action should be triggered (or value
|
||||
#' calculated, in the case of \code{eventReactive}) when the input is
|
||||
#' \code{NULL}. See Details.
|
||||
#' @return \code{observeEvent} returns an observer reference class object (see
|
||||
#' \code{\link{observe}}). \code{eventReactive} returns a reactive expression
|
||||
#' object (see \code{\link{reactive}}).
|
||||
#'
|
||||
#' @seealso \code{\link{actionButton}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' ui <- fluidPage(
|
||||
#' column(4,
|
||||
#' numericInput("x", "Value", 5),
|
||||
#' br(),
|
||||
#' actionButton("button", "Show")
|
||||
#' ),
|
||||
#' column(8, tableOutput("table"))
|
||||
#' )
|
||||
#' server <- function(input, output) {
|
||||
#' # Take an action every time button is pressed;
|
||||
#' # here, we just print a message to the console
|
||||
#' observeEvent(input$button, {
|
||||
#' cat("Showing", input$x, "rows\n")
|
||||
#' })
|
||||
#' # Take a reactive dependency on input$button, but
|
||||
#' # not on any of the stuff inside the function
|
||||
#' df <- eventReactive(input$button, {
|
||||
#' head(cars, input$x)
|
||||
#' })
|
||||
#' output$table <- renderTable({
|
||||
#' df()
|
||||
#' })
|
||||
#' }
|
||||
#' shinyApp(ui=ui, server=server)
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
observeEvent <- function(eventExpr, handlerExpr,
|
||||
event.env = parent.frame(), event.quoted = FALSE,
|
||||
handler.env = parent.frame(), handler.quoted = FALSE,
|
||||
label=NULL, suspended=FALSE, priority=0, domain=getDefaultReactiveDomain(),
|
||||
autoDestroy = TRUE, ignoreNULL = TRUE) {
|
||||
|
||||
eventFunc <- exprToFunction(eventExpr, event.env, event.quoted)
|
||||
if (is.null(label))
|
||||
label <- sprintf('observeEvent(%s)', paste(deparse(body(eventFunc)), collapse='\n'))
|
||||
eventFunc <- wrapFunctionLabel(eventFunc, "observeEventExpr", ..stacktraceon = TRUE)
|
||||
|
||||
handlerFunc <- exprToFunction(handlerExpr, handler.env, handler.quoted)
|
||||
handlerFunc <- wrapFunctionLabel(handlerFunc, "observeEventHandler", ..stacktraceon = TRUE)
|
||||
|
||||
invisible(observe({
|
||||
e <- eventFunc()
|
||||
|
||||
if (ignoreNULL && isNullEvent(e)) {
|
||||
return()
|
||||
}
|
||||
|
||||
isolate(handlerFunc())
|
||||
}, label = label, suspended = suspended, priority = priority, domain = domain,
|
||||
autoDestroy = TRUE, ..stacktraceon = FALSE))
|
||||
}
|
||||
|
||||
#' @rdname observeEvent
|
||||
#' @export
|
||||
eventReactive <- function(eventExpr, valueExpr,
|
||||
event.env = parent.frame(), event.quoted = FALSE,
|
||||
value.env = parent.frame(), value.quoted = FALSE,
|
||||
label=NULL, domain=getDefaultReactiveDomain(),
|
||||
ignoreNULL = TRUE) {
|
||||
|
||||
eventFunc <- exprToFunction(eventExpr, event.env, event.quoted)
|
||||
if (is.null(label))
|
||||
label <- sprintf('eventReactive(%s)', paste(deparse(body(eventFunc)), collapse='\n'))
|
||||
eventFunc <- wrapFunctionLabel(eventFunc, "eventReactiveExpr", ..stacktraceon = TRUE)
|
||||
|
||||
handlerFunc <- exprToFunction(valueExpr, value.env, value.quoted)
|
||||
handlerFunc <- wrapFunctionLabel(handlerFunc, "eventReactiveHandler", ..stacktraceon = TRUE)
|
||||
|
||||
invisible(reactive({
|
||||
e <- eventFunc()
|
||||
|
||||
validate(need(
|
||||
!ignoreNULL || !isNullEvent(e),
|
||||
message = FALSE
|
||||
))
|
||||
|
||||
isolate(handlerFunc())
|
||||
}, label = label, domain = domain, ..stacktraceon = FALSE))
|
||||
}
|
||||
|
||||
isNullEvent <- function(value) {
|
||||
is.null(value) || (inherits(value, 'shinyActionButtonValue') && value == 0)
|
||||
}
|
||||
|
||||
708
R/render-plot.R
Normal file
708
R/render-plot.R
Normal file
@@ -0,0 +1,708 @@
|
||||
#' Plot Output
|
||||
#'
|
||||
#' Renders a reactive plot that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-plot-output}.
|
||||
#'
|
||||
#' @section Interactive plots:
|
||||
#'
|
||||
#' With ggplot2 graphics, the code in \code{renderPlot} should return a ggplot
|
||||
#' object; if instead the code prints the ggplot2 object with something like
|
||||
#' \code{print(p)}, then the coordinates for interactive graphics will not be
|
||||
#' properly scaled to the data space.
|
||||
#'
|
||||
#' See \code{\link{plotOutput}} for more information about interactive plots.
|
||||
#'
|
||||
#' @seealso For the corresponding client-side output function, and example
|
||||
#' usage, see \code{\link{plotOutput}}. For more details on how the plots are
|
||||
#' generated, and how to control the output, see \code{\link{plotPNG}}.
|
||||
#'
|
||||
#' @param expr An expression that generates a plot.
|
||||
#' @param width,height The width/height of the rendered plot, in pixels; or
|
||||
#' \code{'auto'} to use the \code{offsetWidth}/\code{offsetHeight} of the HTML
|
||||
#' element that is bound to this plot. You can also pass in a function that
|
||||
#' returns the width/height in pixels or \code{'auto'}; in the body of the
|
||||
#' function you may reference reactive values and functions. When rendering an
|
||||
#' inline plot, you must provide numeric values (in pixels) to both
|
||||
#' \code{width} and \code{height}.
|
||||
#' @param 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 execOnResize If \code{FALSE} (the default), then when a plot is
|
||||
#' resized, Shiny will \emph{replay} the plot drawing commands with
|
||||
#' \code{\link[grDevices]{replayPlot}()} instead of re-executing \code{expr}.
|
||||
#' This can result in faster plot redrawing, but there may be rare cases where
|
||||
#' it is undesirable. If you encounter problems when resizing a plot, you can
|
||||
#' have Shiny re-execute the code on resize by setting this to \code{TRUE}.
|
||||
#' @param outputArgs A list of arguments to be passed through to the implicit
|
||||
#' call to \code{\link{plotOutput}} when \code{renderPlot} is used in an
|
||||
#' interactive R Markdown document.
|
||||
#' @export
|
||||
renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
env=parent.frame(), quoted=FALSE,
|
||||
execOnResize=FALSE, outputArgs=list()
|
||||
) {
|
||||
# This ..stacktraceon is matched by a ..stacktraceoff.. when plotFunc
|
||||
# is called
|
||||
installExprFunction(expr, "func", env, quoted, ..stacktraceon = TRUE)
|
||||
|
||||
args <- list(...)
|
||||
|
||||
if (is.function(width))
|
||||
widthWrapper <- reactive({ width() })
|
||||
else
|
||||
widthWrapper <- function() { width }
|
||||
|
||||
if (is.function(height))
|
||||
heightWrapper <- reactive({ height() })
|
||||
else
|
||||
heightWrapper <- function() { height }
|
||||
|
||||
# A modified version of print.ggplot which returns the built ggplot object
|
||||
# as well as the gtable grob. This overrides the ggplot::print.ggplot
|
||||
# method, but only within the context of renderPlot. The reason this needs
|
||||
# to be a (pseudo) S3 method is so that, if an object has a class in
|
||||
# addition to ggplot, and there's a print method for that class, that we
|
||||
# won't override that method. https://github.com/rstudio/shiny/issues/841
|
||||
print.ggplot <- function(x) {
|
||||
grid::grid.newpage()
|
||||
|
||||
build <- ggplot2::ggplot_build(x)
|
||||
|
||||
gtable <- ggplot2::ggplot_gtable(build)
|
||||
grid::grid.draw(gtable)
|
||||
|
||||
structure(list(
|
||||
build = build,
|
||||
gtable = gtable
|
||||
), class = "ggplot_build_gtable")
|
||||
}
|
||||
|
||||
|
||||
getDims <- function() {
|
||||
width <- widthWrapper()
|
||||
height <- heightWrapper()
|
||||
|
||||
# Note that these are reactive calls. A change to the width and height
|
||||
# will inherently cause a reactive plot to redraw (unless width and
|
||||
# height were explicitly specified).
|
||||
if (width == 'auto')
|
||||
width <- session$clientData[[paste0('output_', outputName, '_width')]]
|
||||
if (height == 'auto')
|
||||
height <- session$clientData[[paste0('output_', outputName, '_height')]]
|
||||
|
||||
list(width = width, height = height)
|
||||
}
|
||||
|
||||
# Vars to store session and output, so that they can be accessed from
|
||||
# the plotObj() reactive.
|
||||
session <- NULL
|
||||
outputName <- NULL
|
||||
|
||||
# This function is the one that's returned from renderPlot(), and gets
|
||||
# wrapped in an observer when the output value is assigned. The expression
|
||||
# passed to renderPlot() is actually run in plotObj(); this function can only
|
||||
# replay a plot if the width/height changes.
|
||||
renderFunc <- function(shinysession, name, ...) {
|
||||
session <<- shinysession
|
||||
outputName <<- name
|
||||
|
||||
dims <- getDims()
|
||||
|
||||
if (is.null(dims$width) || is.null(dims$height) ||
|
||||
dims$width <= 0 || dims$height <= 0) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
# The reactive that runs the expr in renderPlot()
|
||||
plotData <- plotObj()
|
||||
|
||||
img <- plotData$img
|
||||
|
||||
# If only the width/height have changed, simply replay the plot and make a
|
||||
# new img.
|
||||
if (dims$width != img$width || dims$height != img$height) {
|
||||
pixelratio <- session$clientData$pixelratio %OR% 1
|
||||
|
||||
coordmap <- NULL
|
||||
plotFunc <- function() {
|
||||
..stacktraceon..(grDevices::replayPlot(plotData$recordedPlot))
|
||||
|
||||
# Coordmap must be recalculated after replaying plot, because pixel
|
||||
# dimensions will have changed.
|
||||
if (inherits(plotData$plotResult, "ggplot_build_gtable")) {
|
||||
coordmap <<- getGgplotCoordmap(plotData$plotResult, pixelratio, res)
|
||||
} else {
|
||||
coordmap <<- getPrevPlotCoordmap(dims$width, dims$height)
|
||||
}
|
||||
}
|
||||
outfile <- ..stacktraceoff..(
|
||||
plotPNG(plotFunc, width = dims$width*pixelratio, height = dims$height*pixelratio,
|
||||
res = res*pixelratio)
|
||||
)
|
||||
on.exit(unlink(outfile))
|
||||
|
||||
img <- dropNulls(list(
|
||||
src = session$fileUrl(name, outfile, contentType='image/png'),
|
||||
width = dims$width,
|
||||
height = dims$height,
|
||||
coordmap = coordmap,
|
||||
# Get coordmap error message if present
|
||||
error = attr(coordmap, "error", exact = TRUE)
|
||||
))
|
||||
}
|
||||
|
||||
img
|
||||
}
|
||||
|
||||
|
||||
plotObj <- reactive(label = "plotObj", {
|
||||
if (execOnResize) {
|
||||
isolate({ dims <- getDims() })
|
||||
} else {
|
||||
dims <- getDims()
|
||||
}
|
||||
|
||||
if (is.null(dims$width) || is.null(dims$height) ||
|
||||
dims$width <= 0 || dims$height <= 0) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
# Resolution multiplier
|
||||
pixelratio <- session$clientData$pixelratio %OR% 1
|
||||
|
||||
plotResult <- NULL
|
||||
recordedPlot <- NULL
|
||||
coordmap <- NULL
|
||||
plotFunc <- function() {
|
||||
success <-FALSE
|
||||
tryCatch(
|
||||
{
|
||||
# Actually perform the plotting
|
||||
result <- withVisible(func())
|
||||
success <- TRUE
|
||||
},
|
||||
finally = {
|
||||
if (!success) {
|
||||
# If there was an error in making the plot, there's a good chance
|
||||
# it's "Error in plot.new: figure margins too large". We need to
|
||||
# take a reactive dependency on the width and height, so that the
|
||||
# user's plotting code will re-execute when the plot is resized,
|
||||
# instead of just replaying the previous plot (which errored).
|
||||
getDims()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (result$visible) {
|
||||
# Use capture.output to squelch printing to the actual console; we
|
||||
# are only interested in plot output
|
||||
utils::capture.output({
|
||||
# This ..stacktraceon.. negates the ..stacktraceoff.. that wraps
|
||||
# the call to plotFunc. The value needs to be printed just in case
|
||||
# it's an object that requires printing to generate plot output,
|
||||
# similar to ggplot2. But for base graphics, it would already have
|
||||
# been rendered when func was called above, and the print should
|
||||
# have no effect.
|
||||
plotResult <<- ..stacktraceon..(print(result$value))
|
||||
})
|
||||
}
|
||||
|
||||
recordedPlot <<- grDevices::recordPlot()
|
||||
|
||||
if (inherits(plotResult, "ggplot_build_gtable")) {
|
||||
coordmap <<- getGgplotCoordmap(plotResult, pixelratio, res)
|
||||
} else {
|
||||
coordmap <<- getPrevPlotCoordmap(dims$width, dims$height)
|
||||
}
|
||||
}
|
||||
|
||||
# This ..stacktraceoff.. is matched by the `func` function's
|
||||
# wrapFunctionLabel(..stacktraceon=TRUE) call near the beginning of
|
||||
# renderPlot, and by the ..stacktraceon.. in plotFunc where ggplot objects
|
||||
# are printed
|
||||
outfile <- ..stacktraceoff..(
|
||||
do.call(plotPNG, c(plotFunc, width=dims$width*pixelratio,
|
||||
height=dims$height*pixelratio, res=res*pixelratio, args))
|
||||
)
|
||||
on.exit(unlink(outfile))
|
||||
|
||||
list(
|
||||
# img is the content that gets sent to the client.
|
||||
img = dropNulls(list(
|
||||
src = session$fileUrl(outputName, outfile, contentType='image/png'),
|
||||
width = dims$width,
|
||||
height = dims$height,
|
||||
coordmap = coordmap,
|
||||
# Get coordmap error message if present.
|
||||
error = attr(coordmap, "error", exact = TRUE)
|
||||
)),
|
||||
# Returned value from expression in renderPlot() -- may be a printable
|
||||
# object like ggplot2. Needed just in case we replayPlot and need to get
|
||||
# a coordmap again.
|
||||
plotResult = plotResult,
|
||||
recordedPlot = recordedPlot
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
# If renderPlot isn't going to adapt to the height of the div, then the
|
||||
# div needs to adapt to the height of renderPlot. By default, plotOutput
|
||||
# sets the height to 400px, so to make it adapt we need to override it
|
||||
# with NULL.
|
||||
outputFunc <- plotOutput
|
||||
if (!identical(height, 'auto')) formals(outputFunc)['height'] <- list(NULL)
|
||||
|
||||
markRenderFunction(outputFunc, renderFunc, outputArgs = outputArgs)
|
||||
}
|
||||
|
||||
# The coordmap extraction functions below return something like the examples
|
||||
# below. For base graphics:
|
||||
# plot(mtcars$wt, mtcars$mpg)
|
||||
# str(getPrevPlotCoordmap(400, 300))
|
||||
# List of 1
|
||||
# $ :List of 4
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.36
|
||||
# .. ..$ right : num 5.58
|
||||
# .. ..$ bottom: num 9.46
|
||||
# .. ..$ top : num 34.8
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 50.4
|
||||
# .. ..$ right : num 373
|
||||
# .. ..$ bottom: num 199
|
||||
# .. ..$ top : num 79.6
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ mapping: Named list()
|
||||
#
|
||||
# For ggplot2, it might be something like:
|
||||
# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
|
||||
# str(getGgplotCoordmap(p, 1))
|
||||
# List of 1
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 1
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 1
|
||||
# ..$ panel_vars: Named list()
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 2
|
||||
# .. ..$ x: chr "wt"
|
||||
# .. ..$ y: chr "mpg"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 40.8
|
||||
# .. ..$ right : num 446
|
||||
# .. ..$ bottom: num 263
|
||||
# .. ..$ top : num 14.4
|
||||
#
|
||||
# With a faceted ggplot2 plot, the outer list contains two objects, each of
|
||||
# which represents one panel. In this example, there is one panelvar, but there
|
||||
# can be up to two of them.
|
||||
# mtc <- mtcars
|
||||
# mtc$am <- factor(mtc$am)
|
||||
# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() + facet_wrap(~ am)
|
||||
# str(getGgplotCoordmap(p, 1))
|
||||
# List of 2
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 1
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 1
|
||||
# ..$ panel_vars:List of 1
|
||||
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 1
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 3
|
||||
# .. ..$ x : chr "wt"
|
||||
# .. ..$ y : chr "mpg"
|
||||
# .. ..$ panelvar1: chr "am"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 45.6
|
||||
# .. ..$ right : num 317
|
||||
# .. ..$ bottom: num 251
|
||||
# .. ..$ top : num 35.7
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 2
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 2
|
||||
# ..$ panel_vars:List of 1
|
||||
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 2
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 3
|
||||
# .. ..$ x : chr "wt"
|
||||
# .. ..$ y : chr "mpg"
|
||||
# .. ..$ panelvar1: chr "am"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 322
|
||||
# .. ..$ right : num 594
|
||||
# .. ..$ bottom: num 251
|
||||
# .. ..$ top : num 35.7
|
||||
|
||||
|
||||
# Get a coordmap for the previous plot made with base graphics.
|
||||
# Requires width and height of output image, in pixels.
|
||||
# Must be called before the graphics device is closed.
|
||||
getPrevPlotCoordmap <- function(width, height) {
|
||||
usrCoords <- graphics::par('usr')
|
||||
usrBounds <- usrCoords
|
||||
if (graphics::par('xlog')) {
|
||||
usrBounds[c(1,2)] <- 10 ^ usrBounds[c(1,2)]
|
||||
}
|
||||
if (graphics::par('ylog')) {
|
||||
usrBounds[c(3,4)] <- 10 ^ usrBounds[c(3,4)]
|
||||
}
|
||||
|
||||
# Wrapped in double list because other types of plots can have multiple panels.
|
||||
list(list(
|
||||
# Bounds of the plot area, in data space
|
||||
domain = list(
|
||||
left = usrCoords[1],
|
||||
right = usrCoords[2],
|
||||
bottom = usrCoords[3],
|
||||
top = usrCoords[4]
|
||||
),
|
||||
# The bounds of the plot area, in DOM pixels
|
||||
range = list(
|
||||
left = graphics::grconvertX(usrBounds[1], 'user', 'nfc') * width,
|
||||
right = graphics::grconvertX(usrBounds[2], 'user', 'nfc') * width,
|
||||
bottom = (1-graphics::grconvertY(usrBounds[3], 'user', 'nfc')) * height - 1,
|
||||
top = (1-graphics::grconvertY(usrBounds[4], 'user', 'nfc')) * height - 1
|
||||
),
|
||||
log = list(
|
||||
x = if (graphics::par('xlog')) 10 else NULL,
|
||||
y = if (graphics::par('ylog')) 10 else NULL
|
||||
),
|
||||
# We can't extract the original variable names from a base graphic.
|
||||
# `mapping` is an empty _named_ list, so that it is converted to an object
|
||||
# (not an array) in JSON.
|
||||
mapping = list(x = NULL)[0]
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
# Given a ggplot_build_gtable object, return a coordmap for it.
|
||||
getGgplotCoordmap <- function(p, pixelratio, res) {
|
||||
if (!inherits(p, "ggplot_build_gtable"))
|
||||
return(NULL)
|
||||
|
||||
# Given a built ggplot object, return x and y domains (data space coords) for
|
||||
# each panel.
|
||||
find_panel_info <- function(b) {
|
||||
layout <- b$panel$layout
|
||||
# Convert factor to numbers
|
||||
layout$PANEL <- as.integer(as.character(layout$PANEL))
|
||||
|
||||
# Names of facets
|
||||
facet <- b$plot$facet
|
||||
facet_vars <- NULL
|
||||
if (inherits(facet, "grid")) {
|
||||
facet_vars <- vapply(c(facet$cols, facet$rows), as.character, character(1))
|
||||
} else if (inherits(facet, "wrap")) {
|
||||
facet_vars <- vapply(facet$facets, as.character, character(1))
|
||||
}
|
||||
|
||||
# Iterate over each row in the layout data frame
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
# Slice out one row
|
||||
l <- layout[i, ]
|
||||
|
||||
scale_x <- l$SCALE_X
|
||||
scale_y <- l$SCALE_Y
|
||||
|
||||
mapping <- find_plot_mappings(b)
|
||||
|
||||
# For each of the faceting variables, get the value of that variable in
|
||||
# the current panel. Default to empty _named_ list so that it's sent as a
|
||||
# JSON object, not array.
|
||||
panel_vars <- list(a = NULL)[0]
|
||||
for (i in seq_along(facet_vars)) {
|
||||
var_name <- facet_vars[[i]]
|
||||
vname <- paste0("panelvar", i)
|
||||
|
||||
mapping[[vname]] <- var_name
|
||||
panel_vars[[vname]] <- l[[var_name]]
|
||||
}
|
||||
|
||||
list(
|
||||
panel = l$PANEL,
|
||||
row = l$ROW,
|
||||
col = l$COL,
|
||||
panel_vars = panel_vars,
|
||||
scale_x = scale_x,
|
||||
scale_y = scale_x,
|
||||
log = check_log_scales(b, scale_x, scale_y),
|
||||
domain = find_panel_domain(b, l$PANEL, scale_x, scale_y),
|
||||
mapping = mapping
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
# Given a single range object (representing the data domain) from a built
|
||||
# ggplot object, return the domain.
|
||||
find_panel_domain <- function(b, panel_num, scalex_num = 1, scaley_num = 1) {
|
||||
range <- b$panel$ranges[[panel_num]]
|
||||
domain <- list(
|
||||
left = range$x.range[1],
|
||||
right = range$x.range[2],
|
||||
bottom = range$y.range[1],
|
||||
top = range$y.range[2]
|
||||
)
|
||||
|
||||
# Check for reversed scales
|
||||
xscale <- b$panel$x_scales[[scalex_num]]
|
||||
yscale <- b$panel$y_scales[[scaley_num]]
|
||||
|
||||
if (!is.null(xscale$trans) && xscale$trans$name == "reverse") {
|
||||
domain$left <- -domain$left
|
||||
domain$right <- -domain$right
|
||||
}
|
||||
if (!is.null(yscale$trans) && yscale$trans$name == "reverse") {
|
||||
domain$top <- -domain$top
|
||||
domain$bottom <- -domain$bottom
|
||||
}
|
||||
|
||||
domain
|
||||
}
|
||||
|
||||
# Given built ggplot object, return object with the log base for x and y if
|
||||
# there are log scales or coord transforms.
|
||||
check_log_scales <- function(b, scalex_num = 1, scaley_num = 1) {
|
||||
|
||||
# Given a vector of transformation names like c("log-10", "identity"),
|
||||
# return the first log base, like 10. If none are present, return NULL.
|
||||
extract_log_base <- function(names) {
|
||||
names <- names[grepl("^log-", names)]
|
||||
|
||||
if (length(names) == 0)
|
||||
return(NULL)
|
||||
|
||||
names <- names[1]
|
||||
|
||||
as.numeric(sub("^log-", "", names))
|
||||
}
|
||||
|
||||
# Look for log scales and log coord transforms. People shouldn't use both.
|
||||
x_names <- character(0)
|
||||
y_names <- character(0)
|
||||
|
||||
# Continuous scales have a trans; discrete ones don't
|
||||
if (!is.null(b$panel$x_scales[[scalex_num]]$trans))
|
||||
x_names <- b$panel$x_scales[[scalex_num]]$trans$name
|
||||
if (!is.null(b$panel$y_scales[[scaley_num]]$trans))
|
||||
y_names <- b$panel$y_scales[[scaley_num]]$trans$name
|
||||
|
||||
coords <- b$plot$coordinates
|
||||
if (!is.null(coords$trans)) {
|
||||
if (!is.null(coords$trans$x))
|
||||
x_names <- c(x_names, coords$trans$x$name)
|
||||
if (!is.null(coords$trans$y))
|
||||
y_names <- c(y_names, coords$trans$y$name)
|
||||
}
|
||||
|
||||
# Keep only scale/trans names that start with "log-"
|
||||
x_names <- x_names[grepl("^log-", x_names)]
|
||||
y_names <- y_names[grepl("^log-", y_names)]
|
||||
|
||||
# Extract the log base from the trans name -- a string like "log-10".
|
||||
list(
|
||||
x = extract_log_base(x_names),
|
||||
y = extract_log_base(y_names)
|
||||
)
|
||||
}
|
||||
|
||||
# Given a built ggplot object, return a named list of variables mapped to x
|
||||
# and y. This function will be called for each panel, but in practice the
|
||||
# result is always the same across panels, so we'll cache the result.
|
||||
mappings_cache <- NULL
|
||||
find_plot_mappings <- function(b) {
|
||||
if (!is.null(mappings_cache))
|
||||
return(mappings_cache)
|
||||
|
||||
# lapply'ing as.character results in unexpected behavior for expressions
|
||||
# like `wt/2`. This works better.
|
||||
mappings <- as.list(as.character(b$plot$mapping))
|
||||
|
||||
# If x or y mapping is missing, look in each layer for mappings and return
|
||||
# the first one.
|
||||
missing_mappings <- setdiff(c("x", "y"), names(mappings))
|
||||
if (length(missing_mappings) != 0) {
|
||||
# Grab mappings for each layer
|
||||
layer_mappings <- lapply(b$plot$layers, function(layer) {
|
||||
lapply(layer$mapping, as.character)
|
||||
})
|
||||
|
||||
# Get just the first x or y value in the combined list of plot and layer
|
||||
# mappings.
|
||||
mappings <- c(list(mappings), layer_mappings)
|
||||
mappings <- Reduce(x = mappings, init = list(x = NULL, y = NULL),
|
||||
function(init, m) {
|
||||
if (is.null(init$x) && !is.null(m$x)) init$x <- m$x
|
||||
if (is.null(init$y) && !is.null(m$y)) init$y <- m$y
|
||||
init
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
mappings_cache <<- mappings
|
||||
mappings
|
||||
}
|
||||
|
||||
# Given a gtable object, return the x and y ranges (in pixel dimensions)
|
||||
find_panel_ranges <- function(g, pixelratio) {
|
||||
# Given a vector of unit objects, return logical vector indicating which ones
|
||||
# are "null" units. These units use the remaining available width/height --
|
||||
# that is, the space not occupied by elements that have an absolute size.
|
||||
is_null_unit <- function(x) {
|
||||
# A vector of units can be either a list of individual units (a unit.list
|
||||
# object), each with their own set of attributes, or an atomic vector with
|
||||
# one set of attributes. ggplot2 switched from the former (in version
|
||||
# 1.0.1) to the latter. We need to make sure that we get the correct
|
||||
# result in both cases.
|
||||
if (inherits(x, "unit.list")) {
|
||||
# For ggplot2 <= 1.0.1
|
||||
vapply(x, FUN.VALUE = logical(1), function(u) {
|
||||
isTRUE(attr(u, "unit", exact = TRUE) == "null")
|
||||
})
|
||||
} else {
|
||||
# For later versions of ggplot2
|
||||
attr(x, "unit", exact = TRUE) == "null"
|
||||
}
|
||||
}
|
||||
|
||||
# Workaround for a bug in the quartz device. If you have a 400x400 image and
|
||||
# run `convertWidth(unit(1, "npc"), "native")`, the result will depend on
|
||||
# res setting of the device. If res=72, then it returns 400 (as expected),
|
||||
# but if, e.g., res=96, it will return 300, which is incorrect.
|
||||
devScaleFactor <- 1
|
||||
if (grepl("quartz", names(grDevices::dev.cur()), fixed = TRUE)) {
|
||||
devScaleFactor <- res / 72
|
||||
}
|
||||
|
||||
# Convert a unit (or vector of units) to a numeric vector of pixel sizes
|
||||
h_px <- function(x) {
|
||||
devScaleFactor * grid::convertHeight(x, "native", valueOnly = TRUE)
|
||||
}
|
||||
w_px <- function(x) {
|
||||
devScaleFactor * grid::convertWidth(x, "native", valueOnly = TRUE)
|
||||
}
|
||||
|
||||
# Given a vector of relative sizes (in grid units), and a function for
|
||||
# converting grid units to numeric pixels, return a numeric vector of
|
||||
# pixel sizes.
|
||||
find_px_sizes <- function(rel_sizes, unit_to_px) {
|
||||
# Total pixels (in height or width)
|
||||
total_px <- unit_to_px(grid::unit(1, "npc"))
|
||||
# Calculate size of all panel(s) together. Panels (and only panels) have
|
||||
# null size.
|
||||
null_idx <- is_null_unit(rel_sizes)
|
||||
# All the absolute heights. At this point, null heights are 0. We need to
|
||||
# calculate them separately and add them in later.
|
||||
px_sizes <- unit_to_px(rel_sizes)
|
||||
# Total size for panels is image size minus absolute (non-panel) elements
|
||||
panel_px_total <- total_px - sum(px_sizes)
|
||||
# Divide up the total panel size up into the panels (scaled by size)
|
||||
panel_sizes_rel <- as.numeric(rel_sizes[null_idx])
|
||||
panel_sizes_rel <- panel_sizes_rel / sum(panel_sizes_rel)
|
||||
px_sizes[null_idx] <- panel_px_total * panel_sizes_rel
|
||||
abs(px_sizes)
|
||||
}
|
||||
|
||||
px_heights <- find_px_sizes(g$heights, h_px)
|
||||
px_widths <- find_px_sizes(g$widths, w_px)
|
||||
|
||||
# Convert to absolute pixel positions
|
||||
x_pos <- cumsum(px_widths)
|
||||
y_pos <- cumsum(px_heights)
|
||||
|
||||
# Match up the pixel dimensions to panels
|
||||
layout <- g$layout
|
||||
# For panels:
|
||||
# * For facet_wrap, they'll be named "panel-1", "panel-2", etc.
|
||||
# * For no facet or facet_grid, they'll just be named "panel". For
|
||||
# facet_grid, we need to re-order the layout table. Assume that panel
|
||||
# numbers go from left to right, then next row.
|
||||
# Assign a number to each panel, corresponding to PANEl in the built ggplot
|
||||
# object.
|
||||
layout <- layout[grepl("^panel", layout$name), ]
|
||||
layout <- layout[order(layout$t, layout$l), ]
|
||||
layout$panel <- seq_len(nrow(layout))
|
||||
|
||||
# When using a HiDPI client on a Linux server, the pixel
|
||||
# dimensions are doubled, so we have to divide the dimensions by
|
||||
# `pixelratio`. When a HiDPI client is used on a Mac server (with
|
||||
# the quartz device), the pixel dimensions _aren't_ doubled, even though
|
||||
# the image has double size. In the latter case we don't have to scale the
|
||||
# numbers down.
|
||||
pix_ratio <- 1
|
||||
if (!grepl("^quartz", names(grDevices::dev.cur()))) {
|
||||
pix_ratio <- pixelratio
|
||||
}
|
||||
|
||||
# Return list of lists, where each inner list has left, right, top, bottom
|
||||
# values for a panel
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
p <- layout[i, , drop = FALSE]
|
||||
list(
|
||||
left = x_pos[p$l - 1] / pix_ratio,
|
||||
right = x_pos[p$r] / pix_ratio,
|
||||
bottom = y_pos[p$b] / pix_ratio,
|
||||
top = y_pos[p$t - 1] / pix_ratio
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
tryCatch({
|
||||
# Get info from built ggplot object
|
||||
info <- find_panel_info(p$build)
|
||||
|
||||
# Get ranges from gtable - it's possible for this to return more elements than
|
||||
# info, because it calculates positions even for panels that aren't present.
|
||||
# This can happen with facet_wrap.
|
||||
ranges <- find_panel_ranges(p$gtable, pixelratio)
|
||||
|
||||
for (i in seq_along(info)) {
|
||||
info[[i]]$range <- ranges[[i]]
|
||||
}
|
||||
|
||||
return(info)
|
||||
|
||||
}, error = function(e) {
|
||||
# If there was an error extracting info from the ggplot object, just return
|
||||
# a list with the error message.
|
||||
return(structure(list(), error = e$message))
|
||||
})
|
||||
}
|
||||
208
R/render-table.R
Normal file
208
R/render-table.R
Normal file
@@ -0,0 +1,208 @@
|
||||
#' Table Output
|
||||
#'
|
||||
#' Creates a reactive table that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} and have the CSS
|
||||
#' class name \code{shiny-html-output}.
|
||||
#'
|
||||
#' @param expr An expression that returns an R object that can be used with
|
||||
#' \code{\link[xtable]{xtable}}.
|
||||
#' @param striped,hover,bordered Logicals: if \code{TRUE}, apply the
|
||||
#' corresponding Bootstrap table format to the output table.
|
||||
#' @param spacing The spacing between the rows of the table (\code{xs}
|
||||
#' stands for "extra small", \code{s} for "small", \code{m} for "medium"
|
||||
#' and \code{l} for "large").
|
||||
#' @param width Table width. Must be a valid CSS unit (like "100%", "400px",
|
||||
#' "auto") or a number, which will be coerced to a string and
|
||||
#' have "px" appended.
|
||||
#' @param align A string that specifies the column alignment. If equal to
|
||||
#' \code{'l'}, \code{'c'} or \code{'r'}, then all columns will be,
|
||||
#' respectively, left-, center- or right-aligned. Otherwise, \code{align}
|
||||
#' must have the same number of characters as the resulting table (if
|
||||
#' \code{rownames = TRUE}, this will be equal to \code{ncol()+1}), with
|
||||
#' the \emph{i}-th character specifying the alignment for the
|
||||
#' \emph{i}-th column (besides \code{'l'}, \code{'c'} and
|
||||
#' \code{'r'}, \code{'?'} is also permitted - \code{'?'} is a placeholder
|
||||
#' for that particular column, indicating that it should keep its default
|
||||
#' alignment). If \code{NULL}, then all numeric/integer columns (including
|
||||
#' the row names, if they are numbers) will be right-aligned and
|
||||
#' everything else will be left-aligned (\code{align = '?'} produces the
|
||||
#' same result).
|
||||
#' @param rownames,colnames Logicals: include rownames? include colnames
|
||||
#' (column headers)?
|
||||
#' @param digits An integer specifying the number of decimal places for
|
||||
#' the numeric columns (this will not apply to columns with an integer
|
||||
#' class). If \code{digits} is set to a negative value, then the numeric
|
||||
#' columns will be displayed in scientific format with a precision of
|
||||
#' \code{abs(digits)} digits.
|
||||
#' @param na The string to use in the table cells whose values are missing
|
||||
#' (i.e. they either evaluate to \code{NA} or \code{NaN}).
|
||||
#' @param ... Arguments to be passed through to \code{\link[xtable]{xtable}}
|
||||
#' and \code{\link[xtable]{print.xtable}}.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})?
|
||||
#' This is useful if you want to save an expression in a variable.
|
||||
#' @param outputArgs A list of arguments to be passed through to the
|
||||
#' implicit call to \code{\link{tableOutput}} when \code{renderTable} is
|
||||
#' used in an interactive R Markdown document.
|
||||
#'
|
||||
#' @export
|
||||
renderTable <- function(expr, striped = FALSE, hover = FALSE,
|
||||
bordered = FALSE, spacing = c("s", "xs", "m", "l"),
|
||||
width = "auto", align = NULL,
|
||||
rownames = FALSE, colnames = TRUE,
|
||||
digits = NULL, na = "NA", ...,
|
||||
env = parent.frame(), quoted = FALSE,
|
||||
outputArgs=list()) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
if (!is.function(spacing)) spacing <- match.arg(spacing)
|
||||
|
||||
# A small helper function to create a wrapper for an argument that was
|
||||
# passed to renderTable()
|
||||
createWrapper <- function(arg) {
|
||||
if (is.function(arg)) wrapper <- arg
|
||||
else wrapper <- function() arg
|
||||
return(wrapper)
|
||||
}
|
||||
|
||||
# Create wrappers for most arguments so that functions can also be passed
|
||||
# in, rather than only literals (useful for shiny apps)
|
||||
stripedWrapper <- createWrapper(striped)
|
||||
hoverWrapper <- createWrapper(hover)
|
||||
borderedWrapper <- createWrapper(bordered)
|
||||
spacingWrapper <- createWrapper(spacing)
|
||||
widthWrapper <- createWrapper(width)
|
||||
alignWrapper <- createWrapper(align)
|
||||
rownamesWrapper <- createWrapper(rownames)
|
||||
colnamesWrapper <- createWrapper(colnames)
|
||||
digitsWrapper <- createWrapper(digits)
|
||||
naWrapper <- createWrapper(na)
|
||||
|
||||
renderFunc <- function(shinysession, name, ...) {
|
||||
striped <- stripedWrapper()
|
||||
hover <- hoverWrapper()
|
||||
bordered <- borderedWrapper()
|
||||
format <- c(striped = striped, hover = hover, bordered = bordered)
|
||||
spacing <- spacingWrapper()
|
||||
width <- widthWrapper()
|
||||
align <- alignWrapper()
|
||||
rownames <- rownamesWrapper()
|
||||
colnames <- colnamesWrapper()
|
||||
digits <- digitsWrapper()
|
||||
na <- naWrapper()
|
||||
|
||||
spacing_choices <- c("s", "xs", "m", "l")
|
||||
if (!(spacing %in% spacing_choices)) {
|
||||
stop(paste("`spacing` must be one of",
|
||||
paste0("'", spacing_choices, "'", collapse=", ")))
|
||||
}
|
||||
|
||||
# For css styling
|
||||
classNames <- paste0("table shiny-table",
|
||||
paste0(" table-", names(format)[format], collapse = "" ),
|
||||
paste0(" spacing-", spacing))
|
||||
|
||||
data <- func()
|
||||
data <- as.data.frame(data)
|
||||
|
||||
# Return NULL if no data is provided
|
||||
if (is.null(data) ||
|
||||
(is.data.frame(data) && nrow(data) == 0 && ncol(data) == 0))
|
||||
return(NULL)
|
||||
|
||||
# Separate the ... args to pass to xtable() vs print.xtable()
|
||||
dots <- list(...)
|
||||
xtable_argnames <- setdiff(names(formals(xtable)), c("x", "..."))
|
||||
xtable_args <- dots[intersect(names(dots), xtable_argnames)]
|
||||
non_xtable_args <- dots[setdiff(names(dots), xtable_argnames)]
|
||||
|
||||
# By default, numbers are right-aligned and everything else is left-aligned.
|
||||
defaultAlignment <- function(col) {
|
||||
if (is.numeric(col)) "r" else "l"
|
||||
}
|
||||
|
||||
# Figure out column alignment
|
||||
## Case 1: default alignment
|
||||
if (is.null(align) || align == "?") {
|
||||
names <- defaultAlignment(attr(data, "row.names"))
|
||||
cols <- paste(vapply(data, defaultAlignment, character(1)), collapse = "")
|
||||
cols <- paste0(names, cols)
|
||||
} else {
|
||||
## Case 2: user-specified alignment
|
||||
num_cols <- if (rownames) nchar(align) else nchar(align)+1
|
||||
valid <- !grepl("[^lcr\\?]", align)
|
||||
if (num_cols == ncol(data)+1 && valid) {
|
||||
cols <- if (rownames) align else paste0("r", align)
|
||||
defaults <- grep("\\?", strsplit(cols,"")[[1]])
|
||||
if (length(defaults) != 0) {
|
||||
vals <- vapply(data[,defaults-1], defaultAlignment, character(1))
|
||||
for (i in seq_len(length(defaults))) {
|
||||
substr(cols, defaults[i], defaults[i]) <- vals[i]
|
||||
}
|
||||
}
|
||||
} else if (nchar(align) == 1 && valid) {
|
||||
cols <- paste0(rep(align, ncol(data)+1), collapse="")
|
||||
} else {
|
||||
stop("`align` must contain only the characters `l`, `c`, `r` and/or `?` and",
|
||||
"have length either equal to 1 or to the total number of columns")
|
||||
}
|
||||
}
|
||||
|
||||
# Call xtable with its (updated) args
|
||||
xtable_args <- c(xtable_args, align = cols, digits = digits)
|
||||
xtable_res <- do.call(xtable, c(list(data), xtable_args))
|
||||
|
||||
# Set up print args
|
||||
print_args <- list(
|
||||
xtable_res,
|
||||
type = 'html',
|
||||
include.rownames = rownames,
|
||||
include.colnames = colnames,
|
||||
NA.string = na,
|
||||
html.table.attributes = paste0("class = '", htmlEscape(classNames, TRUE), "' ",
|
||||
"style = 'width:", validateCssUnit(width), ";'"))
|
||||
|
||||
print_args <- c(print_args, non_xtable_args)
|
||||
|
||||
# Capture the raw html table returned by print.xtable(), and store it in
|
||||
# a variable for further processing
|
||||
tab <- paste(utils::capture.output(do.call(print, print_args)),collapse = "\n")
|
||||
|
||||
# Add extra class to cells with NA value, to be able to style them separately
|
||||
tab <- gsub(paste(">", na, "<"), paste(" class='NA'>", na, "<"), tab)
|
||||
|
||||
# All further processing concerns the table headers, so we don't need to run
|
||||
# any of this if colnames=FALSE
|
||||
if (colnames) {
|
||||
# Make sure that the final html table has a proper header (not included
|
||||
# in the print.xtable() default)
|
||||
tab <- sub("<tr>", "<thead> <tr>", tab)
|
||||
tab <- sub("</tr>", "</tr> </thead> <tbody>", tab)
|
||||
tab <- sub("</table>$", "</tbody> </table>", tab)
|
||||
|
||||
# Update the `cols` string (which stores the alignment of each column) so
|
||||
# that it only includes the alignment for the table variables (and not
|
||||
# for the row.names)
|
||||
cols <- if (rownames) cols else substr(cols, 2, nchar(cols))
|
||||
|
||||
# Create a vector whose i-th entry corresponds to the i-th table variable
|
||||
# alignment (substituting "l" by "left", "c" by "center" and "r" by "right")
|
||||
cols <- strsplit(cols, "")[[1]]
|
||||
cols[cols == "l"] <- "left"
|
||||
cols[cols == "r"] <- "right"
|
||||
cols[cols == "c"] <- "center"
|
||||
|
||||
# Align each header accordingly (this guarantees that each header and its
|
||||
# corresponding column have the same alignment)
|
||||
for (i in seq_len(length(cols))) {
|
||||
tab <- sub("<th>", paste0("<th style='text-align: ", cols[i], ";'>"), tab)
|
||||
}
|
||||
}
|
||||
return(tab)
|
||||
}
|
||||
|
||||
# Main render function
|
||||
markRenderFunction(tableOutput, renderFunc, outputArgs = outputArgs)
|
||||
}
|
||||
67
R/run-url.R
67
R/run-url.R
@@ -14,18 +14,22 @@
|
||||
#' @param subdir A subdirectory in the repository that contains the app. By
|
||||
#' default, this function will run an app from the top level of the repo, but
|
||||
#' you can use a path such as `\code{"inst/shinyapp"}.
|
||||
#' @param destdir Directory to store the downloaded application files. If \code{NULL}
|
||||
#' (the default), the application files will be stored in a temporary directory
|
||||
#' and removed when the app exits
|
||||
#' @param ... Other arguments to be passed to \code{\link{runApp}()}, such as
|
||||
#' \code{port} and \code{launch.browser}.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' 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/")
|
||||
#' # Can run an app from a subdirectory in the archive
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
#' subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, ...) {
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, destdir = NULL, ...) {
|
||||
|
||||
if (!is.null(subdir) && ".." %in% strsplit(subdir, '/')[[1]])
|
||||
stop("'..' not allowed in subdir")
|
||||
@@ -43,8 +47,14 @@ runUrl <- function(url, filetype = NULL, subdir = NULL, ...) {
|
||||
stop("Unknown file extension.")
|
||||
|
||||
message("Downloading ", url)
|
||||
filePath <- tempfile('shinyapp', fileext=fileext)
|
||||
fileDir <- tempfile('shinyapp')
|
||||
if (is.null(destdir)) {
|
||||
filePath <- tempfile('shinyapp', fileext = fileext)
|
||||
fileDir <- tempfile('shinyapp')
|
||||
} else {
|
||||
fileDir <- destdir
|
||||
filePath <- paste(destdir, fileext)
|
||||
}
|
||||
|
||||
dir.create(fileDir, showWarnings = FALSE)
|
||||
if (download(url, filePath, mode = "wb", quiet = TRUE) != 0)
|
||||
stop("Failed to download URL ", url)
|
||||
@@ -61,13 +71,16 @@ runUrl <- function(url, filetype = NULL, subdir = NULL, ...) {
|
||||
untar2(filePath, exdir = fileDir)
|
||||
|
||||
} else if (fileext == ".zip") {
|
||||
first <- as.character(unzip(filePath, list=TRUE)$Name)[1]
|
||||
unzip(filePath, exdir = fileDir)
|
||||
first <- as.character(utils::unzip(filePath, list=TRUE)$Name)[1]
|
||||
utils::unzip(filePath, exdir = fileDir)
|
||||
}
|
||||
|
||||
if(is.null(destdir)){
|
||||
on.exit(unlink(fileDir, recursive = TRUE), add = TRUE)
|
||||
}
|
||||
on.exit(unlink(fileDir, recursive = TRUE), add = TRUE)
|
||||
|
||||
appdir <- file.path(fileDir, first)
|
||||
if (!file_test('-d', appdir)) appdir <- dirname(appdir)
|
||||
if (!utils::file_test('-d', appdir)) appdir <- dirname(appdir)
|
||||
|
||||
if (!is.null(subdir)) appdir <- file.path(appdir, subdir)
|
||||
runApp(appdir, ...)
|
||||
@@ -80,15 +93,16 @@ runUrl <- function(url, filetype = NULL, subdir = NULL, ...) {
|
||||
#' all valid values.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGist(3239667)
|
||||
#' runGist("https://gist.github.com/jcheng5/3239667")
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' runGist(3239667)
|
||||
#' runGist("https://gist.github.com/jcheng5/3239667")
|
||||
#'
|
||||
#' # Old URL format without username
|
||||
#' runGist("https://gist.github.com/3239667")
|
||||
#' # Old URL format without username
|
||||
#' runGist("https://gist.github.com/3239667")
|
||||
#' }
|
||||
#'
|
||||
runGist <- function(gist, ...) {
|
||||
runGist <- function(gist, destdir = NULL, ...) {
|
||||
|
||||
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
|
||||
sprintf('https://gist.github.com/%s/download', gist)
|
||||
@@ -98,7 +112,7 @@ runGist <- function(gist, ...) {
|
||||
stop('Unrecognized gist identifier format')
|
||||
}
|
||||
|
||||
runUrl(gistUrl, filetype=".tar.gz", ...)
|
||||
runUrl(gistUrl, filetype = ".zip", destdir = destdir, ...)
|
||||
}
|
||||
|
||||
|
||||
@@ -110,15 +124,16 @@ runGist <- function(gist, ...) {
|
||||
#' Defaults to \code{"master"}.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGitHub("shiny_example", "rstudio")
|
||||
#' # or runGitHub("rstudio/shiny_example")
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' runGitHub("shiny_example", "rstudio")
|
||||
#' # or runGitHub("rstudio/shiny_example")
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the repo
|
||||
#' runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
|
||||
#' # Can run an app from a subdirectory in the repo
|
||||
#' runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
runGitHub <- function(repo, username = getOption("github.user"),
|
||||
ref = "master", subdir = NULL, ...) {
|
||||
ref = "master", subdir = NULL, destdir = NULL, ...) {
|
||||
|
||||
if (grepl('/', repo)) {
|
||||
res <- strsplit(repo, '/')[[1]]
|
||||
@@ -130,5 +145,5 @@ runGitHub <- function(repo, username = getOption("github.user"),
|
||||
url <- paste("https://github.com/", username, "/", repo, "/archive/",
|
||||
ref, ".tar.gz", sep = "")
|
||||
|
||||
runUrl(url, subdir=subdir, ...)
|
||||
runUrl(url, subdir = subdir, destdir = destdir, ...)
|
||||
}
|
||||
|
||||
111
R/server-input-handlers.R
Normal file
111
R/server-input-handlers.R
Normal file
@@ -0,0 +1,111 @@
|
||||
# Create a map for input handlers and register the defaults.
|
||||
inputHandlers <- Map$new()
|
||||
|
||||
#' Register an Input Handler
|
||||
#'
|
||||
#' Adds an input handler for data of this type. When called, Shiny will use the
|
||||
#' function provided to refine the data passed back from the client (after being
|
||||
#' deserialized by jsonlite) before making it available in the \code{input}
|
||||
#' variable of the \code{server.R} file.
|
||||
#'
|
||||
#' This function will register the handler for the duration of the R process
|
||||
#' (unless Shiny is explicitly reloaded). For that reason, the \code{type} used
|
||||
#' should be very specific to this package to minimize the risk of colliding
|
||||
#' with another Shiny package which might use this data type name. We recommend
|
||||
#' the format of "packageName.widgetName".
|
||||
#'
|
||||
#' Currently Shiny registers the following handlers: \code{shiny.matrix},
|
||||
#' \code{shiny.number}, and \code{shiny.date}.
|
||||
#'
|
||||
#' The \code{type} of a custom Shiny Input widget will be deduced using the
|
||||
#' \code{getType()} JavaScript function on the registered Shiny inputBinding.
|
||||
#' @param type The type for which the handler should be added -- should be a
|
||||
#' single-element character vector.
|
||||
#' @param fun The handler function. This is the function that will be used to
|
||||
#' parse the data delivered from the client before it is available in the
|
||||
#' \code{input} variable. The function will be called with the following three
|
||||
#' parameters:
|
||||
#' \enumerate{
|
||||
#' \item{The value of this input as provided by the client, deserialized
|
||||
#' using jsonlite.}
|
||||
#' \item{The \code{shinysession} in which the input exists.}
|
||||
#' \item{The name of the input.}
|
||||
#' }
|
||||
#' @param force If \code{TRUE}, will overwrite any existing handler without
|
||||
#' warning. If \code{FALSE}, will throw an error if this class already has
|
||||
#' a handler defined.
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Register an input handler which rounds a input number to the nearest integer
|
||||
#' registerInputHandler("mypackage.validint", function(x, shinysession, name) {
|
||||
#' if (is.null(x)) return(NA)
|
||||
#' round(x)
|
||||
#' })
|
||||
#'
|
||||
#' ## On the Javascript side, the associated input binding must have a corresponding getType method:
|
||||
#' getType: function(el) {
|
||||
#' return "mypackage.validint";
|
||||
#' }
|
||||
#'
|
||||
#' }
|
||||
#' @seealso \code{\link{removeInputHandler}}
|
||||
#' @export
|
||||
registerInputHandler <- function(type, fun, force=FALSE){
|
||||
if (inputHandlers$containsKey(type) && !force){
|
||||
stop("There is already an input handler for type: ", type)
|
||||
}
|
||||
inputHandlers$set(type, fun)
|
||||
}
|
||||
|
||||
#' Deregister an Input Handler
|
||||
#'
|
||||
#' Removes an Input Handler. Rather than using the previously specified handler
|
||||
#' for data of this type, the default jsonlite serialization will be used.
|
||||
#'
|
||||
#' @param type The type for which handlers should be removed.
|
||||
#' @return The handler previously associated with this \code{type}, if one
|
||||
#' existed. Otherwise, \code{NULL}.
|
||||
#' @seealso \code{\link{registerInputHandler}}
|
||||
#' @export
|
||||
removeInputHandler <- function(type){
|
||||
inputHandlers$remove(type)
|
||||
}
|
||||
|
||||
# Takes a list-of-lists and returns a matrix. The lists
|
||||
# must all be the same length. NULL is replaced by NA.
|
||||
registerInputHandler("shiny.matrix", function(data, ...) {
|
||||
if (length(data) == 0)
|
||||
return(matrix(nrow=0, ncol=0))
|
||||
|
||||
m <- matrix(unlist(lapply(data, function(x) {
|
||||
sapply(x, function(y) {
|
||||
ifelse(is.null(y), NA, y)
|
||||
})
|
||||
})), nrow = length(data[[1]]), ncol = length(data))
|
||||
return(m)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.number", function(val, ...){
|
||||
ifelse(is.null(val), NA, val)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.date", function(val, ...){
|
||||
# First replace NULLs with NA, then convert to Date vector
|
||||
datelist <- ifelse(lapply(val, is.null), NA, val)
|
||||
as.Date(unlist(datelist))
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.datetime", function(val, ...){
|
||||
# First replace NULLs with NA, then convert to POSIXct vector
|
||||
times <- lapply(val, function(x) {
|
||||
if (is.null(x)) NA
|
||||
else x
|
||||
})
|
||||
as.POSIXct(unlist(times), origin = "1970-01-01", tz = "UTC")
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.action", function(val, ...) {
|
||||
# mark up the action button value with a special class so we can recognize it later
|
||||
class(val) <- c(class(val), "shinyActionButtonValue")
|
||||
val
|
||||
})
|
||||
706
R/server.R
706
R/server.R
@@ -1,110 +1,7 @@
|
||||
#' @include globals.R
|
||||
#' @include server-input-handlers.R
|
||||
|
||||
appsByToken <- Map$new()
|
||||
|
||||
# Create a map for input handlers and register the defaults.
|
||||
inputHandlers <- Map$new()
|
||||
|
||||
#' Register an Input Handler
|
||||
#'
|
||||
#' Adds an input handler for data of this type. When called, Shiny will use the
|
||||
#' function provided to refine the data passed back from the client (after being
|
||||
#' deserialized by RJSONIO) before making it available in the \code{input}
|
||||
#' variable of the \code{server.R} file.
|
||||
#'
|
||||
#' This function will register the handler for the duration of the R process
|
||||
#' (unless Shiny is explicitly reloaded). For that reason, the \code{type} used
|
||||
#' should be very specific to this package to minimize the risk of colliding
|
||||
#' with another Shiny package which might use this data type name. We recommend
|
||||
#' the format of "packageName.widgetName".
|
||||
#'
|
||||
#' Currently Shiny registers the following handlers: \code{shiny.matrix},
|
||||
#' \code{shiny.number}, and \code{shiny.date}.
|
||||
#'
|
||||
#' The \code{type} of a custom Shiny Input widget will be deduced using the
|
||||
#' \code{getType()} JavaScript function on the registered Shiny inputBinding.
|
||||
#' @param type The type for which the handler should be added -- should be a
|
||||
#' single-element character vector.
|
||||
#' @param fun The handler function. This is the function that will be used to
|
||||
#' parse the data delivered from the client before it is available in the
|
||||
#' \code{input} variable. The function will be called with the following three
|
||||
#' parameters:
|
||||
#' \enumerate{
|
||||
#' \item{The value of this input as provided by the client, deserialized
|
||||
#' using RJSONIO.}
|
||||
#' \item{The \code{shinysession} in which the input exists.}
|
||||
#' \item{The name of the input.}
|
||||
#' }
|
||||
#' @param force If \code{TRUE}, will overwrite any existing handler without
|
||||
#' warning. If \code{FALSE}, will throw an error if this class already has
|
||||
#' a handler defined.
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Register an input handler which rounds a input number to the nearest integer
|
||||
#' registerInputHandler("mypackage.validint", function(x, shinysession, name) {
|
||||
#' if (is.null(x)) return(NA)
|
||||
#' round(x)
|
||||
#' })
|
||||
#'
|
||||
#' ## On the Javascript side, the associated input binding must have a corresponding getType method:
|
||||
#' getType: function(el) {
|
||||
#' return "mypackage.validint";
|
||||
#' }
|
||||
#'
|
||||
#' }
|
||||
#' @seealso \code{\link{removeInputHandler}}
|
||||
#' @export
|
||||
registerInputHandler <- function(type, fun, force=FALSE){
|
||||
if (inputHandlers$containsKey(type) && !force){
|
||||
stop("There is already an input handler for type: ", type)
|
||||
}
|
||||
inputHandlers$set(type, fun)
|
||||
}
|
||||
|
||||
#' Deregister an Input Handler
|
||||
#'
|
||||
#' Removes an Input Handler. Rather than using the previously specified handler
|
||||
#' for data of this type, the default RJSONIO serialization will be used.
|
||||
#'
|
||||
#' @param type The type for which handlers should be removed.
|
||||
#' @return The handler previously associated with this \code{type}, if one
|
||||
#' existed. Otherwise, \code{NULL}.
|
||||
#' @seealso \code{\link{registerInputHandler}}
|
||||
#' @export
|
||||
removeInputHandler <- function(type){
|
||||
inputHandlers$remove(type)
|
||||
}
|
||||
|
||||
# Takes a list-of-lists and returns a matrix. The lists
|
||||
# must all be the same length. NULL is replaced by NA.
|
||||
registerInputHandler("shiny.matrix", function(data, ...) {
|
||||
if (length(data) == 0)
|
||||
return(matrix(nrow=0, ncol=0))
|
||||
|
||||
m <- matrix(unlist(lapply(data, function(x) {
|
||||
sapply(x, function(y) {
|
||||
ifelse(is.null(y), NA, y)
|
||||
})
|
||||
})), nrow = length(data[[1]]), ncol = length(data))
|
||||
return(m)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.number", function(val, ...){
|
||||
ifelse(is.null(val), NA, val)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.date", function(val, ...){
|
||||
# First replace NULLs with NA, then convert to Date vector
|
||||
datelist <- ifelse(lapply(val, is.null), NA, val)
|
||||
as.Date(unlist(datelist))
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.action", function(val, ...) {
|
||||
# mark up the action button value with a special class so we can recognize it later
|
||||
class(val) <- c(class(val), "shinyActionButtonValue")
|
||||
val
|
||||
})
|
||||
|
||||
# Provide a character representation of the WS that can be used
|
||||
# as a key in a Map.
|
||||
wsToKey <- function(WS) {
|
||||
@@ -169,13 +66,6 @@ addResourcePath <- function(prefix, directoryPath) {
|
||||
|
||||
existing <- .globals$resources[[prefix]]
|
||||
|
||||
if (!is.null(existing)) {
|
||||
if (!identical(existing$directoryPath, directoryPath)) {
|
||||
warning("Overriding existing prefix ", prefix, " => ",
|
||||
existing$directoryPath)
|
||||
}
|
||||
}
|
||||
|
||||
.globals$resources[[prefix]] <- list(directoryPath=directoryPath,
|
||||
func=staticHandler(directoryPath))
|
||||
}
|
||||
@@ -209,14 +99,15 @@ resourcePathHandler <- function(req) {
|
||||
#'
|
||||
#' Defines the server-side logic of the Shiny application. This generally
|
||||
#' involves creating functions that map user inputs to various kinds of output.
|
||||
#' In older versions of Shiny, it was necessary to call \code{shinyServer()} in
|
||||
#' the \code{server.R} file, but this is no longer required as of Shiny 0.10.
|
||||
#' Now the \code{server.R} file may simply return the appropriate server
|
||||
#' function (as the last expression in the code), without calling
|
||||
#' \code{shinyServer()}.
|
||||
#'
|
||||
#' @param func The server function for this application. See the details section
|
||||
#' for more information.
|
||||
#'
|
||||
#' @details
|
||||
#' Call \code{shinyServer} from your application's \code{server.R} file, passing
|
||||
#' in a "server function" that provides the server-side logic of your
|
||||
#' application.
|
||||
#' Call \code{shinyServer} from your application's \code{server.R}
|
||||
#' file, passing in a "server function" that provides the server-side logic of
|
||||
#' your application.
|
||||
#'
|
||||
#' The server function will be called when each client (web browser) first loads
|
||||
#' the Shiny application's page. It must take an \code{input} and an
|
||||
@@ -227,6 +118,9 @@ resourcePathHandler <- function(req) {
|
||||
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{tutorial} for more
|
||||
#' on how to write a server function.
|
||||
#'
|
||||
#' @param func The server function for this application. See the details section
|
||||
#' for more information.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # A very simple Shiny app that takes a message from the user
|
||||
@@ -236,6 +130,16 @@ resourcePathHandler <- function(req) {
|
||||
#' toupper(input$message)
|
||||
#' })
|
||||
#' })
|
||||
#'
|
||||
#'
|
||||
#' # It is also possible for a server.R file to simply return the function,
|
||||
#' # without calling shinyServer().
|
||||
#' # For example, the server.R file could contain just the following:
|
||||
#' function(input, output, session) {
|
||||
#' output$uppercase <- renderText({
|
||||
#' toupper(input$message)
|
||||
#' })
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
@@ -250,9 +154,10 @@ decodeMessage <- function(data) {
|
||||
}
|
||||
|
||||
if (readInt(1) != 0x01020202L) {
|
||||
# use native encoding for the message
|
||||
nativeData <- iconv(rawToChar(data), 'UTF-8')
|
||||
return(fromJSON(nativeData, asText=TRUE, simplify=FALSE))
|
||||
# Treat message as UTF-8
|
||||
charData <- rawToChar(data)
|
||||
Encoding(charData) <- 'UTF-8'
|
||||
return(jsonlite::fromJSON(charData, simplifyVector=FALSE))
|
||||
}
|
||||
|
||||
i <- 5
|
||||
@@ -297,150 +202,161 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
if (!is.null(getOption("shiny.observer.error", NULL))) {
|
||||
warning(
|
||||
call. = FALSE,
|
||||
"options(shiny.observer.error) is no longer supported; please unset it!"
|
||||
)
|
||||
stopApp()
|
||||
}
|
||||
|
||||
shinysession <- ShinySession$new(ws)
|
||||
appsByToken$set(shinysession$token, shinysession)
|
||||
shinysession$setShowcase(.globals$showcaseDefault)
|
||||
|
||||
ws$onMessage(function(binary, msg) {
|
||||
# To ease transition from websockets-based code. Should remove once we're stable.
|
||||
if (is.character(msg))
|
||||
msg <- charToRaw(msg)
|
||||
messageHandler <- function(binary, msg) {
|
||||
withReactiveDomain(shinysession, {
|
||||
# To ease transition from websockets-based code. Should remove once we're stable.
|
||||
if (is.character(msg))
|
||||
msg <- charToRaw(msg)
|
||||
|
||||
if (isTRUE(getOption('shiny.trace'))) {
|
||||
if (binary)
|
||||
message("RECV ", '$$binary data$$')
|
||||
else
|
||||
message("RECV ", rawToChar(msg))
|
||||
}
|
||||
if (isTRUE(getOption('shiny.trace'))) {
|
||||
if (binary)
|
||||
message("RECV ", '$$binary data$$')
|
||||
else
|
||||
message("RECV ", rawToChar(msg))
|
||||
}
|
||||
|
||||
if (identical(charToRaw("\003\xe9"), msg))
|
||||
return()
|
||||
if (identical(charToRaw("\003\xe9"), msg))
|
||||
return()
|
||||
|
||||
msg <- decodeMessage(msg)
|
||||
msg <- decodeMessage(msg)
|
||||
|
||||
# Do our own list simplifying here. sapply/simplify2array give names to
|
||||
# character vectors, which is rarely what we want.
|
||||
if (!is.null(msg$data)) {
|
||||
for (name in names(msg$data)) {
|
||||
val <- msg$data[[name]]
|
||||
# Do our own list simplifying here. sapply/simplify2array give names to
|
||||
# character vectors, which is rarely what we want.
|
||||
if (!is.null(msg$data)) {
|
||||
for (name in names(msg$data)) {
|
||||
val <- msg$data[[name]]
|
||||
|
||||
splitName <- strsplit(name, ':')[[1]]
|
||||
if (length(splitName) > 1) {
|
||||
msg$data[[name]] <- NULL
|
||||
splitName <- strsplit(name, ':')[[1]]
|
||||
if (length(splitName) > 1) {
|
||||
msg$data[[name]] <- NULL
|
||||
|
||||
if (!inputHandlers$containsKey(splitName[[2]])){
|
||||
# No input handler registered for this type
|
||||
stop("No handler registered for for type ", name)
|
||||
}
|
||||
if (!inputHandlers$containsKey(splitName[[2]])){
|
||||
# No input handler registered for this type
|
||||
stop("No handler registered for type ", name)
|
||||
}
|
||||
|
||||
msg$data[[ splitName[[1]] ]] <-
|
||||
msg$data[[ splitName[[1]] ]] <-
|
||||
inputHandlers$get(splitName[[2]])(
|
||||
val,
|
||||
shinysession,
|
||||
splitName[[1]] )
|
||||
}
|
||||
else if (is.list(val) && is.null(names(val))) {
|
||||
val_flat <- unlist(val, recursive = TRUE)
|
||||
val,
|
||||
shinysession,
|
||||
splitName[[1]] )
|
||||
}
|
||||
else if (is.list(val) && is.null(names(val))) {
|
||||
val_flat <- unlist(val, recursive = TRUE)
|
||||
|
||||
if (is.null(val_flat)) {
|
||||
# This is to assign NULL instead of deleting the item
|
||||
msg$data[name] <- list(NULL)
|
||||
} else {
|
||||
msg$data[[name]] <- val_flat
|
||||
if (is.null(val_flat)) {
|
||||
# This is to assign NULL instead of deleting the item
|
||||
msg$data[name] <- list(NULL)
|
||||
} else {
|
||||
msg$data[[name]] <- val_flat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(
|
||||
msg$method,
|
||||
init = {
|
||||
switch(
|
||||
msg$method,
|
||||
init = {
|
||||
|
||||
serverFunc <- serverFuncSource()
|
||||
if (!identicalFunctionBodies(serverFunc, appvars$server)) {
|
||||
appvars$server <- serverFunc
|
||||
if (!is.null(appvars$server))
|
||||
{
|
||||
# Tag this function as the Shiny server function. A debugger may use this
|
||||
# tag to give this function special treatment.
|
||||
# It's very important that it's appvars$server itself and NOT a copy that
|
||||
# is invoked, otherwise new breakpoints won't be picked up.
|
||||
attr(appvars$server, "shinyServerFunction") <- TRUE
|
||||
registerDebugHook("server", appvars, "Server Function")
|
||||
serverFunc <- withReactiveDomain(NULL, serverFuncSource())
|
||||
if (!identicalFunctionBodies(serverFunc, appvars$server)) {
|
||||
appvars$server <- serverFunc
|
||||
if (!is.null(appvars$server))
|
||||
{
|
||||
# Tag this function as the Shiny server function. A debugger may use this
|
||||
# tag to give this function special treatment.
|
||||
# It's very important that it's appvars$server itself and NOT a copy that
|
||||
# is invoked, otherwise new breakpoints won't be picked up.
|
||||
attr(appvars$server, "shinyServerFunction") <- TRUE
|
||||
registerDebugHook("server", appvars, "Server Function")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check for switching into/out of showcase mode
|
||||
if (.globals$showcaseOverride &&
|
||||
exists(".clientdata_url_search", where = msg$data)) {
|
||||
mode <- showcaseModeOfQuerystring(msg$data$.clientdata_url_search)
|
||||
if (!is.null(mode))
|
||||
shinysession$setShowcase(mode)
|
||||
}
|
||||
# Check for switching into/out of showcase mode
|
||||
if (.globals$showcaseOverride &&
|
||||
exists(".clientdata_url_search", where = msg$data)) {
|
||||
mode <- showcaseModeOfQuerystring(msg$data$.clientdata_url_search)
|
||||
if (!is.null(mode))
|
||||
shinysession$setShowcase(mode)
|
||||
}
|
||||
|
||||
shinysession$manageInputs(msg$data)
|
||||
shinysession$manageInputs(msg$data)
|
||||
|
||||
# The client tells us what singletons were rendered into
|
||||
# the initial page
|
||||
if (!is.null(msg$data$.clientdata_singletons)) {
|
||||
shinysession$singletons <<- strsplit(
|
||||
msg$data$.clientdata_singletons, ',')[[1]]
|
||||
}
|
||||
# The client tells us what singletons were rendered into
|
||||
# the initial page
|
||||
if (!is.null(msg$data$.clientdata_singletons)) {
|
||||
shinysession$singletons <- strsplit(
|
||||
msg$data$.clientdata_singletons, ',')[[1]]
|
||||
}
|
||||
|
||||
local({
|
||||
args <- list(
|
||||
input=shinysession$input,
|
||||
output=.createOutputWriter(shinysession))
|
||||
local({
|
||||
args <- argsForServerFunc(serverFunc, shinysession)
|
||||
|
||||
# The clientData and session arguments are optional; check if
|
||||
# each exists
|
||||
if ('clientData' %in% names(formals(serverFunc)))
|
||||
args$clientData <- shinysession$clientData
|
||||
|
||||
if ('session' %in% names(formals(serverFunc)))
|
||||
args$session <- shinysession$session
|
||||
|
||||
withReactiveDomain(shinysession$session, {
|
||||
do.call(appvars$server, args)
|
||||
withReactiveDomain(shinysession, {
|
||||
do.call(
|
||||
# No corresponding ..stacktraceoff; the server func is pure
|
||||
# user code
|
||||
wrapFunctionLabel(appvars$server, "server",
|
||||
..stacktraceon = TRUE
|
||||
),
|
||||
args
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
update = {
|
||||
shinysession$manageInputs(msg$data)
|
||||
},
|
||||
shinysession$dispatch(msg)
|
||||
)
|
||||
shinysession$manageHiddenOutputs()
|
||||
},
|
||||
update = {
|
||||
shinysession$manageInputs(msg$data)
|
||||
},
|
||||
shinysession$dispatch(msg)
|
||||
)
|
||||
shinysession$manageHiddenOutputs()
|
||||
|
||||
if (exists(".shiny__stdout", globalenv()) &&
|
||||
exists("HTTP_GUID", ws$request)) {
|
||||
# safe to assume we're in shiny-server
|
||||
shiny_stdout <- get(".shiny__stdout", globalenv())
|
||||
if (exists(".shiny__stdout", globalenv()) &&
|
||||
exists("HTTP_GUID", ws$request)) {
|
||||
# safe to assume we're in shiny-server
|
||||
shiny_stdout <- get(".shiny__stdout", globalenv())
|
||||
|
||||
# eNter a flushReact
|
||||
writeLines(paste("_n_flushReact ", get("HTTP_GUID", ws$request),
|
||||
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
|
||||
sep=""), con=shiny_stdout)
|
||||
flush(shiny_stdout)
|
||||
# eNter a flushReact
|
||||
writeLines(paste("_n_flushReact ", get("HTTP_GUID", ws$request),
|
||||
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
|
||||
sep=""), con=shiny_stdout)
|
||||
flush(shiny_stdout)
|
||||
|
||||
flushReact()
|
||||
flushReact()
|
||||
|
||||
# eXit a flushReact
|
||||
writeLines(paste("_x_flushReact ", get("HTTP_GUID", ws$request),
|
||||
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
|
||||
sep=""), con=shiny_stdout)
|
||||
flush(shiny_stdout)
|
||||
} else {
|
||||
flushReact()
|
||||
}
|
||||
lapply(appsByToken$values(), function(shinysession) {
|
||||
shinysession$flushOutput()
|
||||
NULL
|
||||
# eXit a flushReact
|
||||
writeLines(paste("_x_flushReact ", get("HTTP_GUID", ws$request),
|
||||
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
|
||||
sep=""), con=shiny_stdout)
|
||||
flush(shiny_stdout)
|
||||
} else {
|
||||
flushReact()
|
||||
}
|
||||
lapply(appsByToken$values(), function(shinysession) {
|
||||
shinysession$flushOutput()
|
||||
NULL
|
||||
})
|
||||
})
|
||||
}
|
||||
ws$onMessage(function(binary, msg) {
|
||||
# If unhandled errors occur, make sure they get properly logged
|
||||
withLogErrors(messageHandler(binary, msg))
|
||||
})
|
||||
|
||||
ws$onClose(function() {
|
||||
shinysession$close()
|
||||
shinysession$wsClosed()
|
||||
appsByToken$remove(shinysession$token)
|
||||
})
|
||||
|
||||
@@ -450,6 +366,26 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
return(appHandlers)
|
||||
}
|
||||
|
||||
# Determine what arguments should be passed to this serverFunc. All server funcs
|
||||
# must take input and output, but clientData (obsolete) and session are
|
||||
# optional.
|
||||
argsForServerFunc <- function(serverFunc, session) {
|
||||
args <- list(input = session$input, output = .createOutputWriter(session))
|
||||
|
||||
paramNames <- names(formals(serverFunc))
|
||||
|
||||
# The clientData and session arguments are optional; check if
|
||||
# each exists
|
||||
|
||||
if ("clientData" %in% paramNames)
|
||||
args$clientData <- session$clientData
|
||||
|
||||
if ("session" %in% paramNames)
|
||||
args$session <- session
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
getEffectiveBody <- function(func) {
|
||||
# Note: NULL values are OK. isS4(NULL) returns FALSE, body(NULL)
|
||||
# returns NULL.
|
||||
@@ -542,8 +478,8 @@ serviceApp <- function() {
|
||||
|
||||
#' Run Shiny Application
|
||||
#'
|
||||
#' Runs a Shiny application. This function normally does not return; interrupt
|
||||
#' R to stop the application (usually by pressing Ctrl+C or Esc).
|
||||
#' Runs a Shiny application. This function normally does not return; interrupt R
|
||||
#' to stop the application (usually by pressing Ctrl+C or Esc).
|
||||
#'
|
||||
#' The host parameter was introduced in Shiny 0.9.0. Its default value of
|
||||
#' \code{"127.0.0.1"} means that, contrary to previous versions of Shiny, only
|
||||
@@ -551,11 +487,20 @@ serviceApp <- function() {
|
||||
#' clients to connect, use the value \code{"0.0.0.0"} instead (which was the
|
||||
#' value that was hard-coded into Shiny in 0.8.0 and earlier).
|
||||
#'
|
||||
#' @param appDir The directory of the application. Should contain
|
||||
#' \code{server.R}, plus, either \code{ui.R} or a \code{www} directory that
|
||||
#' contains the file \code{index.html}. Defaults to the working directory.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' @param appDir The application to run. Should be one of the following:
|
||||
#' \itemize{
|
||||
#' \item A directory containing \code{server.R}, plus, either \code{ui.R} or
|
||||
#' a \code{www} directory that contains the file \code{index.html}.
|
||||
#' \item A directory containing \code{app.R}.
|
||||
#' \item An \code{.R} file containing a Shiny application, ending with an
|
||||
#' expression that produces a Shiny app object.
|
||||
#' \item A list with \code{ui} and \code{server} components.
|
||||
#' \item A Shiny app object created by \code{\link{shinyApp}}.
|
||||
#' }
|
||||
#' @param port The TCP port that the application should listen on. If the
|
||||
#' \code{port} is not specified, and the \code{shiny.port} option is set (with
|
||||
#' \code{options(shiny.port = XX)}), then that port will be used. Otherwise,
|
||||
#' use a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only. This value of this parameter can also be a
|
||||
@@ -570,8 +515,8 @@ serviceApp <- function() {
|
||||
#' the value \code{"showcase"}, shows application code and metadata from a
|
||||
#' \code{DESCRIPTION} file in the application directory alongside the
|
||||
#' application. If set to \code{"normal"}, displays the application normally.
|
||||
#' Defaults to \code{"auto"}, which displays the application in the mode
|
||||
#' given in its \code{DESCRIPTION} file, if any.
|
||||
#' Defaults to \code{"auto"}, which displays the application in the mode given
|
||||
#' in its \code{DESCRIPTION} file, if any.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
@@ -580,22 +525,37 @@ serviceApp <- function() {
|
||||
#'
|
||||
#' # Start app in a subdirectory called myapp
|
||||
#' runApp("myapp")
|
||||
#' }
|
||||
#'
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # Apps can be run without a server.r and ui.r file
|
||||
#' runApp(list(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' ))
|
||||
#'
|
||||
#'
|
||||
#' # Apps can be run without a server.r and ui.r file
|
||||
#' runApp(list(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' ))
|
||||
#' # Running a Shiny app object
|
||||
#' app <- shinyApp(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' )
|
||||
#' runApp(app)
|
||||
#' }
|
||||
#' @export
|
||||
runApp <- function(appDir=getwd(),
|
||||
port=NULL,
|
||||
port=getOption('shiny.port'),
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
@@ -610,7 +570,8 @@ runApp <- function(appDir=getwd(),
|
||||
host <- '0.0.0.0'
|
||||
|
||||
# Make warnings print immediately
|
||||
ops <- options(warn = 1)
|
||||
# Set pool.scheduler to support pool package
|
||||
ops <- options(warn = 1, pool.scheduler = scheduleTask)
|
||||
on.exit(options(ops), add = TRUE)
|
||||
|
||||
workerId(workerId)
|
||||
@@ -621,7 +582,7 @@ runApp <- function(appDir=getwd(),
|
||||
# SHINY_SERVER_VERSION, those will return "" which is considered less than
|
||||
# any valid version.
|
||||
ver <- Sys.getenv('SHINY_SERVER_VERSION')
|
||||
if (compareVersion(ver, .shinyServerMinVersion) < 0) {
|
||||
if (utils::compareVersion(ver, .shinyServerMinVersion) < 0) {
|
||||
warning('Shiny Server v', .shinyServerMinVersion,
|
||||
' or later is required; please upgrade!')
|
||||
}
|
||||
@@ -635,27 +596,50 @@ runApp <- function(appDir=getwd(),
|
||||
# If appDir specifies a path, and display mode is specified in the
|
||||
# DESCRIPTION file at that path, apply it here.
|
||||
if (is.character(appDir)) {
|
||||
desc <- file.path.ci(appDir, "DESCRIPTION")
|
||||
# if appDir specifies a .R file (single-file Shiny app), look for the
|
||||
# DESCRIPTION in the parent directory
|
||||
desc <- file.path.ci(
|
||||
if (tolower(tools::file_ext(appDir)) == "r")
|
||||
dirname(appDir)
|
||||
else
|
||||
appDir, "DESCRIPTION")
|
||||
if (file.exists(desc)) {
|
||||
con <- file(desc, encoding = checkEncoding(desc))
|
||||
on.exit(close(con), add = TRUE)
|
||||
settings <- read.dcf(con)
|
||||
if ("DisplayMode" %in% colnames(settings)) {
|
||||
mode <- settings[1,"DisplayMode"]
|
||||
mode <- settings[1, "DisplayMode"]
|
||||
if (mode == "Showcase") {
|
||||
setShowcaseDefault(1)
|
||||
if ("IncludeWWW" %in% colnames(settings)) {
|
||||
.globals$IncludeWWW <- as.logical(settings[1, "IncludeWWW"])
|
||||
if (is.na(.globals$IncludeWWW)) {
|
||||
stop("In your Description file, `IncludeWWW` ",
|
||||
"must be set to `True` (default) or `False`")
|
||||
}
|
||||
} else {
|
||||
.globals$IncludeWWW <- TRUE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## default is to show the .js, .css and .html files in the www directory
|
||||
## (if not in showcase mode, this variable will simply be ignored)
|
||||
if (is.null(.globals$IncludeWWW) || is.na(.globals$IncludeWWW)) {
|
||||
.globals$IncludeWWW <- TRUE
|
||||
}
|
||||
|
||||
# If display mode is specified as an argument, apply it (overriding the
|
||||
# value specified in DESCRIPTION, if any).
|
||||
display.mode <- match.arg(display.mode)
|
||||
if (display.mode == "normal")
|
||||
if (display.mode == "normal") {
|
||||
setShowcaseDefault(0)
|
||||
else if (display.mode == "showcase")
|
||||
}
|
||||
else if (display.mode == "showcase") {
|
||||
setShowcaseDefault(1)
|
||||
}
|
||||
|
||||
require(shiny)
|
||||
|
||||
@@ -676,7 +660,14 @@ runApp <- function(appDir=getwd(),
|
||||
}
|
||||
else {
|
||||
# Try up to 20 random ports
|
||||
port <- p_randomInt(3000, 8000)
|
||||
while (TRUE) {
|
||||
port <- p_randomInt(3000, 8000)
|
||||
# Reject ports in this range that are considered unsafe by Chrome
|
||||
# http://superuser.com/questions/188058/which-ports-are-considered-unsafe-on-chrome
|
||||
if (!port %in% c(3659, 4045, 6000, 6665:6669)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Test port to see if we can use it
|
||||
@@ -690,10 +681,12 @@ runApp <- function(appDir=getwd(),
|
||||
}
|
||||
|
||||
appParts <- as.shiny.appobj(appDir)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
# Set up the onEnd before we call onStart, so that it gets called even if an
|
||||
# error happens in onStart.
|
||||
if (!is.null(appParts$onEnd))
|
||||
on.exit(appParts$onEnd(), add = TRUE)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
|
||||
server <- startApp(appParts, port, host, quiet)
|
||||
|
||||
@@ -720,16 +713,27 @@ runApp <- function(appDir=getwd(),
|
||||
callAppHook("onAppStop", appUrl)
|
||||
}, add = TRUE)
|
||||
|
||||
.globals$reterror <- NULL
|
||||
.globals$retval <- NULL
|
||||
.globals$stopped <- FALSE
|
||||
shinyCallingHandlers(
|
||||
while (!.globals$stopped) {
|
||||
serviceApp()
|
||||
Sys.sleep(0.001)
|
||||
}
|
||||
# Top-level ..stacktraceoff..; matches with ..stacktraceon in observe(),
|
||||
# reactive(), Callbacks$invoke(), and others
|
||||
..stacktraceoff..(
|
||||
captureStackTraces(
|
||||
while (!.globals$stopped) {
|
||||
serviceApp()
|
||||
Sys.sleep(0.001)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
return(.globals$retval)
|
||||
if (isTRUE(.globals$reterror)) {
|
||||
stop(.globals$retval)
|
||||
}
|
||||
else if (.globals$retval$visible)
|
||||
.globals$retval$value
|
||||
else
|
||||
invisible(.globals$retval$value)
|
||||
}
|
||||
|
||||
#' Stop the currently running Shiny app
|
||||
@@ -741,8 +745,25 @@ runApp <- function(appDir=getwd(),
|
||||
#' \code{\link{runApp}}.
|
||||
#'
|
||||
#' @export
|
||||
stopApp <- function(returnValue = NULL) {
|
||||
.globals$retval <- returnValue
|
||||
stopApp <- function(returnValue = invisible()) {
|
||||
# reterror will indicate whether retval is an error (i.e. it should be passed
|
||||
# to stop() when the serviceApp loop stops) or a regular value (in which case
|
||||
# it should simply be returned with the appropriate visibility).
|
||||
.globals$reterror <- FALSE
|
||||
..stacktraceoff..(
|
||||
tryCatch(
|
||||
{
|
||||
captureStackTraces(
|
||||
.globals$retval <- withVisible(..stacktraceon..(force(returnValue)))
|
||||
)
|
||||
},
|
||||
error = function(e) {
|
||||
.globals$retval <- e
|
||||
.globals$reterror <- TRUE
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
.globals$stopped <- TRUE
|
||||
httpuv::interrupt()
|
||||
}
|
||||
@@ -765,15 +786,16 @@ stopApp <- function(returnValue = NULL) {
|
||||
#' code or commentary.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # List all available examples
|
||||
#' runExample()
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # List all available examples
|
||||
#' runExample()
|
||||
#'
|
||||
#' # Run one of the examples
|
||||
#' runExample("01_hello")
|
||||
#' # Run one of the examples
|
||||
#' runExample("01_hello")
|
||||
#'
|
||||
#' # Print the directory containing the code for all examples
|
||||
#' system.file("examples", package="shiny")
|
||||
#' # Print the directory containing the code for all examples
|
||||
#' system.file("examples", package="shiny")
|
||||
#' }
|
||||
#' @export
|
||||
runExample <- function(example=NA,
|
||||
@@ -804,3 +826,135 @@ runExample <- function(example=NA,
|
||||
display.mode = display.mode)
|
||||
}
|
||||
}
|
||||
|
||||
#' Run a gadget
|
||||
#'
|
||||
#' Similar to \code{runApp}, but handles \code{input$cancel} automatically, and
|
||||
#' if running in RStudio, defaults to viewing the app in the Viewer pane.
|
||||
#'
|
||||
#' @param app Either a Shiny app object as created by
|
||||
#' \code{\link[=shiny]{shinyApp}} et al, or, a UI object.
|
||||
#' @param server Ignored if \code{app} is a Shiny app object; otherwise, passed
|
||||
#' along to \code{shinyApp} (i.e. \code{shinyApp(ui = app, server = server)}).
|
||||
#' @param port See \code{\link[=shiny]{runApp}}.
|
||||
#' @param viewer Specify where the gadget should be displayed--viewer pane,
|
||||
#' dialog window, or external browser--by passing in a call to one of the
|
||||
#' \code{\link{viewer}} functions.
|
||||
#' @param stopOnCancel If \code{TRUE} (the default), then an \code{observeEvent}
|
||||
#' is automatically created that handles \code{input$cancel} by calling
|
||||
#' \code{stopApp()} with an error. Pass \code{FALSE} if you want to handle
|
||||
#' \code{input$cancel} yourself.
|
||||
#' @return The value returned by the gadget.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' library(shiny)
|
||||
#'
|
||||
#' ui <- fillPage(...)
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' ...
|
||||
#' }
|
||||
#'
|
||||
#' # Either pass ui/server as separate arguments...
|
||||
#' runGadget(ui, server)
|
||||
#'
|
||||
#' # ...or as a single app object
|
||||
#' runGadget(shinyApp(ui, server))
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runGadget <- function(app, server = NULL, port = getOption("shiny.port"),
|
||||
viewer = paneViewer(), stopOnCancel = TRUE) {
|
||||
|
||||
if (!is.shiny.appobj(app)) {
|
||||
app <- shinyApp(app, server)
|
||||
}
|
||||
|
||||
if (isTRUE(stopOnCancel)) {
|
||||
app <- decorateServerFunc(app, function(input, output, session) {
|
||||
observeEvent(input$cancel, {
|
||||
stopApp(stop("User cancel", call. = FALSE))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (is.null(viewer)) {
|
||||
viewer <- utils::browseURL
|
||||
}
|
||||
|
||||
shiny::runApp(app, port = port, launch.browser = viewer)
|
||||
}
|
||||
|
||||
# Add custom functionality to a Shiny app object's server func
|
||||
decorateServerFunc <- function(appobj, serverFunc) {
|
||||
origServerFuncSource <- appobj$serverFuncSource
|
||||
appobj$serverFuncSource <- function() {
|
||||
origServerFunc <- origServerFuncSource()
|
||||
function(input, output, session) {
|
||||
serverFunc(input, output, session)
|
||||
|
||||
# The clientData and session arguments are optional; check if
|
||||
# each exists
|
||||
args <- argsForServerFunc(origServerFunc, session)
|
||||
do.call(origServerFunc, args)
|
||||
}
|
||||
}
|
||||
appobj
|
||||
}
|
||||
|
||||
#' Viewer options
|
||||
#'
|
||||
#' Use these functions to control where the gadget is displayed in RStudio (or
|
||||
#' other R environments that emulate RStudio's viewer pane/dialog APIs). If
|
||||
#' viewer APIs are not available in the current R environment, then the gadget
|
||||
#' will be displayed in the system's default web browser (see
|
||||
#' \code{\link[utils]{browseURL}}).
|
||||
#'
|
||||
#' @return A function that takes a single \code{url} parameter, suitable for
|
||||
#' passing as the \code{viewer} argument of \code{\link{runGadget}}.
|
||||
#'
|
||||
#' @rdname viewer
|
||||
#' @name viewer
|
||||
NULL
|
||||
|
||||
#' @param minHeight The minimum height (in pixels) desired to show the gadget in
|
||||
#' the viewer pane. If a positive number, resize the pane if necessary to show
|
||||
#' at least that many pixels. If \code{NULL}, use the existing viewer pane
|
||||
#' size. If \code{"maximize"}, use the maximum available vertical space.
|
||||
#' @rdname viewer
|
||||
#' @export
|
||||
paneViewer <- function(minHeight = NULL) {
|
||||
viewer <- getOption("viewer")
|
||||
if (is.null(viewer)) {
|
||||
utils::browseURL
|
||||
} else {
|
||||
function(url) {
|
||||
viewer(url, minHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#' @param dialogName The window title to display for the dialog.
|
||||
#' @param width,height The desired dialog width/height, in pixels.
|
||||
#' @rdname viewer
|
||||
#' @export
|
||||
dialogViewer <- function(dialogName, width = 600, height = 600) {
|
||||
viewer <- getOption("shinygadgets.showdialog")
|
||||
if (is.null(viewer)) {
|
||||
utils::browseURL
|
||||
} else {
|
||||
function(url) {
|
||||
viewer(dialogName, url, width = width, height = height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#' @param browser See \code{\link[utils]{browseURL}}.
|
||||
#' @rdname viewer
|
||||
#' @export
|
||||
browserViewer <- function(browser = getOption("browser")) {
|
||||
function(url) {
|
||||
utils::browseURL(url, browser = browser)
|
||||
}
|
||||
}
|
||||
|
||||
112
R/shinyui.R
112
R/shinyui.R
@@ -14,81 +14,53 @@ NULL
|
||||
#' # 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'
|
||||
path <- 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
|
||||
tagList(
|
||||
tags$head(
|
||||
singleton(tags$script(src = path, type = 'text/javascript'))
|
||||
),
|
||||
...,
|
||||
tags$script(HTML('MathJax.Hub.Queue(["Typeset", MathJax.Hub]);'))
|
||||
tags$script(HTML('if (window.MathJax) MathJax.Hub.Queue(["Typeset", MathJax.Hub]);'))
|
||||
)
|
||||
}
|
||||
|
||||
renderPage <- function(ui, connection, showcase=0) {
|
||||
# If the ui is a NOT complete document (created by htmlTemplate()), then do some
|
||||
# preprocessing and make sure it's a complete document.
|
||||
if (!inherits(ui, "html_document")) {
|
||||
if (showcase > 0)
|
||||
ui <- showcaseUI(ui)
|
||||
|
||||
if (showcase > 0)
|
||||
ui <- tagList(tags$head(showcaseHead()), ui)
|
||||
# Wrap ui in body tag if it doesn't already have a single top-level body tag.
|
||||
if (!(inherits(ui, "shiny.tag") && ui$name == "body"))
|
||||
ui <- tags$body(ui)
|
||||
|
||||
result <- renderTags(ui)
|
||||
|
||||
deps <- c(
|
||||
list(
|
||||
htmlDependency("json2", "2014.02.04", c(href="shared"), script = "json2-min.js"),
|
||||
htmlDependency("jquery", "1.11.0", c(href="shared"), script = "jquery.js"),
|
||||
htmlDependency("shiny", packageVersion("shiny"), c(href="shared"),
|
||||
script = "shiny.js", stylesheet = "shiny.css")
|
||||
),
|
||||
result$dependencies
|
||||
)
|
||||
deps <- resolveDependencies(deps)
|
||||
deps <- lapply(deps, createWebDependency)
|
||||
depStr <- paste(sapply(deps, function(dep) {
|
||||
sprintf("%s[%s]", dep$name, dep$version)
|
||||
}), collapse = ";")
|
||||
depHtml <- renderDependencies(deps, "href")
|
||||
|
||||
# write preamble
|
||||
writeLines(c('<!DOCTYPE html>',
|
||||
'<html>',
|
||||
'<head>',
|
||||
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>',
|
||||
sprintf(' <script type="application/shiny-singletons">%s</script>',
|
||||
paste(result$singletons, collapse = ',')
|
||||
),
|
||||
sprintf(' <script type="application/html-dependencies">%s</script>',
|
||||
depStr
|
||||
),
|
||||
depHtml
|
||||
),
|
||||
con = connection)
|
||||
writeLines(c(result$head,
|
||||
'</head>',
|
||||
'<body>',
|
||||
recursive=TRUE),
|
||||
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)
|
||||
# Put the body into the default template
|
||||
ui <- htmlTemplate(
|
||||
system.file("template", "default.html", package = "shiny"),
|
||||
body = ui
|
||||
)
|
||||
}
|
||||
|
||||
# write end document
|
||||
writeLines(c('</body>',
|
||||
'</html>'),
|
||||
con = connection)
|
||||
shiny_deps <- list(
|
||||
htmlDependency("json2", "2014.02.04", c(href="shared"), script = "json2-min.js"),
|
||||
htmlDependency("jquery", "1.11.3", c(href="shared"), script = "jquery.min.js"),
|
||||
htmlDependency("babel-polyfill", "6.7.2", c(href="shared"), script = "babel-polyfill.min.js"),
|
||||
htmlDependency("shiny", utils::packageVersion("shiny"), c(href="shared"),
|
||||
script = if (getOption("shiny.minified", TRUE)) "shiny.min.js" else "shiny.js",
|
||||
stylesheet = "shiny.css")
|
||||
)
|
||||
html <- renderDocument(ui, shiny_deps, processDep = createWebDependency)
|
||||
writeUTF8(html, con = connection)
|
||||
}
|
||||
|
||||
#' Create a Shiny UI handler
|
||||
#'
|
||||
#' Historically this function was used in ui.R files to register a user
|
||||
#' interface with Shiny. It is no longer required; simply ensure that the last
|
||||
#' expression to be returned from ui.R is a user interface. This function is
|
||||
#' kept for backwards compatibility with older applications. It returns the
|
||||
#' value that is passed to it.
|
||||
#' interface with Shiny. It is no longer required as of Shiny 0.10; simply
|
||||
#' ensure that the last expression to be returned from ui.R is a user interface.
|
||||
#' This function is kept for backwards compatibility with older applications. It
|
||||
#' returns the value that is passed to it.
|
||||
#'
|
||||
#' @param ui A user interace definition
|
||||
#' @return The user interface definition, without modifications or side effects.
|
||||
@@ -99,7 +71,7 @@ shinyUI <- function(ui) {
|
||||
ui
|
||||
}
|
||||
|
||||
uiHttpHandler <- function(ui, path = "/") {
|
||||
uiHttpHandler <- function(ui, uiPattern = "^/$") {
|
||||
|
||||
force(ui)
|
||||
|
||||
@@ -107,10 +79,10 @@ uiHttpHandler <- function(ui, path = "/") {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
if (req$PATH_INFO != path)
|
||||
if (!isTRUE(grepl(uiPattern, req$PATH_INFO)))
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
textConn <- file(open = "w+")
|
||||
on.exit(close(textConn))
|
||||
|
||||
showcaseMode <- .globals$showcaseDefault
|
||||
@@ -120,15 +92,21 @@ uiHttpHandler <- function(ui, path = "/") {
|
||||
showcaseMode <- mode
|
||||
}
|
||||
uiValue <- if (is.function(ui)) {
|
||||
if (length(formals(ui)) > 0)
|
||||
ui(req)
|
||||
else
|
||||
ui()
|
||||
}
|
||||
else
|
||||
if (length(formals(ui)) > 0) {
|
||||
# No corresponding ..stacktraceoff.., this is pure user code
|
||||
..stacktraceon..(ui(req))
|
||||
} else {
|
||||
# No corresponding ..stacktraceoff.., this is pure user code
|
||||
..stacktraceon..(ui())
|
||||
}
|
||||
} else {
|
||||
ui
|
||||
}
|
||||
if (is.null(uiValue))
|
||||
return(NULL)
|
||||
|
||||
renderPage(uiValue, textConn, showcaseMode)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
html <- paste(readLines(textConn, encoding = 'UTF-8'), collapse='\n')
|
||||
return(httpResponse(200, content=enc2utf8(html)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,24 +12,75 @@ globalVariables('func')
|
||||
#' an output ID.
|
||||
#' @param renderFunc A function that is suitable for assigning to a Shiny output
|
||||
#' slot.
|
||||
#' @param outputArgs A list of arguments to pass to the \code{uiFunc}. Render
|
||||
#' functions should include \code{outputArgs = list()} in their own parameter
|
||||
#' list, and pass through the value to \code{markRenderFunction}, to allow
|
||||
#' app authors to customize outputs. (Currently, this is only supported for
|
||||
#' dynamically generated UIs, such as those created by Shiny code snippets
|
||||
#' embedded in R Markdown documents).
|
||||
#' @return The \code{renderFunc} function, with annotations.
|
||||
#'
|
||||
#' @export
|
||||
markRenderFunction <- function(uiFunc, renderFunc) {
|
||||
markRenderFunction <- function(uiFunc, renderFunc, outputArgs = list()) {
|
||||
# a mutable object that keeps track of whether `useRenderFunction` has been
|
||||
# executed (this usually only happens when rendering Shiny code snippets in
|
||||
# an interactive R Markdown document); its initial value is FALSE
|
||||
hasExecuted <- Mutable$new()
|
||||
hasExecuted$set(FALSE)
|
||||
|
||||
origRenderFunc <- renderFunc
|
||||
renderFunc <- function(...) {
|
||||
# if the user provided something through `outputArgs` BUT the
|
||||
# `useRenderFunction` was not executed, then outputArgs will be ignored,
|
||||
# so throw a warning to let user know the correct usage
|
||||
if (length(outputArgs) != 0 && !hasExecuted$get()) {
|
||||
warning("Unused argument: outputArgs. The argument outputArgs is only ",
|
||||
"meant to be used when embedding snippets of Shiny code in an ",
|
||||
"R Markdown code chunk (using runtime: shiny). When running a ",
|
||||
"full Shiny app, please set the output arguments directly in ",
|
||||
"the corresponding output function of your UI code.")
|
||||
# stop warning from happening again for the same object
|
||||
hasExecuted$set(TRUE)
|
||||
}
|
||||
if (is.null(formals(origRenderFunc))) origRenderFunc()
|
||||
else origRenderFunc(...)
|
||||
}
|
||||
|
||||
structure(renderFunc,
|
||||
class = c("shiny.render.function", "function"),
|
||||
outputFunc = uiFunc)
|
||||
class = c("shiny.render.function", "function"),
|
||||
outputFunc = uiFunc,
|
||||
outputArgs = outputArgs,
|
||||
hasExecuted = hasExecuted)
|
||||
}
|
||||
|
||||
useRenderFunction <- function(renderFunc, inline = FALSE) {
|
||||
outputFunction <- attr(renderFunc, "outputFunc")
|
||||
outputArgs <- attr(renderFunc, "outputArgs")
|
||||
hasExecuted <- attr(renderFunc, "hasExecuted")
|
||||
hasExecuted$set(TRUE)
|
||||
|
||||
for (arg in names(outputArgs)) {
|
||||
if (!arg %in% names(formals(outputFunction))) {
|
||||
stop(paste0("Unused argument: in 'outputArgs', '",
|
||||
arg, "' is not an valid argument for ",
|
||||
"the output function"))
|
||||
outputArgs[[arg]] <- NULL
|
||||
}
|
||||
}
|
||||
|
||||
id <- createUniqueId(8, "out")
|
||||
# Make the id the first positional argument
|
||||
outputArgs <- c(list(id), outputArgs)
|
||||
|
||||
o <- getDefaultReactiveDomain()$output
|
||||
if (!is.null(o))
|
||||
o[[id]] <- renderFunc
|
||||
if (is.logical(formals(outputFunction)[["inline"]])) {
|
||||
outputFunction(id, inline = inline)
|
||||
} else outputFunction(id)
|
||||
|
||||
if (is.logical(formals(outputFunction)[["inline"]]) && !("inline" %in% names(outputArgs))) {
|
||||
outputArgs[["inline"]] <- inline
|
||||
}
|
||||
|
||||
do.call(outputFunction, outputArgs)
|
||||
}
|
||||
|
||||
#' @export
|
||||
@@ -38,141 +89,6 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
useRenderFunction(x, inline = inline)
|
||||
}
|
||||
|
||||
#' Plot Output
|
||||
#'
|
||||
#' Renders a reactive plot that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-plot-output}.
|
||||
#'
|
||||
#' @seealso For more details on how the plots are generated, and how to control
|
||||
#' the output, see \code{\link{plotPNG}}.
|
||||
#'
|
||||
#' @param expr An expression that generates a plot.
|
||||
#' @param width,height The width/height of the rendered plot, in pixels; or
|
||||
#' \code{'auto'} to use the \code{offsetWidth}/\code{offsetHeight} of the HTML
|
||||
#' element that is bound to this plot. You can also pass in a function that
|
||||
#' returns the width/height in pixels or \code{'auto'}; in the body of the
|
||||
#' function you may reference reactive values and functions. When rendering an
|
||||
#' inline plot, you must provide numeric values (in pixels) to both
|
||||
#' \code{width} and \code{height}.
|
||||
#' @param res Resolution of resulting plot, in pixels per inch. This value is
|
||||
#' passed to \code{\link{png}}. Note that this affects the resolution of PNG
|
||||
#' rendering in R; it won't change the actual ppi of the browser.
|
||||
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that generates a plot (deprecated; use \code{expr}
|
||||
#' instead).
|
||||
#'
|
||||
#' @export
|
||||
renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPlot: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
args <- list(...)
|
||||
|
||||
if (is.function(width))
|
||||
widthWrapper <- reactive({ width() })
|
||||
else
|
||||
widthWrapper <- NULL
|
||||
|
||||
if (is.function(height))
|
||||
heightWrapper <- reactive({ height() })
|
||||
else
|
||||
heightWrapper <- NULL
|
||||
|
||||
# If renderPlot isn't going to adapt to the height of the div, then the
|
||||
# div needs to adapt to the height of renderPlot. By default, plotOutput
|
||||
# sets the height to 400px, so to make it adapt we need to override it
|
||||
# with NULL.
|
||||
outputFunc <- plotOutput
|
||||
if (!identical(height, 'auto')) formals(outputFunc)['height'] <- list(NULL)
|
||||
|
||||
return(markRenderFunction(outputFunc, function(shinysession, name, ...) {
|
||||
if (!is.null(widthWrapper))
|
||||
width <- widthWrapper()
|
||||
if (!is.null(heightWrapper))
|
||||
height <- heightWrapper()
|
||||
|
||||
# Note that these are reactive calls. A change to the width and height
|
||||
# will inherently cause a reactive plot to redraw (unless width and
|
||||
# height were explicitly specified).
|
||||
prefix <- 'output_'
|
||||
if (width == 'auto')
|
||||
width <- shinysession$clientData[[paste(prefix, name, '_width', sep='')]];
|
||||
if (height == 'auto')
|
||||
height <- shinysession$clientData[[paste(prefix, name, '_height', sep='')]];
|
||||
|
||||
if (is.null(width) || is.null(height) || width <= 0 || height <= 0)
|
||||
return(NULL)
|
||||
|
||||
# Resolution multiplier
|
||||
pixelratio <- shinysession$clientData$pixelratio
|
||||
if (is.null(pixelratio))
|
||||
pixelratio <- 1
|
||||
|
||||
coordmap <- NULL
|
||||
plotFunc <- function() {
|
||||
# Actually perform the plotting
|
||||
result <- withVisible(func())
|
||||
if (result$visible) {
|
||||
# Use capture.output to squelch printing to the actual console; we
|
||||
# are only interested in plot output
|
||||
capture.output(print(result$value))
|
||||
}
|
||||
|
||||
# Now capture some graphics device info before we close it
|
||||
usrCoords <- par('usr')
|
||||
usrBounds <- usrCoords
|
||||
if (par('xlog')) {
|
||||
usrBounds[c(1,2)] <- 10 ^ usrBounds[c(1,2)]
|
||||
}
|
||||
if (par('ylog')) {
|
||||
usrBounds[c(3,4)] <- 10 ^ usrBounds[c(3,4)]
|
||||
}
|
||||
|
||||
coordmap <<- list(
|
||||
usr = c(
|
||||
left = usrCoords[1],
|
||||
right = usrCoords[2],
|
||||
bottom = usrCoords[3],
|
||||
top = usrCoords[4]
|
||||
),
|
||||
# The bounds of the plot area, in DOM pixels
|
||||
bounds = c(
|
||||
left = grconvertX(usrBounds[1], 'user', 'nfc') * width,
|
||||
right = grconvertX(usrBounds[2], 'user', 'nfc') * width,
|
||||
bottom = (1-grconvertY(usrBounds[3], 'user', 'nfc')) * height,
|
||||
top = (1-grconvertY(usrBounds[4], 'user', 'nfc')) * height
|
||||
),
|
||||
log = c(
|
||||
x = par('xlog'),
|
||||
y = par('ylog')
|
||||
),
|
||||
pixelratio = pixelratio
|
||||
)
|
||||
}
|
||||
|
||||
outfile <- do.call(plotPNG, c(plotFunc, width=width*pixelratio,
|
||||
height=height*pixelratio, res=res*pixelratio, args))
|
||||
on.exit(unlink(outfile))
|
||||
|
||||
# Return a list of attributes for the img
|
||||
return(list(
|
||||
src=shinysession$fileUrl(name, outfile, contentType='image/png'),
|
||||
width=width, height=height, coordmap=coordmap
|
||||
))
|
||||
}))
|
||||
}
|
||||
|
||||
#' Image file output
|
||||
#'
|
||||
#' Renders a reactive image that is suitable for assigning to an \code{output}
|
||||
@@ -204,13 +120,23 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
#' it is sent to the client browser? Generally speaking, if the image is a
|
||||
#' temp file generated within \code{func}, then this should be \code{TRUE};
|
||||
#' if the image is not a temp file, this should be \code{FALSE}.
|
||||
#'
|
||||
#' @param outputArgs A list of arguments to be passed through to the implicit
|
||||
#' call to \code{\link{imageOutput}} when \code{renderImage} is used in an
|
||||
#' interactive R Markdown document.
|
||||
#' @export
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' shinyServer(function(input, output, clientData) {
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("n", "Number of observations", 2, 1000, 500),
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2"),
|
||||
#' plotOutput("plot3")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#'
|
||||
#' # A plot of fixed size
|
||||
#' output$plot1 <- renderImage({
|
||||
@@ -232,14 +158,14 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
#' output$plot2 <- renderImage({
|
||||
#' # Read plot2's width and height. These are reactive values, so this
|
||||
#' # expression will re-run whenever these values change.
|
||||
#' width <- clientData$output_plot2_width
|
||||
#' height <- clientData$output_plot2_height
|
||||
#' width <- session$clientData$output_plot2_width
|
||||
#' height <- session$clientData$output_plot2_height
|
||||
#'
|
||||
#' # A temp file to save the output.
|
||||
#' outfile <- tempfile(fileext='.png')
|
||||
#'
|
||||
#' png(outfile, width=width, height=height)
|
||||
#' hist(rnorm(input$obs))
|
||||
#' hist(rnorm(input$n))
|
||||
#' dev.off()
|
||||
#'
|
||||
#' # Return a list containing the filename
|
||||
@@ -250,6 +176,8 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
#' }, deleteFile = TRUE)
|
||||
#'
|
||||
#' # Send a pre-rendered image, and don't delete the image after sending it
|
||||
#' # NOTE: For this example to work, it would require files in a subdirectory
|
||||
#' # named images/
|
||||
#' output$plot3 <- renderImage({
|
||||
#' # When input$n is 1, filename is ./images/image1.jpeg
|
||||
#' filename <- normalizePath(file.path('./images',
|
||||
@@ -258,14 +186,15 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
#' # Return a list containing the filename
|
||||
#' list(src = filename)
|
||||
#' }, deleteFile = FALSE)
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
deleteFile=TRUE) {
|
||||
deleteFile=TRUE, outputArgs=list()) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
return(markRenderFunction(imageOutput, function(shinysession, name, ...) {
|
||||
renderFunc <- 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.
|
||||
@@ -274,11 +203,7 @@ renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
}
|
||||
|
||||
# If contentType not specified, autodetect based on extension
|
||||
if (is.null(imageinfo$contentType)) {
|
||||
contentType <- getContentType(sub('^.*\\.', '', basename(imageinfo$src)))
|
||||
} else {
|
||||
contentType <- imageinfo$contentType
|
||||
}
|
||||
contentType <- imageinfo$contentType %OR% getContentType(imageinfo$src)
|
||||
|
||||
# Extra values are everything in imageinfo except 'src' and 'contentType'
|
||||
extra_attr <- imageinfo[!names(imageinfo) %in% c('src', 'contentType')]
|
||||
@@ -286,55 +211,12 @@ renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
# 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 expr An expression that returns an R object that can be used with
|
||||
#' \code{\link[xtable]{xtable}}.
|
||||
#' @param ... Arguments to be passed through to \code{\link[xtable]{xtable}} and
|
||||
#' \code{\link[xtable]{print.xtable}}.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns an R object that can be used with
|
||||
#' \code{\link[xtable]{xtable}} (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @export
|
||||
renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderTable: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
markRenderFunction(tableOutput, function() {
|
||||
classNames <- getOption('shiny.table.class') %OR% 'data table table-bordered table-condensed'
|
||||
data <- func()
|
||||
|
||||
if (is.null(data) || identical(data, data.frame()))
|
||||
return("")
|
||||
|
||||
return(paste(
|
||||
capture.output(
|
||||
print(xtable(data, ...),
|
||||
type='html',
|
||||
html.table.attributes=paste('class="',
|
||||
htmlEscape(classNames, TRUE),
|
||||
'"',
|
||||
sep=''), ...)),
|
||||
collapse="\n"))
|
||||
})
|
||||
markRenderFunction(imageOutput, renderFunc, outputArgs = outputArgs)
|
||||
}
|
||||
|
||||
|
||||
#' Printable Output
|
||||
#'
|
||||
#' Makes a reactive version of the given function that captures any printed
|
||||
@@ -357,28 +239,28 @@ renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL)
|
||||
#' 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).
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param width The value for \code{\link{options}('width')}.
|
||||
#' @param outputArgs A list of arguments to be passed through to the implicit
|
||||
#' call to \code{\link{verbatimTextOutput}} when \code{renderPrint} is used
|
||||
#' in an interactive R Markdown document.
|
||||
#' @seealso \code{\link{renderText}} for displaying the value returned from a
|
||||
#' function, instead of the printed output.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#' @export
|
||||
renderPrint <- function(expr, env = parent.frame(), quoted = FALSE, func = NULL,
|
||||
width = getOption('width')) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPrint: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
renderPrint <- function(expr, env = parent.frame(), quoted = FALSE,
|
||||
width = getOption('width'), outputArgs=list()) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
markRenderFunction(verbatimTextOutput, function() {
|
||||
renderFunc <- function(shinysession, name, ...) {
|
||||
op <- options(width = width)
|
||||
on.exit(options(op), add = TRUE)
|
||||
paste(capture.output(func()), collapse = "\n")
|
||||
})
|
||||
paste(utils::capture.output(func()), collapse = "\n")
|
||||
}
|
||||
|
||||
markRenderFunction(verbatimTextOutput, renderFunc, outputArgs = outputArgs)
|
||||
}
|
||||
|
||||
#' Text Output
|
||||
@@ -399,8 +281,9 @@ renderPrint <- function(expr, env = parent.frame(), quoted = FALSE, func = NULL,
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns an R object that can be used as an
|
||||
#' argument to \code{cat}.(deprecated; use \code{expr} instead).
|
||||
#' @param outputArgs A list of arguments to be passed through to the implicit
|
||||
#' call to \code{\link{textOutput}} when \code{renderText} is used in an
|
||||
#' interactive R Markdown document.
|
||||
#'
|
||||
#' @seealso \code{\link{renderPrint}} for capturing the print output of a
|
||||
#' function, rather than the returned text value.
|
||||
@@ -408,17 +291,16 @@ renderPrint <- function(expr, env = parent.frame(), quoted = FALSE, func = NULL,
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#' @export
|
||||
renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderText: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
renderText <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
outputArgs=list()) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
renderFunc <- function(shinysession, name, ...) {
|
||||
value <- func()
|
||||
return(paste(utils::capture.output(cat(value)), collapse="\n"))
|
||||
}
|
||||
|
||||
markRenderFunction(textOutput, function() {
|
||||
value <- func()
|
||||
return(paste(capture.output(cat(value)), collapse="\n"))
|
||||
})
|
||||
markRenderFunction(textOutput, renderFunc, outputArgs = outputArgs)
|
||||
}
|
||||
|
||||
#' UI Output
|
||||
@@ -434,46 +316,45 @@ renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' or a list of such objects (deprecated; use \code{expr} instead).
|
||||
#' @param outputArgs A list of arguments to be passed through to the implicit
|
||||
#' call to \code{\link{uiOutput}} when \code{renderUI} is used in an
|
||||
#' interactive R Markdown document.
|
||||
#'
|
||||
#' @seealso conditionalPanel
|
||||
#'
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' output$moreControls <- renderUI({
|
||||
#' list(
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' uiOutput("moreControls")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' output$moreControls <- renderUI({
|
||||
#' tagList(
|
||||
#' sliderInput("n", "N", 1, 1000, 500),
|
||||
#' textInput("label", "Label")
|
||||
#' )
|
||||
#' })
|
||||
#' }
|
||||
renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderUI: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
renderUI <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
outputArgs=list()) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
markRenderFunction(uiOutput, function(shinysession, name, ...) {
|
||||
renderFunc <- function(shinysession, name, ...) {
|
||||
result <- func()
|
||||
if (is.null(result) || length(result) == 0)
|
||||
return(NULL)
|
||||
|
||||
result <- takeSingletons(result, shinysession$singletons, desingleton=FALSE)$ui
|
||||
result <- surroundSingletons(result)
|
||||
dependencies <- lapply(resolveDependencies(findDependencies(result)),
|
||||
createWebDependency)
|
||||
names(dependencies) <- NULL
|
||||
processDeps(result, shinysession)
|
||||
}
|
||||
|
||||
# renderTags returns a list with head, singletons, and html
|
||||
output <- list(
|
||||
html = doRenderTags(result),
|
||||
deps = dependencies
|
||||
)
|
||||
|
||||
return(output)
|
||||
})
|
||||
markRenderFunction(uiOutput, renderFunc, outputArgs = outputArgs)
|
||||
}
|
||||
|
||||
#' File Downloads
|
||||
@@ -499,28 +380,40 @@ renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
#' example \code{"text/csv"} or \code{"image/png"}. If \code{NULL} or
|
||||
#' \code{NA}, the content type will be guessed based on the filename
|
||||
#' extension, or \code{application/octet-stream} if the extension is unknown.
|
||||
#' @param outputArgs A list of arguments to be passed through to the implicit
|
||||
#' call to \code{\link{downloadButton}} when \code{downloadHandler} is used
|
||||
#' in an interactive R Markdown document.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # In server.R:
|
||||
#' output$downloadData <- downloadHandler(
|
||||
#' filename = function() {
|
||||
#' paste('data-', Sys.Date(), '.csv', sep='')
|
||||
#' },
|
||||
#' content = function(file) {
|
||||
#' write.csv(data, file)
|
||||
#' }
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' downloadLink("downloadData", "Download")
|
||||
#' )
|
||||
#'
|
||||
#' # In ui.R:
|
||||
#' downloadLink('downloadData', 'Download')
|
||||
#' server <- function(input, output) {
|
||||
#' # Our dataset
|
||||
#' data <- mtcars
|
||||
#'
|
||||
#' output$downloadData <- downloadHandler(
|
||||
#' filename = function() {
|
||||
#' paste("data-", Sys.Date(), ".csv", sep="")
|
||||
#' },
|
||||
#' content = function(file) {
|
||||
#' write.csv(data, file)
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
downloadHandler <- function(filename, content, contentType=NA) {
|
||||
return(markRenderFunction(downloadButton, function(shinysession, name, ...) {
|
||||
downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()) {
|
||||
renderFunc <- function(shinysession, name, ...) {
|
||||
shinysession$registerDownload(name, filename, contentType, content)
|
||||
}))
|
||||
}
|
||||
markRenderFunction(downloadButton, renderFunc, outputArgs = outputArgs)
|
||||
}
|
||||
|
||||
#' Table output with the JavaScript library DataTables
|
||||
@@ -534,7 +427,9 @@ downloadHandler <- function(filename, content, contentType=NA) {
|
||||
#' \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.
|
||||
#' character string. Note this only applies to the root-level elements of the
|
||||
#' options list, and the \code{I()} notation does not work for lower-level
|
||||
#' elements in the list.
|
||||
#' @param expr An expression that returns a data frame or a matrix.
|
||||
#' @param options A list of initialization options to be passed to DataTables,
|
||||
#' or a function to return such a list.
|
||||
@@ -542,35 +437,125 @@ downloadHandler <- function(filename, content, contentType=NA) {
|
||||
#' 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/}).
|
||||
#' instance to be available (\url{http://datatables.net/extensions/}).
|
||||
#' @param escape Whether to escape HTML entities in the table: \code{TRUE} means
|
||||
#' to escape the whole table, and \code{FALSE} means not to escape it.
|
||||
#' Alternatively, you can specify numeric column indices or column names to
|
||||
#' indicate which columns to escape, e.g. \code{1:5} (the first 5 columns),
|
||||
#' \code{c(1, 3, 4)}, or \code{c(-1, -3)} (all columns except the first and
|
||||
#' third), or \code{c('Species', 'Sepal.Length')}.
|
||||
#' @param outputArgs A list of arguments to be passed through to the implicit
|
||||
#' call to \code{\link{dataTableOutput}} when \code{renderDataTable} is used
|
||||
#' in an interactive R Markdown document.
|
||||
#'
|
||||
#' @references \url{http://datatables.net}
|
||||
#' @note This function only provides the server-side version of DataTables
|
||||
#' (using R to process the data object on the server side). There is a
|
||||
#' separate package \pkg{DT} (\url{https://github.com/rstudio/DT}) that allows
|
||||
#' you to create both server-side and client-side DataTables, and supports
|
||||
#' additional DataTables features. Consider using \code{DT::renderDataTable()}
|
||||
#' and \code{DT::dataTableOutput()} (see
|
||||
#' \url{http://rstudio.github.io/DT/shiny.html} for more information).
|
||||
#' @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.');}")
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # pass a callback function to DataTables using I()
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' fluidRow(
|
||||
#' column(12,
|
||||
#' dataTableOutput('table')
|
||||
#' )
|
||||
#' )
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$table <- renderDataTable(iris,
|
||||
#' options = list(
|
||||
#' pageLength = 5,
|
||||
#' initComplete = I("function(settings, json) {alert('Done.');}")
|
||||
#' )
|
||||
#' )
|
||||
#' }
|
||||
#' )
|
||||
#' )
|
||||
#' }
|
||||
renderDataTable <- function(expr, options = NULL, searchDelay = 500,
|
||||
callback = 'function(oTable) {}',
|
||||
env = parent.frame(), quoted = FALSE) {
|
||||
callback = 'function(oTable) {}', escape = TRUE,
|
||||
env = parent.frame(), quoted = FALSE,
|
||||
outputArgs=list()) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
markRenderFunction(dataTableOutput, function(shinysession, name, ...) {
|
||||
res <- checkAsIs(if (is.function(options)) options() else options)
|
||||
renderFunc <- function(shinysession, name, ...) {
|
||||
if (is.function(options)) options <- options()
|
||||
options <- checkDT9(options)
|
||||
res <- checkAsIs(options)
|
||||
data <- func()
|
||||
if (length(dim(data)) != 2) return() # expects a rectangular data object
|
||||
if (is.data.frame(data)) data <- as.data.frame(data)
|
||||
action <- shinysession$registerDataObj(name, data, dataTablesJSON)
|
||||
colnames <- colnames(data)
|
||||
# if escape is column names, turn names to numeric indices
|
||||
if (is.character(escape)) {
|
||||
escape <- stats::setNames(seq_len(ncol(data)), colnames)[escape]
|
||||
if (any(is.na(escape)))
|
||||
stop("Some column names in the 'escape' argument not found in data")
|
||||
}
|
||||
colnames[escape] <- htmlEscape(colnames[escape])
|
||||
if (!is.logical(escape)) {
|
||||
if (!is.numeric(escape))
|
||||
stop("'escape' must be TRUE, FALSE, or a numeric vector, or column names")
|
||||
escape <- paste(escape, collapse = ',')
|
||||
}
|
||||
list(
|
||||
colnames = colnames(data), action = action, options = res$options,
|
||||
colnames = colnames, action = action, options = res$options,
|
||||
evalOptions = if (length(res$eval)) I(res$eval), searchDelay = searchDelay,
|
||||
callback = paste(callback, collapse = '\n')
|
||||
callback = paste(callback, collapse = '\n'), escape = escape
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
markRenderFunction(dataTableOutput, renderFunc, outputArgs = outputArgs)
|
||||
}
|
||||
|
||||
# a data frame containing the DataTables 1.9 and 1.10 names
|
||||
DT10Names <- function() {
|
||||
rbind(
|
||||
utils::read.table(
|
||||
system.file('www/shared/datatables/upgrade1.10.txt', package = 'shiny'),
|
||||
stringsAsFactors = FALSE
|
||||
),
|
||||
c('aoColumns', 'Removed') # looks like an omission on the upgrade guide
|
||||
)
|
||||
}
|
||||
|
||||
# check DataTables 1.9.x options, and give instructions for upgrading to 1.10.x
|
||||
checkDT9 <- function(options) {
|
||||
nms <- names(options)
|
||||
if (length(nms) == 0L) return(options)
|
||||
DT10 <- DT10Names()
|
||||
# e.g. the top level option name for oLanguage.sSearch should be oLanguage
|
||||
i <- nms %in% gsub('[.].*', '', DT10[, 1])
|
||||
if (!any(i)) return(options) # did not see old option names, ready to go!
|
||||
msg <- paste(
|
||||
'shiny (>= 0.10.2) has upgraded DataTables from 1.9.4 to 1.10.2, ',
|
||||
'and DataTables 1.10.x uses different parameter names with 1.9.x. ',
|
||||
'Please follow the upgrade guide https://datatables.net/upgrade/1.10-convert',
|
||||
' to change your DataTables parameter names:\n\n',
|
||||
paste(utils::formatUL(nms[i]), collapse = '\n'), '\n', sep = ''
|
||||
)
|
||||
j <- gsub('[.].*', '', DT10[, 1]) %in% nms
|
||||
# I cannot help you upgrade automatically in these cases, so I have to stop
|
||||
if (any(grepl('[.]', DT10[j, 1])) || any(grepl('[.]', DT10[j, 2]))) stop(msg)
|
||||
warning(msg)
|
||||
nms10 <- DT10[match(nms[i], DT10[, 1]), 2]
|
||||
if (any(nms10 == 'Removed')) stop(
|
||||
"These parameters have been removed in DataTables 1.10.x:\n\n",
|
||||
paste(utils::formatUL(nms[i][nms10 == 'Removed']), collapse = '\n'),
|
||||
"\n\n", msg
|
||||
)
|
||||
names(options)[i] <- nms10
|
||||
options
|
||||
}
|
||||
|
||||
# Deprecated functions ------------------------------------------------------
|
||||
|
||||
|
||||
121
R/showcase.R
121
R/showcase.R
@@ -31,12 +31,10 @@ licenseLink <- function(licenseName) {
|
||||
showcaseHead <- function() {
|
||||
|
||||
deps <- list(
|
||||
htmlDependency("jqueryui", "1.10.4", c(href="shared/jqueryui/1.10.4"),
|
||||
htmlDependency("jqueryui", "1.11.4", c(href="shared/jqueryui"),
|
||||
script = "jquery-ui.min.js"),
|
||||
htmlDependency("showdown", "0.3.1", c(href="shared/showdown/compressed"),
|
||||
script = "showdown.js"),
|
||||
htmlDependency("font-awesome", "4.0.3", c(href="shared/font-awesome"),
|
||||
stylesheet = "css/font-awesome.min.css"),
|
||||
htmlDependency("highlight.js", "6.2", c(href="shared/highlight"),
|
||||
script = "highlight.pack.js")
|
||||
)
|
||||
@@ -62,7 +60,7 @@ showcaseHead <- function() {
|
||||
appMetadata <- function(desc) {
|
||||
cols <- colnames(desc)
|
||||
if ("Title" %in% cols)
|
||||
with(tags, h4(class="muted shiny-showcase-apptitle", desc[1,"Title"],
|
||||
with(tags, h4(class="text-muted shiny-showcase-apptitle", desc[1,"Title"],
|
||||
if ("Author" %in% cols) small(
|
||||
br(), "by",
|
||||
if ("AuthorUrl" %in% cols)
|
||||
@@ -79,36 +77,82 @@ appMetadata <- function(desc) {
|
||||
else ""
|
||||
}
|
||||
|
||||
navTabsHelper <- function(files, prefix = "") {
|
||||
lapply(files, function(file) {
|
||||
with(tags,
|
||||
li(class=if (tolower(file) %in% c("app.r", "server.r")) "active" else "",
|
||||
a(href=paste("#", gsub(".", "_", file, fixed=TRUE), "_code", sep=""),
|
||||
"data-toggle"="tab", paste0(prefix, file)))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
navTabsDropdown <- function(files) {
|
||||
if (length(files) > 0) {
|
||||
with(tags,
|
||||
li(role="presentation", class="dropdown",
|
||||
a(class="dropdown-toggle", `data-toggle`="dropdown", href="#",
|
||||
role="button", `aria-haspopup`="true", `aria-expanded`="false",
|
||||
"www", span(class="caret")
|
||||
),
|
||||
ul(class="dropdown-menu", navTabsHelper(files))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
tabContentHelper <- function(files, path, language) {
|
||||
lapply(files, function(file) {
|
||||
with(tags,
|
||||
div(class=paste("tab-pane",
|
||||
if (tolower(file) %in% c("app.r", "server.r")) " active"
|
||||
else "",
|
||||
sep=""),
|
||||
id=paste(gsub(".", "_", file, fixed=TRUE),
|
||||
"_code", sep=""),
|
||||
pre(class="shiny-code",
|
||||
# we need to prevent the indentation of <code> ... </code>
|
||||
HTML(format(tags$code(
|
||||
class=paste0("language-", language),
|
||||
paste(readUTF8(file.path.ci(path, file)), collapse="\n")
|
||||
), indent = FALSE))))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
# Returns tags containing the application's code in Bootstrap-style tabs in
|
||||
# showcase mode.
|
||||
showcaseCodeTabs <- function(codeLicense) {
|
||||
rFiles <- list.files(pattern = "\\.[rR]$")
|
||||
wwwFiles <- list()
|
||||
if (isTRUE(.globals$IncludeWWW)) {
|
||||
path <- file.path(getwd(), "www")
|
||||
wwwFiles$jsFiles <- list.files(path, pattern = "\\.js$")
|
||||
wwwFiles$cssFiles <- list.files(path, pattern = "\\.css$")
|
||||
wwwFiles$htmlFiles <- list.files(path, pattern = "\\.html$")
|
||||
}
|
||||
with(tags, div(id="showcase-code-tabs",
|
||||
a(id="showcase-code-position-toggle",
|
||||
class="btn btn-default btn-small",
|
||||
class="btn btn-default btn-sm",
|
||||
onclick="toggleCodePosition()",
|
||||
i(class="fa fa-level-up", "show with app")),
|
||||
icon("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))
|
||||
})),
|
||||
navTabsHelper(rFiles),
|
||||
navTabsDropdown(unlist(wwwFiles))
|
||||
),
|
||||
div(class="tab-content", id="showcase-code-content",
|
||||
lapply(rFiles, function(rFile) {
|
||||
div(class=paste("tab-pane",
|
||||
if (tolower(rFile) == "server.r") " active" else "",
|
||||
sep=""),
|
||||
id=paste(gsub(".", "_", rFile, fixed=TRUE),
|
||||
"_code", sep=""),
|
||||
pre(class="shiny-code",
|
||||
# we need to prevent the indentation of <code> ... </code>
|
||||
HTML(format(tags$code(
|
||||
class="language-r",
|
||||
paste(readUTF8(file.path.ci(getwd(), rFile)), collapse="\n")
|
||||
), indent = FALSE))))
|
||||
})),
|
||||
tabContentHelper(rFiles, path = getwd(), language = "r"),
|
||||
tabContentHelper(wwwFiles$jsFiles,
|
||||
path = paste0(getwd(), "/www"),
|
||||
language = "javascript"),
|
||||
tabContentHelper(wwwFiles$cssFiles,
|
||||
path = paste0(getwd(), "/www"),
|
||||
language = "css"),
|
||||
tabContentHelper(wwwFiles$htmlFiles,
|
||||
path = paste0(getwd(), "/www"),
|
||||
language = "xml")
|
||||
),
|
||||
codeLicense))
|
||||
}
|
||||
|
||||
@@ -127,17 +171,17 @@ showcaseAppInfo <- function() {
|
||||
with(tags,
|
||||
div(class="container-fluid shiny-code-container well",
|
||||
id="showcase-well",
|
||||
div(class="row-fluid",
|
||||
div(class="row",
|
||||
if (hasDesc || hasReadme) {
|
||||
div(id="showcase-app-metadata", class="span4",
|
||||
div(id="showcase-app-metadata", class="col-sm-4",
|
||||
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",
|
||||
class=if (hasReadme || hasDesc) "col-sm-8" else "col-sm-10 col-sm-offset-1",
|
||||
showcaseCodeTabs(
|
||||
if (hasDesc && "License" %in% colnames(desc)) {
|
||||
small(class="showcase-code-license muted",
|
||||
small(class="showcase-code-license text-muted",
|
||||
"Code license: ",
|
||||
licenseLink(desc[1,"License"]))
|
||||
} else "")))))
|
||||
@@ -150,7 +194,7 @@ showcaseBody <- function(htmlBody) {
|
||||
table(id="showcase-app-code",
|
||||
tr(td(id="showcase-app-container",
|
||||
class="showcase-app-container-expanded",
|
||||
HTML(htmlBody),
|
||||
htmlBody,
|
||||
td(id="showcase-sxs-code",
|
||||
class="showcase-sxs-code-collapsed")))),
|
||||
showcaseAppInfo()))
|
||||
@@ -161,3 +205,20 @@ setShowcaseDefault <- function(showcaseDefault) {
|
||||
.globals$showcaseDefault <- showcaseDefault
|
||||
.globals$showcaseOverride <- as.logical(showcaseDefault)
|
||||
}
|
||||
|
||||
|
||||
# Given a UI tag/tagList, wrap it in appropriate tags for showcase mode.
|
||||
showcaseUI <- function(ui) {
|
||||
# If top-level tag is a body, replace its children with children wrapped in
|
||||
# showcase stuff.
|
||||
if (inherits(ui, "shiny.tag") && ui$name == "body") {
|
||||
ui$children <- showcaseUI(ui$children)
|
||||
return(ui)
|
||||
}
|
||||
|
||||
tagList(
|
||||
tags$head(showcaseHead()),
|
||||
showcaseBody(ui)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
142
R/slider.R
142
R/slider.R
@@ -1,142 +0,0 @@
|
||||
hasDecimals <- function(value) {
|
||||
truncatedValue <- round(value)
|
||||
return (!identical(value, truncatedValue))
|
||||
}
|
||||
|
||||
#' @rdname sliderInput
|
||||
#'
|
||||
#' @param interval The interval, in milliseconds, between each animation step.
|
||||
#' @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
|
||||
#' \code{\link{HTML}}).
|
||||
#' @param pauseButton Similar to \code{playButton}, but for the pause button.
|
||||
#'
|
||||
#' @export
|
||||
animationOptions <- function(interval=1000,
|
||||
loop=FALSE,
|
||||
playButton=NULL,
|
||||
pauseButton=NULL) {
|
||||
list(interval=interval,
|
||||
loop=loop,
|
||||
playButton=playButton,
|
||||
pauseButton=pauseButton)
|
||||
}
|
||||
|
||||
# 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)
|
||||
slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
round=FALSE, format='#,##0.#####', locale='us',
|
||||
ticks=TRUE, animate=FALSE, width=NULL) {
|
||||
# validate inputId
|
||||
inputId <- as.character(inputId)
|
||||
if (!is.character(inputId))
|
||||
stop("inputId not specified")
|
||||
|
||||
# validate numeric inputs
|
||||
if (!is.numeric(value) || !is.numeric(min) || !is.numeric(max))
|
||||
stop("min, max, 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,
|
||||
"is greater than the specified maximum"))
|
||||
else if (min > max)
|
||||
stop(paste("slider maximum is greater than minimum"))
|
||||
else if (!is.null(step)) {
|
||||
if (!is.numeric(step))
|
||||
stop("step is not a numeric value")
|
||||
if (step > (max - min))
|
||||
stop("step is greater than range")
|
||||
}
|
||||
|
||||
# step
|
||||
range <- max - min
|
||||
if (is.null(step)) {
|
||||
# short range or decimals means continuous decimal
|
||||
if (range < 2 || hasDecimals(min) || hasDecimals(max))
|
||||
step <- range / 250 # ~ one step per pixel
|
||||
else
|
||||
step = 1
|
||||
}
|
||||
|
||||
# Default state is to not have ticks
|
||||
if (identical(ticks, TRUE)) {
|
||||
# Automatic ticks
|
||||
tickCount <- (range / step) + 1
|
||||
if (tickCount <= 26)
|
||||
ticks <- paste(rep('|', floor(tickCount)), collapse=';')
|
||||
else {
|
||||
ticks <- NULL
|
||||
# # This is a smarter auto-tick algorithm, but to be truly useful
|
||||
# # we need jslider to be able to space ticks irregularly
|
||||
# tickSize <- 10^(floor(log10(range/0.39)))
|
||||
# if ((range / tickSize) == floor(range / tickSize)) {
|
||||
# ticks <- paste(rep('|', (range / tickSize) + 1), collapse=';')
|
||||
# }
|
||||
# else {
|
||||
# ticks <- NULL
|
||||
# }
|
||||
}
|
||||
}
|
||||
else if (is.numeric(ticks) && length(ticks) == 1) {
|
||||
# Use n ticks
|
||||
ticks <- paste(rep('|', ticks), collapse=';')
|
||||
}
|
||||
else if (length(ticks) > 1 && (is.numeric(ticks) || is.character(ticks))) {
|
||||
# Explicit ticks
|
||||
ticks <- paste(ticks, collapse=';')
|
||||
}
|
||||
else {
|
||||
ticks <- NULL
|
||||
}
|
||||
|
||||
# build slider
|
||||
dep <- htmlDependency("jslider", "1", c(href="shared/slider"),
|
||||
script = "js/jquery.slider.min.js",
|
||||
stylesheet = "css/jquery.slider.min.css"
|
||||
)
|
||||
sliderFragment <- list(
|
||||
attachDependencies(
|
||||
tags$input(
|
||||
id=inputId, type="slider",
|
||||
name=inputId, value=paste(value, collapse=';'), class="jslider",
|
||||
'data-from'=min, 'data-to'=max, 'data-step'=step,
|
||||
'data-skin'='plastic', 'data-round'=round, 'data-locale'=locale,
|
||||
'data-format'=format, 'data-scale'=ticks,
|
||||
'data-smooth'=FALSE,
|
||||
'data-width'=validateCssUnit(width)
|
||||
),
|
||||
dep
|
||||
)
|
||||
)
|
||||
|
||||
if (identical(animate, TRUE))
|
||||
animate <- animationOptions()
|
||||
|
||||
if (!is.null(animate) && !identical(animate, 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='#',
|
||||
class='slider-animate-button',
|
||||
'data-target-id'=inputId,
|
||||
'data-interval'=animate$interval,
|
||||
'data-loop'=animate$loop,
|
||||
tags$span(class='play', animate$playButton),
|
||||
tags$span(class='pause', animate$pauseButton)))
|
||||
}
|
||||
|
||||
return(tagList(sliderFragment))
|
||||
}
|
||||
70
R/stack.R
Normal file
70
R/stack.R
Normal file
@@ -0,0 +1,70 @@
|
||||
# A Stack object backed by a list. The backing list will grow or shrink as
|
||||
# the stack changes in size.
|
||||
Stack <- R6Class(
|
||||
'Stack',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
|
||||
initialize = function(init = 20L) {
|
||||
# init is the initial size of the list. It is also used as the minimum
|
||||
# size of the list as it shrinks.
|
||||
private$stack <- vector("list", init)
|
||||
private$init <- init
|
||||
},
|
||||
|
||||
push = function(..., .list = NULL) {
|
||||
args <- c(list(...), .list)
|
||||
new_size <- count + length(args)
|
||||
|
||||
# Grow if needed; double in size
|
||||
while (new_size > length(stack)) {
|
||||
stack[length(stack) * 2] <<- list(NULL)
|
||||
}
|
||||
stack[count + seq_along(args)] <<- args
|
||||
count <<- new_size
|
||||
|
||||
invisible(self)
|
||||
},
|
||||
|
||||
pop = function() {
|
||||
if (count == 0L)
|
||||
return(NULL)
|
||||
|
||||
value <- stack[[count]]
|
||||
stack[count] <<- list(NULL)
|
||||
count <<- count - 1L
|
||||
|
||||
# Shrink list if < 1/4 of the list is used, down to a minimum size of `init`
|
||||
len <- length(stack)
|
||||
if (len > init && count < len/4) {
|
||||
new_len <- max(init, ceiling(len/2))
|
||||
stack <<- stack[seq_len(new_len)]
|
||||
}
|
||||
|
||||
value
|
||||
},
|
||||
|
||||
peek = function() {
|
||||
if (count == 0L)
|
||||
return(NULL)
|
||||
stack[[count]]
|
||||
},
|
||||
|
||||
size = function() {
|
||||
count
|
||||
},
|
||||
|
||||
# Return the entire stack as a list, where the first item in the list is the
|
||||
# oldest item in the stack, and the last item is the most recently added.
|
||||
as_list = function() {
|
||||
stack[seq_len(count)]
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
stack = NULL, # A list that holds the items
|
||||
count = 0L, # Current number of items in the stack
|
||||
init = 20L # Initial and minimum size of the stack
|
||||
)
|
||||
)
|
||||
2
R/tar.R
2
R/tar.R
@@ -46,7 +46,7 @@ untar2 <- function(tarfile, files = NULL, list = FALSE, exdir = ".")
|
||||
mydir.create <- function(path, ...) {
|
||||
## for Windows' sake
|
||||
path <- sub("[\\/]$", "", path)
|
||||
if(file_test("-d", path)) return()
|
||||
if(utils::file_test("-d", path)) return()
|
||||
if(!dir.create(path, showWarnings = TRUE, recursive = TRUE, ...))
|
||||
stop(gettextf("failed to create directory %s", sQuote(path)),
|
||||
domain = NA)
|
||||
|
||||
28
R/timer.R
28
R/timer.R
@@ -4,16 +4,17 @@ now <- function() {
|
||||
as.numeric(Sys.time()) * 1000
|
||||
}
|
||||
|
||||
TimerCallbacks <- setRefClass(
|
||||
TimerCallbacks <- R6Class(
|
||||
'TimerCallbacks',
|
||||
fields = list(
|
||||
.nextId = 'integer',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.nextId = 0L,
|
||||
.funcs = 'Map',
|
||||
.times = 'data.frame'
|
||||
),
|
||||
methods = list(
|
||||
.times = data.frame(),
|
||||
|
||||
initialize = function() {
|
||||
.nextId <<- 0L
|
||||
.funcs <<- Map$new()
|
||||
},
|
||||
clear = function() {
|
||||
.nextId <<- 0L
|
||||
@@ -70,3 +71,16 @@ TimerCallbacks <- setRefClass(
|
||||
)
|
||||
|
||||
timerCallbacks <- TimerCallbacks$new()
|
||||
|
||||
scheduleTask <- function(millis, callback) {
|
||||
cancelled <- FALSE
|
||||
timerCallbacks$schedule(millis, function() {
|
||||
if (!cancelled)
|
||||
callback()
|
||||
})
|
||||
|
||||
function() {
|
||||
cancelled <<- TRUE
|
||||
callback <<- NULL # to allow for callback to be gc'ed
|
||||
}
|
||||
}
|
||||
|
||||
413
R/update-input.R
413
R/update-input.R
@@ -6,9 +6,16 @@
|
||||
#' @seealso \code{\link{textInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("controller", "Controller", 0, 20, 10),
|
||||
#' textInput("inText", "Input text"),
|
||||
#' textInput("inText2", "Input text 2")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
@@ -22,7 +29,9 @@
|
||||
#' label = paste("New label", x),
|
||||
#' value = paste("New text", x))
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateTextInput <- function(session, inputId, label = NULL, value = NULL) {
|
||||
@@ -39,52 +48,81 @@ updateTextInput <- function(session, inputId, label = NULL, value = NULL) {
|
||||
#' @seealso \code{\link{checkboxInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("controller", "Controller", 0, 1, 0, step = 1),
|
||||
#' checkboxInput("inCheckbox", "Input checkbox")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' observe({
|
||||
#' # TRUE if input$controller is even, FALSE otherwise.
|
||||
#' x_even <- input$controller %% 2 == 0
|
||||
#' # TRUE if input$controller is odd, FALSE if even.
|
||||
#' x_even <- input$controller %% 2 == 1
|
||||
#'
|
||||
#' updateCheckboxInput(session, "inCheckbox", value = x_even)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateCheckboxInput <- updateTextInput
|
||||
|
||||
|
||||
#' Change the value of a slider input on the client
|
||||
#' Change the label or icon of an action button on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#' @param icon The icon to set for the input object. To remove the
|
||||
#' current icon, use \code{icon=character(0)}.
|
||||
#'
|
||||
#' @seealso \code{\link{sliderInput}}
|
||||
#' @seealso \code{\link{actionButton}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' actionButton("update", "Update other buttons"),
|
||||
#' br(),
|
||||
#' actionButton("goButton", "Go"),
|
||||
#' br(),
|
||||
#' actionButton("goButton2", "Go 2", icon = icon("area-chart")),
|
||||
#' br(),
|
||||
#' actionButton("goButton3", "Go 3")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#' req(input$update)
|
||||
#'
|
||||
#' # Similar to number and text. only label and value can be set for slider
|
||||
#' updateSliderInput(session, "inSlider",
|
||||
#' label = paste("Slider label", x),
|
||||
#' value = x)
|
||||
#' # Updates goButton's label and icon
|
||||
#' updateActionButton(session, "goButton",
|
||||
#' label = "New label",
|
||||
#' icon = icon("calendar"))
|
||||
#'
|
||||
#' # For sliders that pick out a range, pass in a vector of 2 values.
|
||||
#' updateSliderInput(session, "inSlider2", value = c(x-1, x+1))
|
||||
#' # Leaves goButton2's label unchaged and
|
||||
#' # removes its icon
|
||||
#' updateActionButton(session, "goButton2",
|
||||
#' icon = character(0))
|
||||
#'
|
||||
#' # An NA means to not change that value (the low or high one)
|
||||
#' updateSliderInput(session, "inSlider3", value = c(NA, x+2))
|
||||
#' # Leaves goButton3's icon, if it exists,
|
||||
#' # unchaged and changes its label
|
||||
#' updateActionButton(session, "goButton3",
|
||||
#' label = "New label 3")
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateSliderInput <- updateTextInput
|
||||
updateActionButton <- function(session, inputId, label = NULL, icon = NULL) {
|
||||
if (!is.null(icon)) icon <- as.character(validateIcon(icon))
|
||||
message <- dropNulls(list(label=label, icon=icon))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a date input on the client
|
||||
#'
|
||||
@@ -99,9 +137,15 @@ updateSliderInput <- updateTextInput
|
||||
#' @seealso \code{\link{dateInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("controller", "Controller", 1, 30, 10),
|
||||
#' dateInput("inDate", "Input date")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
@@ -114,7 +158,9 @@ updateSliderInput <- updateTextInput
|
||||
#' max = paste("2013-04-", x+1, sep="")
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
@@ -146,9 +192,15 @@ updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
#' @seealso \code{\link{dateRangeInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("controller", "Controller", 1, 30, 10),
|
||||
#' dateRangeInput("inDateRange", "Input date range")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
@@ -156,10 +208,13 @@ updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
#'
|
||||
#' updateDateRangeInput(session, "inDateRange",
|
||||
#' label = paste("Date range label", x),
|
||||
#' start = paste("2013-01-", x, sep=""))
|
||||
#' end = paste("2013-12-", x, sep=""))
|
||||
#' start = paste("2013-01-", x, sep=""),
|
||||
#' end = paste("2013-12-", x, sep="")
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateDateRangeInput <- function(session, inputId, label = NULL,
|
||||
@@ -194,22 +249,31 @@ updateDateRangeInput <- function(session, inputId, label = NULL,
|
||||
#' \code{\link{navbarPage}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # TRUE if input$controller is even, FALSE otherwise.
|
||||
#' x_even <- input$controller %% 2 == 0
|
||||
#' ui <- fluidPage(sidebarLayout(
|
||||
#' sidebarPanel(
|
||||
#' sliderInput("controller", "Controller", 1, 3, 1)
|
||||
#' ),
|
||||
#' mainPanel(
|
||||
#' tabsetPanel(id = "inTabset",
|
||||
#' tabPanel(title = "Panel 1", value = "panel1", "Panel 1 content"),
|
||||
#' tabPanel(title = "Panel 2", value = "panel2", "Panel 2 content"),
|
||||
#' tabPanel(title = "Panel 3", value = "panel3", "Panel 3 content")
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' # Change the selected tab.
|
||||
#' # Note that the tabset container must have been created with an 'id' argument
|
||||
#' if (x_even) {
|
||||
#' updateTabsetPanel(session, "inTabset", selected = "panel2")
|
||||
#' } else {
|
||||
#' updateTabsetPanel(session, "inTabset", selected = "panel1")
|
||||
#' }
|
||||
#' server <- function(input, output, session) {
|
||||
#' observeEvent(input$controller, {
|
||||
#' updateTabsetPanel(session, "inTabset",
|
||||
#' selected = paste0("panel", input$controller)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateTabsetPanel <- function(session, inputId, selected = NULL) {
|
||||
@@ -217,6 +281,13 @@ updateTabsetPanel <- function(session, inputId, selected = NULL) {
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' @rdname updateTabsetPanel
|
||||
#' @export
|
||||
updateNavbarPage <- updateTabsetPanel
|
||||
|
||||
#' @rdname updateTabsetPanel
|
||||
#' @export
|
||||
updateNavlistPanel <- updateTabsetPanel
|
||||
|
||||
#' Change the value of a number input on the client
|
||||
#'
|
||||
@@ -229,10 +300,18 @@ updateTabsetPanel <- function(session, inputId, selected = NULL) {
|
||||
#' @seealso \code{\link{numericInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' observe({
|
||||
#' ui <- fluidPage(
|
||||
#' sliderInput("controller", "Controller", 0, 20, 10),
|
||||
#' numericInput("inNumber", "Input number", 0),
|
||||
#' numericInput("inNumber2", "Input number 2", 0)
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#'
|
||||
#' observeEvent(input$controller, {
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
@@ -243,7 +322,9 @@ updateTabsetPanel <- function(session, inputId, selected = NULL) {
|
||||
#' label = paste("Number label ", x),
|
||||
#' value = x, min = x-10, max = x+10, step = 5)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
@@ -256,18 +337,91 @@ updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' Change the value of a slider input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#' @param min Minimum value.
|
||||
#' @param max Maximum value.
|
||||
#' @param step Step size.
|
||||
#'
|
||||
#' @seealso \code{\link{sliderInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' sidebarLayout(
|
||||
#' sidebarPanel(
|
||||
#' p("The first slider controls the second"),
|
||||
#' sliderInput("control", "Controller:", min=0, max=20, value=10,
|
||||
#' step=1),
|
||||
#' sliderInput("receive", "Receiver:", min=0, max=20, value=10,
|
||||
#' step=1)
|
||||
#' ),
|
||||
#' mainPanel()
|
||||
#' )
|
||||
#' ),
|
||||
#' server = function(input, output, session) {
|
||||
#' observe({
|
||||
#' val <- input$control
|
||||
#' # Control the value, min, max, and step.
|
||||
#' # Step size is 2 when input value is even; 1 when value is odd.
|
||||
#' updateSliderInput(session, "receive", value = val,
|
||||
#' min = floor(val/2), max = val+4, step = (val+1)%%2 + 1)
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#' @export
|
||||
updateSliderInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL, step = NULL)
|
||||
{
|
||||
# Make sure that value, min, max all have the same type, because we need
|
||||
# special handling for dates and datetimes.
|
||||
vals <- dropNulls(list(value, min, max))
|
||||
|
||||
type <- unique(lapply(vals, function(x) {
|
||||
if (inherits(x, "Date")) "date"
|
||||
else if (inherits(x, "POSIXt")) "datetime"
|
||||
else "number"
|
||||
}))
|
||||
if (length(type) > 1) {
|
||||
stop("Type mismatch for value, min, and max")
|
||||
}
|
||||
|
||||
if ((length(type) == 1) && (type == "date" || type == "datetime")) {
|
||||
to_ms <- function(x) 1000 * as.numeric(as.POSIXct(x))
|
||||
if (!is.null(min)) min <- to_ms(min)
|
||||
if (!is.null(max)) max <- to_ms(max)
|
||||
if (!is.null(value)) value <- to_ms(value)
|
||||
}
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = label,
|
||||
value = formatNoSci(value),
|
||||
min = formatNoSci(min),
|
||||
max = formatNoSci(max),
|
||||
step = formatNoSci(step)
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, inline = FALSE,
|
||||
type = 'checkbox') {
|
||||
|
||||
choices <- choicesWithNames(choices)
|
||||
if (!is.null(choices))
|
||||
choices <- choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
|
||||
options <- if (length(choices))
|
||||
options <- if (!is.null(choices)) {
|
||||
format(tagList(
|
||||
generateOptions(inputId, choices, selected, inline, type = type)
|
||||
))
|
||||
}
|
||||
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
|
||||
@@ -282,31 +436,35 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
|
||||
#' @seealso \code{\link{checkboxGroupInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' p("The first checkbox group controls the second"),
|
||||
#' checkboxGroupInput("inCheckboxGroup", "Input checkbox",
|
||||
#' c("Item A", "Item B", "Item C")),
|
||||
#' checkboxGroupInput("inCheckboxGroup2", "Input checkbox 2",
|
||||
#' c("Item A", "Item B", "Item C"))
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#' x <- input$inCheckboxGroup
|
||||
#'
|
||||
#' # Create a list of new options, where the name of the items is something
|
||||
#' # like 'option label x 1', and the values are 'option-x-1'.
|
||||
#' cb_options <- list()
|
||||
#' cb_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
|
||||
#' cb_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
|
||||
#'
|
||||
#' # Change values for input$inCheckboxGroup
|
||||
#' updateCheckboxGroupInput(session, "inCheckboxGroup", choices = cb_options)
|
||||
#' # Can use character(0) to remove all choices
|
||||
#' if (is.null(x))
|
||||
#' x <- character(0)
|
||||
#'
|
||||
#' # Can also set the label and select items
|
||||
#' updateCheckboxGroupInput(session, "inCheckboxGroup2",
|
||||
#' label = paste("checkboxgroup label", x),
|
||||
#' choices = cb_options,
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' label = paste("Checkboxgroup label", length(x)),
|
||||
#' choices = x,
|
||||
#' selected = x
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
@@ -324,29 +482,31 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
#' @seealso \code{\link{radioButtons}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' p("The first radio button group controls the second"),
|
||||
#' radioButtons("inRadioButtons", "Input radio buttons",
|
||||
#' c("Item A", "Item B", "Item C")),
|
||||
#' radioButtons("inRadioButtons2", "Input radio buttons 2",
|
||||
#' c("Item A", "Item B", "Item C"))
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#' x <- input$inRadioButtons
|
||||
#'
|
||||
#' r_options <- list()
|
||||
#' r_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
|
||||
#' r_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
|
||||
#'
|
||||
#' # Change values for input$inRadio
|
||||
#' updateRadioButtons(session, "inRadio", choices = r_options)
|
||||
#'
|
||||
#' # Can also set the label and select an item
|
||||
#' updateRadioButtons(session, "inRadio2",
|
||||
#' label = paste("Radio label", x),
|
||||
#' choices = r_options,
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' # Can also set the label and select items
|
||||
#' updateRadioButtons(session, "inRadioButtons2",
|
||||
#' label = paste("radioButtons label", x),
|
||||
#' choices = x,
|
||||
#' selected = x
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
|
||||
@@ -365,40 +525,43 @@ updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
|
||||
#' @seealso \code{\link{selectInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' p("The checkbox group controls the select input"),
|
||||
#' checkboxGroupInput("inCheckboxGroup", "Input checkbox",
|
||||
#' c("Item A", "Item B", "Item C")),
|
||||
#' selectInput("inSelect", "Select input",
|
||||
#' c("Item A", "Item B", "Item C"))
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#' x <- input$inCheckboxGroup
|
||||
#'
|
||||
#' # Create a list of new options, where the name of the items is something
|
||||
#' # like 'option label x 1', and the values are 'option-x-1'.
|
||||
#' s_options <- list()
|
||||
#' s_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
|
||||
#' s_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
|
||||
#' # Can use character(0) to remove all choices
|
||||
#' if (is.null(x))
|
||||
#' x <- character(0)
|
||||
#'
|
||||
#' # Change values for input$inSelect
|
||||
#' updateSelectInput(session, "inSelect", choices = s_options)
|
||||
#'
|
||||
#' # Can also set the label and select an item (or more than one if it's a
|
||||
#' # multi-select)
|
||||
#' updateSelectInput(session, "inSelect2",
|
||||
#' label = paste("Select label", x),
|
||||
#' choices = s_options,
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' # Can also set the label and select items
|
||||
#' updateSelectInput(session, "inSelect",
|
||||
#' label = paste("Select input label", length(x)),
|
||||
#' choices = x,
|
||||
#' selected = tail(x, 1)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateSelectInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL) {
|
||||
choices <- choicesWithNames(choices)
|
||||
choices <- if (!is.null(choices)) choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
options <- if (length(choices)) selectOptions(choices, selected)
|
||||
options <- if (!is.null(choices)) selectOptions(choices, selected)
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
@@ -426,17 +589,11 @@ updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
if (!server) {
|
||||
return(updateSelectInput(session, inputId, label, choices, selected))
|
||||
}
|
||||
# in the server mode, the choices are not available before we type, so we
|
||||
# cannot really pre-select any options, but here we insert the `selected`
|
||||
# options into selectize forcibly
|
||||
value <- unname(selected)
|
||||
selected <- choicesWithNames(selected)
|
||||
attr(choices, 'selected_value') <- value
|
||||
message <- dropNulls(list(
|
||||
label = label,
|
||||
value = value,
|
||||
selected = if (length(selected)) {
|
||||
columnToRowData(list(label = names(selected), value = selected))
|
||||
},
|
||||
url = session$registerDataObj(inputId, choices, selectizeJSON)
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
@@ -445,18 +602,20 @@ updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selectizeJSON <- function(data, req) {
|
||||
query <- parseQueryString(req$QUERY_STRING)
|
||||
# extract the query variables, conjunction (and/or), search string, maximum options
|
||||
var <- fromJSON(query$field)
|
||||
var <- c(jsonlite::fromJSON(query$field))
|
||||
cjn <- if (query$conju == 'and') all else any
|
||||
# all keywords in lower-case, for case-insensitive matching
|
||||
key <- unique(strsplit(tolower(query$query), '\\s+')[[1]])
|
||||
if (identical(key, '')) key <- character(0)
|
||||
mop <- query$maxop
|
||||
mop <- as.numeric(query$maxop)
|
||||
vfd <- query$value # the value field name
|
||||
sel <- attr(data, 'selected_value', exact = TRUE)
|
||||
|
||||
# convert a single vector to a data frame so it returns {label: , value: }
|
||||
# later in JSON; other objects return arbitrary JSON {x: , y: , foo: , ...}
|
||||
data <- if (is.atomic(data)) {
|
||||
data <- choicesWithNames(data)
|
||||
data.frame(label = names(data), value = data, stringsAsFactors = FALSE)
|
||||
data.frame(label = names(choicesWithNames(data)), value = data,
|
||||
stringsAsFactors = FALSE)
|
||||
} else as.data.frame(data, stringsAsFactors = FALSE)
|
||||
|
||||
# start searching for keywords in all specified columns
|
||||
@@ -473,8 +632,14 @@ selectizeJSON <- function(data, req) {
|
||||
idx <- idx | apply(matches, 1, cjn)
|
||||
}
|
||||
# only return the first n rows (n = maximum options in configuration)
|
||||
idx <- head(which(idx), mop)
|
||||
idx <- utils::head(if (length(key)) which(idx) else seq_along(idx), mop)
|
||||
# make sure the selected value is in the data
|
||||
if (length(sel)) {
|
||||
i <- stats::na.omit(match(sel, data[, vfd]))
|
||||
if (length(i)) idx <- sort(utils::head(unique(c(i, idx)), mop))
|
||||
}
|
||||
data <- data[idx, ]
|
||||
|
||||
httpResponse(200, 'application/json', toJSON(columnToRowData(data)))
|
||||
res <- toJSON(columnToRowData(data))
|
||||
httpResponse(200, 'application/json', enc2utf8(res))
|
||||
}
|
||||
|
||||
24
README.md
24
README.md
@@ -4,14 +4,14 @@
|
||||
|
||||
Shiny is a new package from RStudio that makes it incredibly easy to build interactive web applications with R.
|
||||
|
||||
For an introduction and examples, visit the [Shiny homepage](http://www.rstudio.com/shiny/).
|
||||
For an introduction and examples, visit the [Shiny Dev Center](http://shiny.rstudio.com/).
|
||||
|
||||
## Features
|
||||
|
||||
* Build useful web applications with only a few lines of code—no JavaScript required.
|
||||
* Shiny applications are automatically "live" in the same way that spreadsheets are live. Outputs change instantly as users modify inputs, without requiring a reload of the browser.
|
||||
* Shiny user interfaces can be built entirely using R, or can be written directly in HTML, CSS, and JavaScript for more flexibility.
|
||||
* Works in any R environment (Console R, Rgui for Windows or Mac, ESS, StatET, RStudio, etc.)
|
||||
* Works in any R environment (Console R, Rgui for Windows or Mac, ESS, StatET, RStudio, etc.).
|
||||
* Attractive default UI theme based on [Bootstrap](http://getbootstrap.com/2.3.2/).
|
||||
* 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.
|
||||
@@ -32,15 +32,31 @@ To install the latest development builds directly from GitHub, run this instead:
|
||||
```r
|
||||
if (!require("devtools"))
|
||||
install.packages("devtools")
|
||||
devtools::install_github("shiny", "rstudio")
|
||||
devtools::install_github("rstudio/shiny")
|
||||
```
|
||||
|
||||
## 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.
|
||||
To learn more we highly recommend you check out the [Shiny Tutorial](http://shiny.rstudio.com/tutorial/). The tutorial explains the framework in-depth, walks you through building a simple application, and includes extensive annotated examples.
|
||||
|
||||
We hope you enjoy using Shiny. If you have general questions about using Shiny, please use the Shiny [mailing list](https://groups.google.com/forum/#!forum/shiny-discuss). For bug reports, please use the [issue tracker](https://github.com/rstudio/shiny/issues).
|
||||
|
||||
## Bootstrap 3 migration
|
||||
|
||||
Shiny versions 0.10.2.2 and below used the Bootstrap 2 web framework. After 0.10.2.2, Shiny switched to Bootstrap 3. For most users, the upgrade should be seamless. However, if you have have customized your HTML-generating code to use features specific to Bootstrap 2, you may need to update your code to work with Bootstrap 3.
|
||||
|
||||
If you do not wish to update your code at this time, you can use the [shinybootstrap2](https://github.com/rstudio/shinybootstrap2) package for backward compatibility.
|
||||
|
||||
If you prefer to install an older version of Shiny, you can do it using the devtools package:
|
||||
|
||||
```R
|
||||
devtools::install_version("shiny", version = "0.10.2.2")
|
||||
```
|
||||
|
||||
## Development notes
|
||||
|
||||
The Javascript code in Shiny is minified using tools that run on Node.js. See the tools/ directory for more information.
|
||||
|
||||
## License
|
||||
|
||||
The shiny package is licensed under the GPLv3. See these files in the inst directory for additional details:
|
||||
|
||||
0
cran-comments.md
Normal file
0
cran-comments.md
Normal file
678
inst/COPYING
678
inst/COPYING
@@ -1,678 +0,0 @@
|
||||
The shiny package is licensed to you under the GPLv3, the terms of
|
||||
which are included below. The markdown pacakge includes other open
|
||||
source software whose license terms can be found in the file NOTICE.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
325
inst/NOTICE
325
inst/NOTICE
@@ -1,325 +0,0 @@
|
||||
The shiny package inludes other open source software components. The following
|
||||
is a list of these components (full copies of the license agreements used by
|
||||
these components are included below):
|
||||
|
||||
- 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,
|
||||
http://jquery.com/
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Bootstrap, bootstrap-datepicker, and selectize License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,6 @@
|
||||
name: 01_hello
|
||||
account: admin
|
||||
server: localhost
|
||||
bundleId: 1
|
||||
url: http://localhost:3939/admin/01_hello/
|
||||
when: 1436550957.65385
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic required to draw a histogram
|
||||
shinyServer(function(input, output) {
|
||||
function(input, output) {
|
||||
|
||||
# Expression that generates a histogram. The expression is
|
||||
# wrapped in a call to renderPlot to indicate that:
|
||||
@@ -18,4 +18,4 @@ shinyServer(function(input, output) {
|
||||
hist(x, breaks = bins, col = 'darkgray', border = 'white')
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for application that draws a histogram
|
||||
shinyUI(fluidPage(
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Hello Shiny!"),
|
||||
@@ -21,4 +21,4 @@ shinyUI(fluidPage(
|
||||
plotOutput("distPlot")
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ library(datasets)
|
||||
|
||||
# Define server logic required to summarize and view the selected
|
||||
# dataset
|
||||
shinyServer(function(input, output) {
|
||||
function(input, output) {
|
||||
|
||||
# Return the requested dataset
|
||||
datasetInput <- reactive({
|
||||
@@ -23,4 +23,4 @@ shinyServer(function(input, output) {
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
shinyUI(fluidPage(
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Shiny Text"),
|
||||
@@ -24,4 +24,4 @@ shinyUI(fluidPage(
|
||||
tableOutput("view")
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ library(datasets)
|
||||
|
||||
# Define server logic required to summarize and view the selected
|
||||
# dataset
|
||||
shinyServer(function(input, output) {
|
||||
function(input, output) {
|
||||
|
||||
# By declaring datasetInput as a reactive expression we ensure
|
||||
# that:
|
||||
@@ -50,4 +50,4 @@ shinyServer(function(input, output) {
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
shinyUI(fluidPage(
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Reactivity"),
|
||||
@@ -31,4 +31,4 @@ shinyUI(fluidPage(
|
||||
tableOutput("view")
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -11,24 +11,24 @@ mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
|
||||
|
||||
# Define server logic required to plot various variables against
|
||||
# mpg
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Compute the forumla text in a reactive expression since it is
|
||||
function(input, output) {
|
||||
|
||||
# Compute the formula text in a reactive expression since it is
|
||||
# shared by the output$caption and output$mpgPlot functions
|
||||
formulaText <- reactive({
|
||||
paste("mpg ~", input$variable)
|
||||
})
|
||||
|
||||
|
||||
# Return the formula text for printing as a caption
|
||||
output$caption <- renderText({
|
||||
formulaText()
|
||||
})
|
||||
|
||||
|
||||
# Generate a plot of the requested variable against mpg and
|
||||
# only include outliers if requested
|
||||
output$mpgPlot <- renderPlot({
|
||||
boxplot(as.formula(formulaText()),
|
||||
boxplot(as.formula(formulaText()),
|
||||
data = mpgData,
|
||||
outline = input$outliers)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for miles per gallon application
|
||||
shinyUI(fluidPage(
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Miles Per Gallon"),
|
||||
@@ -26,4 +26,4 @@ shinyUI(fluidPage(
|
||||
plotOutput("mpgPlot")
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic for slider examples
|
||||
shinyServer(function(input, output) {
|
||||
function(input, output) {
|
||||
|
||||
# Reactive expression to compose a data frame containing all of
|
||||
# the values
|
||||
@@ -26,4 +26,4 @@ shinyServer(function(input, output) {
|
||||
output$values <- renderTable({
|
||||
sliderValues()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for slider demo application
|
||||
shinyUI(fluidPage(
|
||||
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Sliders"),
|
||||
|
||||
|
||||
# Sidebar with sliders that demonstrate various available
|
||||
# options
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
# Simple integer interval
|
||||
sliderInput("integer", "Integer:",
|
||||
sliderInput("integer", "Integer:",
|
||||
min=0, max=1000, value=500),
|
||||
|
||||
|
||||
# Decimal interval with step value
|
||||
sliderInput("decimal", "Decimal:",
|
||||
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,
|
||||
|
||||
# Provide a custom currency format for value display,
|
||||
# with basic animation
|
||||
sliderInput("format", "Custom Format:",
|
||||
sliderInput("format", "Custom Format:",
|
||||
min = 0, max = 10000, value = 0, step = 2500,
|
||||
format="$#,##0", locale="us", animate=TRUE),
|
||||
|
||||
pre = "$", sep = ",", animate=TRUE),
|
||||
|
||||
# Animation with custom interval (in ms) to control speed,
|
||||
# plus looping
|
||||
sliderInput("animation", "Looping Animation:", 1, 2000, 1,
|
||||
step = 10, animate=
|
||||
animationOptions(interval=300, loop=TRUE))
|
||||
),
|
||||
|
||||
|
||||
# Show a table summarizing the values entered
|
||||
mainPanel(
|
||||
tableOutput("values")
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic for random distribution application
|
||||
shinyServer(function(input, output) {
|
||||
function(input, output) {
|
||||
|
||||
# Reactive expression to generate the requested distribution.
|
||||
# This is called whenever the inputs change. The output
|
||||
@@ -41,4 +41,4 @@ shinyServer(function(input, output) {
|
||||
data.frame(x=data())
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for random distribution application
|
||||
shinyUI(fluidPage(
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Tabsets"),
|
||||
@@ -35,4 +35,4 @@ shinyUI(fluidPage(
|
||||
)
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ library(datasets)
|
||||
|
||||
# Define server logic required to summarize and view the
|
||||
# selected dataset
|
||||
shinyServer(function(input, output) {
|
||||
function(input, output) {
|
||||
|
||||
# Return the requested dataset
|
||||
datasetInput <- reactive({
|
||||
@@ -23,4 +23,4 @@ shinyServer(function(input, output) {
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
shinyUI(fluidPage(
|
||||
fluidPage(
|
||||
|
||||
# Application title.
|
||||
titlePanel("More Widgets"),
|
||||
@@ -40,4 +40,4 @@ shinyUI(fluidPage(
|
||||
tableOutput("view")
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic for random distribution application
|
||||
shinyServer(function(input, output) {
|
||||
function(input, output) {
|
||||
|
||||
# Reactive expression to generate the requested distribution. This is
|
||||
# called whenever the inputs change. The output expressions defined
|
||||
@@ -39,4 +39,4 @@ shinyServer(function(input, output) {
|
||||
data.frame(x=data())
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
library(shiny)
|
||||
|
||||
shinyServer(function(input, output) {
|
||||
function(input, output) {
|
||||
output$contents <- renderTable({
|
||||
|
||||
# input$file1 will be NULL initially. After the user selects
|
||||
@@ -17,4 +17,4 @@ shinyServer(function(input, output) {
|
||||
read.csv(inFile$datapath, header=input$header, sep=input$sep,
|
||||
quote=input$quote)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
library(shiny)
|
||||
|
||||
shinyUI(fluidPage(
|
||||
fluidPage(
|
||||
titlePanel("Uploading Files"),
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
@@ -25,4 +25,4 @@ shinyUI(fluidPage(
|
||||
tableOutput('contents')
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
shinyServer(function(input, output) {
|
||||
function(input, output) {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
@@ -18,4 +18,4 @@ shinyServer(function(input, output) {
|
||||
write.csv(datasetInput(), file)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
shinyUI(fluidPage(
|
||||
fluidPage(
|
||||
titlePanel('Downloading Data'),
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
@@ -10,4 +10,4 @@ shinyUI(fluidPage(
|
||||
tableOutput('table')
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
shinyServer(function(input, output, session) {
|
||||
function(input, output, session) {
|
||||
output$currentTime <- renderText({
|
||||
invalidateLater(1000, session)
|
||||
paste("The current time is", Sys.time())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
shinyUI(fluidPage(
|
||||
fluidPage(
|
||||
textOutput("currentTime")
|
||||
))
|
||||
)
|
||||
|
||||
@@ -5,6 +5,8 @@ sd_section("UI Layout",
|
||||
"bootstrapPage",
|
||||
"column",
|
||||
"conditionalPanel",
|
||||
"fillPage",
|
||||
"fillRow",
|
||||
"fixedPage",
|
||||
"fluidPage",
|
||||
"headerPanel",
|
||||
@@ -42,6 +44,8 @@ sd_section("UI Inputs",
|
||||
"sliderInput",
|
||||
"submitButton",
|
||||
"textInput",
|
||||
"passwordInput",
|
||||
"updateActionButton",
|
||||
"updateCheckboxGroupInput",
|
||||
"updateCheckboxInput",
|
||||
"updateDateInput",
|
||||
@@ -58,13 +62,14 @@ sd_section("UI Outputs",
|
||||
"Functions for creating user interface elements that, in conjunction with rendering functions, display different kinds of output from your application.",
|
||||
c(
|
||||
"htmlOutput",
|
||||
"imageOutput",
|
||||
"plotOutput",
|
||||
"outputOptions",
|
||||
"tableOutput",
|
||||
"textOutput",
|
||||
"verbatimTextOutput",
|
||||
"downloadButton"
|
||||
"downloadButton",
|
||||
"Progress",
|
||||
"withProgress"
|
||||
)
|
||||
)
|
||||
sd_section("Interface builder functions",
|
||||
@@ -76,7 +81,10 @@ sd_section("Interface builder functions",
|
||||
"singleton",
|
||||
"tag",
|
||||
"validateCssUnit",
|
||||
"withTags"
|
||||
"withTags",
|
||||
"htmlTemplate",
|
||||
"bootstrapLib",
|
||||
"suppressDependencies"
|
||||
)
|
||||
)
|
||||
sd_section("Rendering functions",
|
||||
@@ -105,6 +113,7 @@ sd_section("Reactive constructs",
|
||||
"isolate",
|
||||
"makeReactiveBinding",
|
||||
"observe",
|
||||
"observeEvent",
|
||||
"reactive",
|
||||
"reactiveFileReader",
|
||||
"reactivePoll",
|
||||
@@ -126,14 +135,18 @@ sd_section("Running",
|
||||
"Functions that are used to run or stop Shiny applications.",
|
||||
c(
|
||||
"runApp",
|
||||
"runGadget",
|
||||
"runExample",
|
||||
"runGadget",
|
||||
"runUrl",
|
||||
"stopApp"
|
||||
"stopApp",
|
||||
"viewer"
|
||||
)
|
||||
)
|
||||
sd_section("Extending Shiny",
|
||||
"Functions that are intended to be called by third-party packages that extend Shiny.",
|
||||
c(
|
||||
"createWebDependency",
|
||||
"addResourcePath",
|
||||
"registerInputHandler",
|
||||
"removeInputHandler",
|
||||
@@ -143,6 +156,8 @@ sd_section("Extending Shiny",
|
||||
sd_section("Utility functions",
|
||||
"Miscellaneous utilities that may be useful to advanced users or when extending Shiny.",
|
||||
c(
|
||||
"req",
|
||||
"cancelOutput",
|
||||
"validate",
|
||||
"session",
|
||||
"exprToFunction",
|
||||
@@ -151,7 +166,26 @@ sd_section("Utility functions",
|
||||
"plotPNG",
|
||||
"repeatable",
|
||||
"shinyDeprecated",
|
||||
"serverInfo"
|
||||
"serverInfo",
|
||||
"shiny-options"
|
||||
)
|
||||
)
|
||||
sd_section("Plot interaction",
|
||||
"Functions related to interactive plots",
|
||||
c(
|
||||
"brushedPoints",
|
||||
"brushOpts",
|
||||
"clickOpts",
|
||||
"dblclickOpts",
|
||||
"hoverOpts",
|
||||
"nearPoints"
|
||||
)
|
||||
)
|
||||
sd_section("Modules",
|
||||
"Functions for modularizing Shiny apps",
|
||||
c(
|
||||
"NS",
|
||||
"callModule"
|
||||
)
|
||||
)
|
||||
sd_section("Embedding",
|
||||
|
||||
7
inst/template/default.html
Normal file
7
inst/template/default.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{{ headContent() }}
|
||||
</head>
|
||||
{{ body }}
|
||||
</html>
|
||||
@@ -1,95 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,2 +0,0 @@
|
||||
<label>Text input:</label>
|
||||
<input id="in_text" type="text" value="starting value"/>
|
||||
@@ -1,20 +0,0 @@
|
||||
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.
|
||||
@@ -1,681 +0,0 @@
|
||||
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;
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
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; }
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user