Compare commits
556 Commits
feature/sh
...
v0.10.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c711b76b0 | ||
|
|
9c914f10c4 | ||
|
|
eda56d118a | ||
|
|
02c7351c6d | ||
|
|
ab618235f1 | ||
|
|
ffead9ed70 | ||
|
|
36aefadced | ||
|
|
75ccfe38ce | ||
|
|
e3cb3fe2e4 | ||
|
|
983e7e9b75 | ||
|
|
3db47c076c | ||
|
|
eeff285b33 | ||
|
|
029595f8ea | ||
|
|
ea2ec27724 | ||
|
|
f6bf4a416f | ||
|
|
af978a68e3 | ||
|
|
89dc1323e1 | ||
|
|
a4b5f63deb | ||
|
|
feaa6ccff4 | ||
|
|
7159293337 | ||
|
|
4a5b31e3a7 | ||
|
|
6f1dc89fb3 | ||
|
|
29dd405fe5 | ||
|
|
0f0b0cd3d8 | ||
|
|
262528e36a | ||
|
|
597e86dd57 | ||
|
|
b604dba948 | ||
|
|
1837a64bd2 | ||
|
|
9b413de4d8 | ||
|
|
3d77cbd677 | ||
|
|
62176c3218 | ||
|
|
d7a01c32cc | ||
|
|
cc493fd545 | ||
|
|
6b8679454d | ||
|
|
1b68d61e54 | ||
|
|
418de862e6 | ||
|
|
413653858e | ||
|
|
f0886a7556 | ||
|
|
0e2666948f | ||
|
|
d2fc851816 | ||
|
|
e1fb29c8c5 | ||
|
|
fe3158fdd6 | ||
|
|
721b26f80b | ||
|
|
d3ecfb22ee | ||
|
|
27a98020c9 | ||
|
|
ab56b72f39 | ||
|
|
8063f66958 | ||
|
|
bf270b9adb | ||
|
|
972db08740 | ||
|
|
6326c7cbaa | ||
|
|
4152ace514 | ||
|
|
038221408c | ||
|
|
9f76def7ce | ||
|
|
1b83770c5c | ||
|
|
3458d924ca | ||
|
|
0749b9500c | ||
|
|
b1dfc18a8c | ||
|
|
7b25c282c0 | ||
|
|
a128ceaf2d | ||
|
|
f266cab580 | ||
|
|
23bf9aaf17 | ||
|
|
1983f60ec6 | ||
|
|
27f8909406 | ||
|
|
9988206911 | ||
|
|
31fe1fdfa6 | ||
|
|
77b125ce2d | ||
|
|
6e68e07aa2 | ||
|
|
86bb010a93 | ||
|
|
4a623b596b | ||
|
|
bcf098ea7d | ||
|
|
4bfb226fb5 | ||
|
|
691615108b | ||
|
|
858ab00e36 | ||
|
|
7023f5b145 | ||
|
|
eb4fabeac6 | ||
|
|
a5e09f9ce4 | ||
|
|
c2fe4e8b6d | ||
|
|
5d22648d34 | ||
|
|
066fd15184 | ||
|
|
fe90c230d5 | ||
|
|
0b5ae92136 | ||
|
|
1c5565aaee | ||
|
|
69c177a3ec | ||
|
|
0645b3f65b | ||
|
|
9e7471fcc0 | ||
|
|
c520f53799 | ||
|
|
0bf1386802 | ||
|
|
b2ab3797aa | ||
|
|
ede0ca8bd1 | ||
|
|
81e35f0cc3 | ||
|
|
237522a1f7 | ||
|
|
2f94e1d2c9 | ||
|
|
2689dd32bb | ||
|
|
ad5e703b8f | ||
|
|
d3bc2e9279 | ||
|
|
0cd1644cf1 | ||
|
|
f02b405c12 | ||
|
|
baa7036799 | ||
|
|
431aecaf00 | ||
|
|
f31bb56ea6 | ||
|
|
cf3b805c46 | ||
|
|
517283ca58 | ||
|
|
f416b7ba47 | ||
|
|
973190b7a1 | ||
|
|
f536a9d3d3 | ||
|
|
1348ec3bcf | ||
|
|
9a250a4861 | ||
|
|
6450927192 | ||
|
|
7c9dbdc802 | ||
|
|
8d460afe2d | ||
|
|
6c44c2cf24 | ||
|
|
cea550ebba | ||
|
|
911a352ee6 | ||
|
|
3fadfbe06e | ||
|
|
5bf362927f | ||
|
|
4da5ca5ba9 | ||
|
|
d747005b30 | ||
|
|
03a395107d | ||
|
|
58ef4ccabf | ||
|
|
71ed082bb5 | ||
|
|
0819ac8124 | ||
|
|
0cdd223172 | ||
|
|
571393f146 | ||
|
|
c85868c652 | ||
|
|
a7a6f3b020 | ||
|
|
3a0a11d55a | ||
|
|
7eb8ddf372 | ||
|
|
87af63644a | ||
|
|
0a9dd18070 | ||
|
|
f82b061ba7 | ||
|
|
c17509e2a0 | ||
|
|
cb383d4f62 | ||
|
|
451f950d0d | ||
|
|
bd0eae0961 | ||
|
|
53a401f847 | ||
|
|
b288f5ca19 | ||
|
|
7a2fc19c4f | ||
|
|
046d712d6a | ||
|
|
e829aaecf1 | ||
|
|
9ab2f5338e | ||
|
|
d7bda924be | ||
|
|
07eb2e51b7 | ||
|
|
dfafa7ae40 | ||
|
|
dde266768c | ||
|
|
01c81675f7 | ||
|
|
71972eb362 | ||
|
|
eb9f5f9025 | ||
|
|
eb4d4d7437 | ||
|
|
1cb5e09109 | ||
|
|
cc82fff5d3 | ||
|
|
3212e59dcc | ||
|
|
44a795bf18 | ||
|
|
376e6f35a2 | ||
|
|
3b324e9532 | ||
|
|
a0df8f3490 | ||
|
|
6c14789362 | ||
|
|
880a12b914 | ||
|
|
93d69400e6 | ||
|
|
d4829e49ea | ||
|
|
1c56be3a6b | ||
|
|
07a0dfddc7 | ||
|
|
b86f9086ef | ||
|
|
343ca12c6f | ||
|
|
af3c4f84b6 | ||
|
|
3679e8795f | ||
|
|
39b4805a76 | ||
|
|
3bdcdf96d4 | ||
|
|
b54e5d33bc | ||
|
|
85e020a513 | ||
|
|
5b6268f5bc | ||
|
|
063b58eebb | ||
|
|
f8b38e4683 | ||
|
|
18e85c32b4 | ||
|
|
831fba9a53 | ||
|
|
b1f233cd8c | ||
|
|
3d0caba695 | ||
|
|
79c92f1f8e | ||
|
|
87f26e47bb | ||
|
|
9d8d04ae28 | ||
|
|
a42f046ff8 | ||
|
|
01c24a578b | ||
|
|
6b82354129 | ||
|
|
b00fbda1ae | ||
|
|
bab200ff03 | ||
|
|
357e81aeca | ||
|
|
3189c748b5 | ||
|
|
2700643cbf | ||
|
|
b0f95cd9e0 | ||
|
|
ff628ac0b2 | ||
|
|
f21aefe9e9 | ||
|
|
8babbd69d8 | ||
|
|
11bf02eb56 | ||
|
|
f5fa7d6d4b | ||
|
|
77bff6e6c2 | ||
|
|
e84a76cebd | ||
|
|
342265be94 | ||
|
|
62ec9291d8 | ||
|
|
dee6fbcb8f | ||
|
|
72fa9a2dcb | ||
|
|
ca27a9e31a | ||
|
|
18d0f45cf9 | ||
|
|
424fd515a4 | ||
|
|
00b40d64a1 | ||
|
|
3a7d0a5a9f | ||
|
|
57a02318e3 | ||
|
|
8f6d8cf0d6 | ||
|
|
5b6605b296 | ||
|
|
4d83596595 | ||
|
|
7e12a281f5 | ||
|
|
c63c10e48a | ||
|
|
155554f0b7 | ||
|
|
26b0836756 | ||
|
|
a87dc9bab2 | ||
|
|
9c1555a110 | ||
|
|
fbda2db884 | ||
|
|
2a229774ef | ||
|
|
137e5b13ef | ||
|
|
7920d66cd0 | ||
|
|
9f2dae7f3b | ||
|
|
ffde0ad1f5 | ||
|
|
2c2658a8ec | ||
|
|
6f2f8f6f7a | ||
|
|
4b6dcdd1b0 | ||
|
|
de346fd6c3 | ||
|
|
bf9d7c2012 | ||
|
|
143803f86d | ||
|
|
311143451d | ||
|
|
c9030f401d | ||
|
|
8668ddce74 | ||
|
|
7a495357f7 | ||
|
|
13864a811d | ||
|
|
5b65e4b250 | ||
|
|
dfe4a80501 | ||
|
|
bf82b9742a | ||
|
|
829a466f72 | ||
|
|
1206c70c42 | ||
|
|
3c32c349b9 | ||
|
|
0709f08d65 | ||
|
|
50f78c6e40 | ||
|
|
7e7afc6d38 | ||
|
|
1130eadac8 | ||
|
|
959fc2bbb2 | ||
|
|
f8ae505011 | ||
|
|
cd183a1926 | ||
|
|
bb2796fbc3 | ||
|
|
5de7103890 | ||
|
|
a78c91ba7e | ||
|
|
fca50da57b | ||
|
|
61f2c908b1 | ||
|
|
4c096ac068 | ||
|
|
2c95678be1 | ||
|
|
1a643cecf3 | ||
|
|
aa10b2e8c4 | ||
|
|
0b9317d047 | ||
|
|
4d58f05f38 | ||
|
|
6e879c8156 | ||
|
|
b6ee67aa41 | ||
|
|
07bed0c7c7 | ||
|
|
d2bd59d149 | ||
|
|
7bdac5a44e | ||
|
|
51f5db4374 | ||
|
|
e395ae6555 | ||
|
|
1df9c498cf | ||
|
|
57b3b919a5 | ||
|
|
00c6bbb297 | ||
|
|
b6536a0af3 | ||
|
|
d08a2507fa | ||
|
|
8bc8829577 | ||
|
|
c843e6f68c | ||
|
|
84583e5501 | ||
|
|
4548562138 | ||
|
|
32c170b10a | ||
|
|
97dafa0a55 | ||
|
|
0be1ee46f2 | ||
|
|
34c9ab7643 | ||
|
|
59dbca250f | ||
|
|
4028dbfda1 | ||
|
|
b9dbf610b0 | ||
|
|
d443810520 | ||
|
|
fcd941d33d | ||
|
|
9c063fa37c | ||
|
|
2720cfe346 | ||
|
|
c39e38081e | ||
|
|
3deb4c3f42 | ||
|
|
6945091238 | ||
|
|
c758c4785a | ||
|
|
19269a20fb | ||
|
|
45669cacb1 | ||
|
|
840bc52aae | ||
|
|
bbc36e349f | ||
|
|
a4325adcdd | ||
|
|
23f39649d0 | ||
|
|
87b09a534e | ||
|
|
39f0e5ae0c | ||
|
|
62aaab0926 | ||
|
|
cddfe999aa | ||
|
|
fcbb658ac2 | ||
|
|
3bbf06ba49 | ||
|
|
d9be6f1d2e | ||
|
|
5d70e68a0b | ||
|
|
529f2325b2 | ||
|
|
314d433f86 | ||
|
|
12ea950c5f | ||
|
|
f4d12220ca | ||
|
|
6a9cba90f4 | ||
|
|
6873e1f1cb | ||
|
|
fa0a91a75d | ||
|
|
020bb659c5 | ||
|
|
b1d6687fb0 | ||
|
|
f67e17b287 | ||
|
|
81bd57c5ea | ||
|
|
d803bae874 | ||
|
|
14606f4087 | ||
|
|
599fdc7ee5 | ||
|
|
722e205db5 | ||
|
|
f67849eb47 | ||
|
|
662ca4e40a | ||
|
|
aa61be74d8 | ||
|
|
10296fcd6b | ||
|
|
f8bf146b6c | ||
|
|
52f104c517 | ||
|
|
6c1fc224f0 | ||
|
|
6b9ae3a8b3 | ||
|
|
07f73030c6 | ||
|
|
47130c79ee | ||
|
|
f3a3bdfe4f | ||
|
|
e5e54fe4c1 | ||
|
|
29c0f9a43a | ||
|
|
0b78229c77 | ||
|
|
c2a1d70070 | ||
|
|
260ecd1d9f | ||
|
|
3dce2e761a | ||
|
|
80a54200ce | ||
|
|
51227d438a | ||
|
|
6fb4199d37 | ||
|
|
6ba46aff6b | ||
|
|
5da34d0646 | ||
|
|
f215088939 | ||
|
|
df34dcdb0c | ||
|
|
89f464af99 | ||
|
|
3f6f02f7d2 | ||
|
|
0d861e5389 | ||
|
|
b290c8700c | ||
|
|
81b6fbe263 | ||
|
|
b3af293f66 | ||
|
|
b187485172 | ||
|
|
b449d9759c | ||
|
|
d9d63a3a2e | ||
|
|
fd7b54fb77 | ||
|
|
887f8a606d | ||
|
|
7e3717243f | ||
|
|
221849aa3a | ||
|
|
b52d40ab28 | ||
|
|
3ed68ffd92 | ||
|
|
cc3cd2c141 | ||
|
|
5e30f7efc4 | ||
|
|
35090251ef | ||
|
|
338afb4893 | ||
|
|
194d8a05f8 | ||
|
|
93e276bd9b | ||
|
|
a69517519c | ||
|
|
f646b1efb4 | ||
|
|
fc9bedacc0 | ||
|
|
795eeee809 | ||
|
|
6d7818962e | ||
|
|
068517c933 | ||
|
|
5b030200df | ||
|
|
c732122966 | ||
|
|
d7eb9b2d18 | ||
|
|
b8b09adda1 | ||
|
|
07c8f0c4b7 | ||
|
|
2bd201de63 | ||
|
|
0b7e118a37 | ||
|
|
a546769225 | ||
|
|
81745f932d | ||
|
|
4415bf31d2 | ||
|
|
5c1bcb41d8 | ||
|
|
b659c4c2bb | ||
|
|
65adc8a405 | ||
|
|
4141f78717 | ||
|
|
80cb02d206 | ||
|
|
a5a4510a1e | ||
|
|
95c30649d3 | ||
|
|
8e5cbde08c | ||
|
|
6df8632e29 | ||
|
|
3c1218fff1 | ||
|
|
69c0414791 | ||
|
|
d63f83fcbb | ||
|
|
75c3bf0c2f | ||
|
|
c9a8ab2389 | ||
|
|
2c467c00e1 | ||
|
|
c63ec5a1f2 | ||
|
|
e886558cbb | ||
|
|
8dd6dabe50 | ||
|
|
c090c6adf9 | ||
|
|
84da0befcd | ||
|
|
267751c8b9 | ||
|
|
8add9f7188 | ||
|
|
a100b0991b | ||
|
|
9ce9c5e535 | ||
|
|
b2d004ca1a | ||
|
|
657d50f9a3 | ||
|
|
60e355c4f5 | ||
|
|
adb444a60f | ||
|
|
e7e13ff70d | ||
|
|
a1e81db597 | ||
|
|
f23f2ff0a0 | ||
|
|
c1b18098f1 | ||
|
|
31c39592e3 | ||
|
|
82a1dad22a | ||
|
|
1ecec24727 | ||
|
|
607841e947 | ||
|
|
e234b403ae | ||
|
|
80ce7a36f8 | ||
|
|
705a8666be | ||
|
|
9167905118 | ||
|
|
bdeb6734d8 | ||
|
|
9a7b042594 | ||
|
|
7aea256fd8 | ||
|
|
857b5e6932 | ||
|
|
1a2d675439 | ||
|
|
0c749643de | ||
|
|
09bb1548f9 | ||
|
|
5ffe531844 | ||
|
|
fab24a3200 | ||
|
|
899d5e9d1d | ||
|
|
ba510884f2 | ||
|
|
78e8df8e17 | ||
|
|
deba1609c3 | ||
|
|
88d2425ca3 | ||
|
|
7117f9e058 | ||
|
|
c21c407416 | ||
|
|
4b4ad42063 | ||
|
|
474d514c7d | ||
|
|
6239466da8 | ||
|
|
7746d75582 | ||
|
|
642c9ded08 | ||
|
|
e0ae931ddd | ||
|
|
0d7727a405 | ||
|
|
28f689498a | ||
|
|
eb8fec7f2d | ||
|
|
2e16fa1d70 | ||
|
|
1b856c4909 | ||
|
|
585ad30af1 | ||
|
|
c0cdc4083c | ||
|
|
9b9db4f161 | ||
|
|
84a1d8d25e | ||
|
|
d3115a3bf3 | ||
|
|
964789e9a6 | ||
|
|
eeded51ff8 | ||
|
|
8f24f1b4d6 | ||
|
|
ad910a295a | ||
|
|
cf14c6b1e9 | ||
|
|
49da114caa | ||
|
|
b8376ebbf7 | ||
|
|
29701d7295 | ||
|
|
16279695a9 | ||
|
|
999fc86bc6 | ||
|
|
0276d533fb | ||
|
|
b77fc34a7b | ||
|
|
60c450d57e | ||
|
|
73411c75db | ||
|
|
8d146f7dff | ||
|
|
5c34aa0bb5 | ||
|
|
2b2ed8162d | ||
|
|
9770bd8005 | ||
|
|
4e020818ae | ||
|
|
58471c6971 | ||
|
|
2a2e02bf56 | ||
|
|
75d8cee766 | ||
|
|
1aed36bd16 | ||
|
|
00ce58ed18 | ||
|
|
d11aa1a61c | ||
|
|
56a62d3b4d | ||
|
|
e6dd668657 | ||
|
|
f60a64c8db | ||
|
|
eff1c298c9 | ||
|
|
358b0a122b | ||
|
|
c0f7ba9d46 | ||
|
|
c4edae8196 | ||
|
|
398dab808c | ||
|
|
3530871560 | ||
|
|
1ba26fdb98 | ||
|
|
a3b85b4e3e | ||
|
|
e37a5d0394 | ||
|
|
e5a8e77e2a | ||
|
|
314b59798f | ||
|
|
e9ae16e534 | ||
|
|
c971ca0ce2 | ||
|
|
0ad9a5f9c6 | ||
|
|
c31d91668a | ||
|
|
f5c196d717 | ||
|
|
3b90eed89f | ||
|
|
9828c8b787 | ||
|
|
b3e997134f | ||
|
|
f560baa69b | ||
|
|
8cf5f00c87 | ||
|
|
482c3895d3 | ||
|
|
fc0d4bde35 | ||
|
|
33ed89a036 | ||
|
|
0a5953c104 | ||
|
|
77f6be1a8b | ||
|
|
5bd3f9a571 | ||
|
|
ef59119663 | ||
|
|
45baca7018 | ||
|
|
9b1edb7a97 | ||
|
|
31c071d086 | ||
|
|
ecf4c5c104 | ||
|
|
35fbfece0d | ||
|
|
b7721e42d3 | ||
|
|
386346cee9 | ||
|
|
bbecccc45e | ||
|
|
1a8f84c134 | ||
|
|
66181fdcdf | ||
|
|
b9c05e8a9c | ||
|
|
9c22d6c12a | ||
|
|
f3cedbbd6f | ||
|
|
3f3a660ca1 | ||
|
|
1c6ded8416 | ||
|
|
aa63fdb26f | ||
|
|
3932330ce6 | ||
|
|
3b946b1c69 | ||
|
|
14df829f18 | ||
|
|
788d024be6 | ||
|
|
c20b56e089 | ||
|
|
287f4f239e | ||
|
|
dce66945ec | ||
|
|
92bd1d5200 | ||
|
|
06d2df8211 | ||
|
|
36256856b5 | ||
|
|
a771ae853c | ||
|
|
ef4e10bbb1 | ||
|
|
0dbe4d936e | ||
|
|
731fee11d4 | ||
|
|
6759df52c3 | ||
|
|
914b997076 | ||
|
|
0b8a2fea72 | ||
|
|
fb2538135c | ||
|
|
b4c547c278 | ||
|
|
b243bc846b | ||
|
|
6b8f6162b6 | ||
|
|
158db1532b | ||
|
|
99c3c2fc80 | ||
|
|
a86fc96730 | ||
|
|
cf51af17fd | ||
|
|
8c1b6a5cf0 | ||
|
|
bcecb8cd76 | ||
|
|
557790b0e5 | ||
|
|
8eb5a45718 | ||
|
|
7b64cef73b | ||
|
|
106203170e | ||
|
|
174d2bfc11 | ||
|
|
abda9c7f97 | ||
|
|
6f627fca96 | ||
|
|
339fbc482b | ||
|
|
a9750fb088 |
@@ -1,13 +1,13 @@
|
||||
^\.Rproj\.user$
|
||||
^\.git$
|
||||
^examples$
|
||||
^README\.md$
|
||||
^shiny\.Rproj$
|
||||
^shiny\.sh$
|
||||
^shiny\.cmd$
|
||||
^run\.R$
|
||||
^\.gitignore$
|
||||
^res$
|
||||
^tools$
|
||||
^man-roxygen$
|
||||
^\.travis\.yml$
|
||||
^staticdocs$
|
||||
^tools$
|
||||
|
||||
2
.Rinstignore
Normal file
@@ -0,0 +1,2 @@
|
||||
^tools$
|
||||
^Rmd$
|
||||
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/NEWS merge=union
|
||||
14
.travis.yml
@@ -9,12 +9,20 @@ env:
|
||||
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-get update
|
||||
- sudo apt-get install r-base-dev
|
||||
- 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"
|
||||
- Rscript -e "install.packages('$R_MY_PKG', dep = TRUE, repos = 'http://cran.rstudio.org')"
|
||||
- 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
|
||||
|
||||
49
DESCRIPTION
@@ -1,8 +1,8 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 0.8.0.99
|
||||
Date: 2013-10-26
|
||||
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
|
||||
@@ -14,42 +14,51 @@ Depends:
|
||||
R (>= 2.14.1),
|
||||
methods
|
||||
Imports:
|
||||
stats,
|
||||
tools,
|
||||
utils,
|
||||
httpuv (>= 1.2.0),
|
||||
caTools,
|
||||
RJSONIO,
|
||||
xtable,
|
||||
digest
|
||||
digest,
|
||||
htmltools (>= 0.2.4)
|
||||
Suggests:
|
||||
datasets,
|
||||
markdown,
|
||||
Cairo (>= 1.5-5),
|
||||
testthat
|
||||
testthat,
|
||||
knitr (>= 1.6),
|
||||
markdown
|
||||
URL: http://www.rstudio.com/shiny/
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Roxygen: list(wrap = FALSE)
|
||||
Collate:
|
||||
'app.R'
|
||||
'bootstrap-layout.R'
|
||||
'map.R'
|
||||
'priorityqueue.R'
|
||||
'globals.R'
|
||||
'utils.R'
|
||||
'tar.R'
|
||||
'timer.R'
|
||||
'tags.R'
|
||||
'bootstrap.R'
|
||||
'cache.R'
|
||||
'graph.R'
|
||||
'react.R'
|
||||
'reactives.R'
|
||||
'fileupload.R'
|
||||
'sessioncontext.R'
|
||||
'graph.R'
|
||||
'hooks.R'
|
||||
'html-deps.R'
|
||||
'htmltools.R'
|
||||
'imageutils.R'
|
||||
'jqueryui.R'
|
||||
'middleware-shiny.R'
|
||||
'middleware.R'
|
||||
'priorityqueue.R'
|
||||
'react.R'
|
||||
'reactive-domains.R'
|
||||
'reactives.R'
|
||||
'run-url.R'
|
||||
'server.R'
|
||||
'shiny.R'
|
||||
'shinywrappers.R'
|
||||
'shinyui.R'
|
||||
'shinywrappers.R'
|
||||
'showcase.R'
|
||||
'slider.R'
|
||||
'bootstrap.R'
|
||||
'run-url.R'
|
||||
'imageutils.R'
|
||||
'tar.R'
|
||||
'timer.R'
|
||||
'update-input.R'
|
||||
'bootstrap-layout.R'
|
||||
'hooks.R'
|
||||
|
||||
53
NAMESPACE
@@ -1,3 +1,5 @@
|
||||
# Generated by roxygen2 (4.0.1): do not edit by hand
|
||||
|
||||
S3method("$",reactivevalues)
|
||||
S3method("$",shinyoutput)
|
||||
S3method("$<-",reactivevalues)
|
||||
@@ -11,25 +13,24 @@ S3method("[[",shinyoutput)
|
||||
S3method("[[<-",reactivevalues)
|
||||
S3method("[[<-",shinyoutput)
|
||||
S3method("names<-",reactivevalues)
|
||||
S3method(as.character,shiny.tag)
|
||||
S3method(as.character,shiny.tag.list)
|
||||
S3method(as.list,reactivevalues)
|
||||
S3method(format,html)
|
||||
S3method(format,shiny.tag)
|
||||
S3method(format,shiny.tag.list)
|
||||
S3method(as.shiny.appobj,character)
|
||||
S3method(as.shiny.appobj,list)
|
||||
S3method(as.shiny.appobj,shiny.appobj)
|
||||
S3method(as.tags,shiny.appobj)
|
||||
S3method(as.tags,shiny.render.function)
|
||||
S3method(names,reactivevalues)
|
||||
S3method(parseShinyInput,default)
|
||||
S3method(parseShinyInput,shinyDate)
|
||||
S3method(parseShinyInput,shinyMatrix)
|
||||
S3method(print,html)
|
||||
S3method(print,reactive)
|
||||
S3method(print,shiny.tag)
|
||||
S3method(print,shiny.tag.list)
|
||||
S3method(print,shiny.appobj)
|
||||
S3method(str,reactivevalues)
|
||||
export(HTML)
|
||||
export(a)
|
||||
export(absolutePanel)
|
||||
export(actionButton)
|
||||
export(actionLink)
|
||||
export(addResourcePath)
|
||||
export(animationOptions)
|
||||
export(as.shiny.appobj)
|
||||
export(basicPage)
|
||||
export(bootstrapPage)
|
||||
export(br)
|
||||
@@ -49,9 +50,12 @@ export(em)
|
||||
export(exprToFunction)
|
||||
export(fileInput)
|
||||
export(fixedPage)
|
||||
export(fixedPanel)
|
||||
export(fixedRow)
|
||||
export(flowLayout)
|
||||
export(fluidPage)
|
||||
export(fluidRow)
|
||||
export(getDefaultReactiveDomain)
|
||||
export(h1)
|
||||
export(h2)
|
||||
export(h3)
|
||||
@@ -70,23 +74,33 @@ export(includeHTML)
|
||||
export(includeMarkdown)
|
||||
export(includeScript)
|
||||
export(includeText)
|
||||
export(inputPanel)
|
||||
export(installExprFunction)
|
||||
export(invalidateLater)
|
||||
export(is.reactive)
|
||||
export(is.reactivevalues)
|
||||
export(is.singleton)
|
||||
export(isolate)
|
||||
export(knit_print.html)
|
||||
export(knit_print.shiny.appobj)
|
||||
export(knit_print.shiny.render.function)
|
||||
export(knit_print.shiny.tag)
|
||||
export(knit_print.shiny.tag.list)
|
||||
export(mainPanel)
|
||||
export(makeReactiveBinding)
|
||||
export(markRenderFunction)
|
||||
export(maskReactiveContext)
|
||||
export(navbarMenu)
|
||||
export(navbarPage)
|
||||
export(navlistPanel)
|
||||
export(need)
|
||||
export(numericInput)
|
||||
export(observe)
|
||||
export(onReactiveDomainEnded)
|
||||
export(outputOptions)
|
||||
export(p)
|
||||
export(pageWithSidebar)
|
||||
export(parseQueryString)
|
||||
export(parseShinyInput)
|
||||
export(plotOutput)
|
||||
export(plotPNG)
|
||||
export(pre)
|
||||
@@ -102,6 +116,8 @@ export(reactiveTimer)
|
||||
export(reactiveUI)
|
||||
export(reactiveValues)
|
||||
export(reactiveValuesToList)
|
||||
export(registerInputHandler)
|
||||
export(removeInputHandler)
|
||||
export(renderDataTable)
|
||||
export(renderImage)
|
||||
export(renderPlot)
|
||||
@@ -116,6 +132,10 @@ export(runGist)
|
||||
export(runGitHub)
|
||||
export(runUrl)
|
||||
export(selectInput)
|
||||
export(selectizeInput)
|
||||
export(serverInfo)
|
||||
export(shinyApp)
|
||||
export(shinyAppDir)
|
||||
export(shinyServer)
|
||||
export(shinyUI)
|
||||
export(showReactLog)
|
||||
@@ -124,6 +144,7 @@ export(sidebarPanel)
|
||||
export(singleton)
|
||||
export(sliderInput)
|
||||
export(span)
|
||||
export(splitLayout)
|
||||
export(stopApp)
|
||||
export(strong)
|
||||
export(submitButton)
|
||||
@@ -131,6 +152,7 @@ export(tabPanel)
|
||||
export(tableOutput)
|
||||
export(tabsetPanel)
|
||||
export(tag)
|
||||
export(tagAppendAttributes)
|
||||
export(tagAppendChild)
|
||||
export(tagAppendChildren)
|
||||
export(tagList)
|
||||
@@ -147,17 +169,22 @@ export(updateDateRangeInput)
|
||||
export(updateNumericInput)
|
||||
export(updateRadioButtons)
|
||||
export(updateSelectInput)
|
||||
export(updateSelectizeInput)
|
||||
export(updateSliderInput)
|
||||
export(updateTabsetPanel)
|
||||
export(updateTextInput)
|
||||
export(validate)
|
||||
export(validateCssUnit)
|
||||
export(verbatimTextOutput)
|
||||
export(verticalLayout)
|
||||
export(wellPanel)
|
||||
export(withMathJax)
|
||||
export(withReactiveDomain)
|
||||
export(withTags)
|
||||
import(RJSONIO)
|
||||
import(caTools)
|
||||
import(digest)
|
||||
import(htmltools)
|
||||
import(httpuv)
|
||||
import(methods)
|
||||
import(xtable)
|
||||
importFrom(RJSONIO,fromJSON)
|
||||
|
||||
205
NEWS
@@ -1,4 +1,107 @@
|
||||
shiny 0.8.0.99
|
||||
shiny 0.10.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Added Unicode support for Windows. Shiny apps running on Windows must use the
|
||||
UTF-8 encoding for ui.R and server.R (also the optional global.R) if they
|
||||
contain non-ASCII characters. See this article for details and examples:
|
||||
http://shiny.rstudio.com/gallery/unicode-characters.html (#516)
|
||||
|
||||
* `runGitHub()` also allows the 'username/repo' syntax now, which is equivalent
|
||||
to `runGitHub('repo', 'username')`. (#427)
|
||||
|
||||
* `navbarPage()` now accepts a `windowTitle` parameter to set the web browser
|
||||
page title to something other than the title displayed in the navbar.
|
||||
|
||||
* Added an `inline` argument to `textOutput()`, `imageOutput()`, `plotOutput()`,
|
||||
and `htmlOutput()`. When `inline = TRUE`, these outputs will be put in
|
||||
`span()` instead of the default `div()`. This occurs automatically when these
|
||||
outputs are created via the inline expressions (e.g. `r textOutput(expr)`) in
|
||||
R Markdown documents. See an R Markdown example at
|
||||
http://shiny.rstudio.com/gallery/inline-output.html (#512)
|
||||
|
||||
* Added support for option groups in the select/selectize inputs. When the
|
||||
`choices` argument for `selectInput()`/`selectizeInput()` is a list of
|
||||
sub-lists and any sub-list is of length greater than 1, the HTML tag
|
||||
`<optgroup>` will be used. See an example at
|
||||
http://shiny.rstudio.com/gallery/option-groups-for-selectize-input.html (#542)
|
||||
|
||||
shiny 0.10.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* BREAKING CHANGE: By default, observers now terminate themselves if they were
|
||||
created during a session and that session ends. See ?domains for more details.
|
||||
|
||||
* Shiny can now be used in R Markdown v2 documents, to create "Shiny Docs":
|
||||
reports and presentations that combine narrative, statically computed output,
|
||||
and fully dynamic inputs and outputs. For more info, including examples, see
|
||||
http://rmarkdown.rstudio.com/authoring_shiny.html.
|
||||
|
||||
* The `session` object that can be passed into a server function (e.g.
|
||||
shinyServer(function(input, output, session) {...})) is now documented: see
|
||||
`?session`.
|
||||
|
||||
* Most inputs can now accept `NULL` label values to omit the label altogether.
|
||||
|
||||
* New `actionLink` input control; like `actionButton`, but with the appearance
|
||||
of a normal link.
|
||||
|
||||
* `renderPlot` now calls `print` on its result if it's visible (i.e. no more
|
||||
explicit `print()` required for ggplot2).
|
||||
|
||||
* Introduced Shiny app objects (see `?shinyApp`). These essentially replace the
|
||||
little-advertised ability for `runApp` to take a `list(ui=..., server=...)`
|
||||
as the first argument instead of a directory (though that ability remains for
|
||||
backward compatibility). Unlike those lists, Shiny app objects are tagged with
|
||||
class `shiny.appobj` so they can be run simply by printing them.
|
||||
|
||||
* Added `maskReactiveContext` function. It blocks the current reactive context,
|
||||
to evaluate expressions that shouldn't use reactive sources directly. (This
|
||||
should not be commonly needed.)
|
||||
|
||||
* Added `flowLayout`, `splitLayout`, and `inputPanel` functions for putting UI
|
||||
elements side by side. `flowLayout` lays out its children in a left-to-right,
|
||||
top-to-bottom arrangement. `splitLayout` evenly divides its horizontal space
|
||||
among its children (or unevenly divides if `cellWidths` argument is provided).
|
||||
`inputPanel` is like `flowPanel`, but with a light grey background, and is
|
||||
intended to be used to encapsulate small input controls wherever vertical
|
||||
space is at a premium.
|
||||
|
||||
* Added `serverInfo` to obtain info about the Shiny Server if the app is served
|
||||
through it.
|
||||
|
||||
* Added an `inline` argument (TRUE/FALSE) in `checkboxGroupInput()` and
|
||||
`radioButtons()` to allow the horizontal layout (inline = TRUE) of checkboxes
|
||||
or radio buttons. (Thanks, @saurfang, #481)
|
||||
|
||||
* `sliderInput` and `selectizeInput`/`selectInput` now use a standard horizontal
|
||||
size instead of filling up all available horizontal space. Pass `width="100%"`
|
||||
explicitly for the old behavior.
|
||||
|
||||
* Added the `updateSelectizeInput()` function to make it possible to process
|
||||
searching on the server side (i.e. using R), which can be much faster than the
|
||||
client side processing (i.e. using HTML and JavaScript). See the article at
|
||||
http://shiny.rstudio.com/articles/selectize.html for a detailed introduction.
|
||||
|
||||
* Fixed a bug of renderDataTable() when the data object only has 1 row and 1
|
||||
column. (Thanks, ZJ Dai, #429)
|
||||
|
||||
* `renderPrint` gained a new argument 'width' to control the width of the text
|
||||
output, e.g. renderPrint({mtcars}, width = 40).
|
||||
|
||||
* Fixed #220: the zip file for a directory created by some programs may not have
|
||||
the directory name as its first entry, in which case runUrl() can fail. (#220)
|
||||
|
||||
* `runGitHub()` can also take a value of the form "username/repo" in its first
|
||||
argument, e.g. both runGitHub("shiny_example", "rstudio") and
|
||||
runGitHub("rstudio/shiny_example") are valid ways to run the GitHub repo.
|
||||
|
||||
shiny 0.9.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fixed warning 'Error in Context$new : could not find function "loadMethod"'
|
||||
that was happening to dependent packages on "R CMD check".
|
||||
|
||||
shiny 0.9.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* BREAKING CHANGE: Added a `host` parameter to runApp() and runExample(),
|
||||
@@ -9,7 +112,21 @@ shiny 0.8.0.99
|
||||
(or the IP address of one of your network interfaces, if you care to be
|
||||
explicit about it).
|
||||
|
||||
* Upgraded to Bootstrap 2.3.2 and jQuery 1.10.2.
|
||||
* Added a new function `selectizeInput()` to use the JavaScript library
|
||||
selectize.js (https://github.com/brianreavis/selectize.js), which extends
|
||||
the basic select input in many aspects.
|
||||
|
||||
* The `selectInput()` function also gained a new argument `selectize = TRUE`
|
||||
to makes use of selectize.js by default. If you want to revert back to the
|
||||
original select input, you have to call selectInput(..., selectize = FALSE).
|
||||
|
||||
* Added Showcase mode, which displays the R code for an app right in the app
|
||||
itself. You can invoke Showcase mode by passing `display.mode="showcase"`
|
||||
to the `runApp()` function. Or, if an app is designed to run in Showcase
|
||||
mode by default, add a DESCRIPTION file in the app dir with Title, Author,
|
||||
and License fields; with "Type: Shiny"; and with "DisplayMode: Showcase".
|
||||
|
||||
* Upgraded to Bootstrap 2.3.2 and jQuery 1.11.0.
|
||||
|
||||
* Make `tags$head()` and `singleton()` behave correctly when used with
|
||||
`renderUI()` and `uiOutput()`. Previously, "hoisting content to the head"
|
||||
@@ -19,7 +136,7 @@ shiny 0.8.0.99
|
||||
* Files are now sourced with the `keep.source` option, to help with debugging
|
||||
and profiling.
|
||||
|
||||
* Support user-defined input parsers for data coming in from JavaScript using
|
||||
* Support user-defined input parsers for data coming in from JavaScript using
|
||||
the parseShinyInput method.
|
||||
|
||||
* Fixed the bug #299: renderDataTable() can deal with 0-row data frames now.
|
||||
@@ -31,32 +148,102 @@ shiny 0.8.0.99
|
||||
* Added `navlistPanel()` function to create layouts with a a bootstrap
|
||||
navlist on the left and tabPanels on the right
|
||||
|
||||
* Added `type` parameter to `tabsetPanel()` to enable the use of pill
|
||||
* Added `type` parameter to `tabsetPanel()` to enable the use of pill
|
||||
style tabs in addition to the standard ones.
|
||||
|
||||
* Added `position` paramter to `tabsetPanel()` to enable positioning of tabs
|
||||
above, below, left, or right of tab content.
|
||||
|
||||
* Added `fluidPage()` and `fixedPage()` functions as well as related row and
|
||||
column layout functions for creating arbitrary bootstrap grid layouts.
|
||||
column layout functions for creating arbitrary bootstrap grid layouts.
|
||||
|
||||
* Added `hr()` builder function for creating horizontal rules.
|
||||
|
||||
* Automatically concatenate duplicate attributes in tag definitions
|
||||
|
||||
* Added `responsive` parameter to page building functions for opting-out of
|
||||
bootstrap responsive css.
|
||||
bootstrap responsive css.
|
||||
|
||||
* Added `theme` parameter to page building functions for specifying alternate
|
||||
bootstrap css styles.
|
||||
bootstrap css styles.
|
||||
|
||||
* Added `icon()` function for embedding icons from the
|
||||
[http://fontawesome.io/](font awesome) icon library
|
||||
* Added `icon()` function for embedding icons from the
|
||||
[font awesome](http://fontawesome.io/) icon library
|
||||
|
||||
* Added `makeReactiveBinding` function to turn a "regular" variable into a
|
||||
reactive one (i.e. reading the variable makes the current reactive context
|
||||
dependent on it, and setting the variable is a source of reactivity).
|
||||
|
||||
* Added a function `withMathJax()` to include the MathJax library in an app.
|
||||
|
||||
* The argument `selected` in checkboxGroupInput(), selectInput(), and
|
||||
radioButtons() refers to the value(s) instead of the name(s) of the
|
||||
argument `choices` now. For example, the value of the `selected` argument
|
||||
in selectInput(..., choices = c('Label 1' = 'x1', 'Label 2' = 'x2'),
|
||||
selected = 'Label 2') must be updated to 'x2', although names/labels will
|
||||
be automatically converted to values internally for backward
|
||||
compatibility. The same change applies to updateCheckboxGroupInput(),
|
||||
updateSelectInput(), and updateRadioButtons() as well. (#340)
|
||||
|
||||
* Now it is possible to only update the value of a checkbox group, select input,
|
||||
or radio buttons using the `selected` argument without providing the
|
||||
`choices` argument in updateCheckboxGroupInput(), updateSelectInput(), and
|
||||
updateRadioButtons(), respectively. (#340)
|
||||
|
||||
* Added `absolutePanel` and `fixedPanel` functions for creating absolute-
|
||||
and fixed-position panels. They can be easily made user-draggable by
|
||||
specifying `draggable = TRUE`.
|
||||
|
||||
* For the `options` argument of the function `renderDataTable()`, we can
|
||||
pass literal JavaScript code to the DataTables library via `I()`. This
|
||||
makes it possible to use any JavaScript object in the options, e.g. a
|
||||
JavaScript function (which is not supported in JSON). See
|
||||
`?renderDataTable` for details and examples.
|
||||
|
||||
* DataTables also works under IE8 now.
|
||||
|
||||
* Fixed a bug in DataTables pagination when searching is turned on, which
|
||||
caused failures for matrices as well as empty rows when displaying data
|
||||
frames using renderDataTable().
|
||||
|
||||
* The `options` argument in `renderDataTable()` can also take a function
|
||||
that returns a list. This makes it possible to use reactive values in the
|
||||
options. (#392)
|
||||
|
||||
* `renderDataTable()` respects more DataTables options now: (1) either
|
||||
bPaginate = FALSE or iDisplayLength = -1 will disable pagination (i.e. all
|
||||
rows are returned from the data); besides, this means we can also use -1
|
||||
in the length menu, e.g. aLengthMenu = list(c(10, 30, -1), list(10, 30,
|
||||
'All')); (2) we can disable searching for individual columns through the
|
||||
bSearchable option, e.g. aoColumns = list(list(bSearchable = FALSE),
|
||||
list(bSearchable = TRUE),...) (the search box for the first column is
|
||||
hidden); (3) we can turn off searching entirely (for both global searching
|
||||
and individual columns) using the option bFilter = FALSE.
|
||||
|
||||
* Added an argument `callback` in `renderDataTable()` so that a custom
|
||||
JavaScript function can be applied to the DataTable object. This makes it
|
||||
much easier to use DataTables plug-ins.
|
||||
|
||||
* For numeric columns in a DataTable, the search boxes support lower and
|
||||
upper bounds now: a search query of the form "lower,upper" (without
|
||||
quotes) indicates the limits [lower, upper]. For a column X, this means
|
||||
the rows corresponding to X >= lower & X <= upper are returned. If we omit
|
||||
either the lower limit or the upper limit, only the other limit will be
|
||||
used, e.g. ",upper" means X <= upper.
|
||||
|
||||
* `updateNumericInput(value)` tries to preserve numeric precision by avoiding
|
||||
scientific notation when possible, e.g. 102145 is no longer rounded to
|
||||
1.0214e+05 = 102140. (Thanks, Martin Loos. #401)
|
||||
|
||||
* `sliderInput()` no longer treats a label wrapped in HTML() as plain text,
|
||||
e.g. the label in sliderInput(..., label = HTML('<em>A Label</em>')) will
|
||||
not be escaped any more. (#119)
|
||||
|
||||
* Fixed #306: the trailing slash in a path could fail `addResourcePath()`
|
||||
under Windows. (Thanks, ZJ Dai)
|
||||
|
||||
* Dots are now legal characters for inputId/outputId. (Thanks, Kevin
|
||||
Lindquist. #358)
|
||||
|
||||
shiny 0.8.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
308
R/app.R
Normal file
@@ -0,0 +1,308 @@
|
||||
# TODO: Subapp global.R
|
||||
|
||||
#' Create a Shiny app object
|
||||
#'
|
||||
#' These functions create Shiny app objects from either an explicit UI/server
|
||||
#' pair (\code{shinyApp}), or by passing the path of a directory that
|
||||
#' contains a Shiny app (\code{shinyAppDir}). You generally shouldn't need to
|
||||
#' use these functions to create/run applications; they are intended for
|
||||
#' interoperability purposes, such as embedding Shiny apps inside a \pkg{knitr}
|
||||
#' document.
|
||||
#'
|
||||
#' @param ui The UI definition of the app (for example, a call to
|
||||
#' \code{fluidPage()} with nested controls)
|
||||
#' @param server A server function
|
||||
#' @param onStart A function that will be called before the app is actually run.
|
||||
#' This is only needed for \code{shinyAppObj}, since in the \code{shinyAppDir}
|
||||
#' case, a \code{global.R} file can be used for this purpose.
|
||||
#' @param options Named options that should be passed to the `runApp` call. You
|
||||
#' can also specify \code{width} and \code{height} parameters which provide a
|
||||
#' hint to the embedding environment about the ideal height/width for the app.
|
||||
#' @param uiPattern A regular expression that will be applied to each \code{GET}
|
||||
#' request to determine whether the \code{ui} should be used to handle the
|
||||
#' request. Note that the entire request path must match the regular
|
||||
#' expression in order for the match to be considered successful.
|
||||
#' @return An object that represents the app. Printing the object will run the
|
||||
#' app.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' numericInput("n", "n", 1),
|
||||
#' plotOutput("plot")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot( plot(head(cars, input$n)) )
|
||||
#' },
|
||||
#' options=list(launch.browser = rstudio::viewer)
|
||||
#' )
|
||||
#'
|
||||
#' shinyAppDir(system.file("examples/01_hello", package="shiny"))
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
shinyApp <- function(ui, server, onStart=NULL, options=list(), uiPattern="/") {
|
||||
# Ensure that the entire path is a match
|
||||
uiPattern <- sprintf("^%s$", uiPattern)
|
||||
|
||||
httpHandler <- function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
if (!isTRUE(grepl(uiPattern, req$PATH_INFO)))
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
on.exit(close(textConn))
|
||||
|
||||
uiValue <- if (is.function(ui)) {
|
||||
if (length(formals(ui)) > 0)
|
||||
ui(req)
|
||||
else
|
||||
ui()
|
||||
} else {
|
||||
ui
|
||||
}
|
||||
if (is.null(uiValue))
|
||||
return(NULL)
|
||||
|
||||
renderPage(uiValue, textConn)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(httpResponse(200, content=enc2utf8(html)))
|
||||
}
|
||||
|
||||
serverFuncSource <- function() {
|
||||
server
|
||||
}
|
||||
|
||||
structure(
|
||||
list(
|
||||
httpHandler = httpHandler,
|
||||
serverFuncSource = serverFuncSource,
|
||||
onStart = onStart,
|
||||
options = options),
|
||||
class = "shiny.appobj"
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param appDir Path to directory that contains a Shiny app (i.e. a server.R
|
||||
#' file and either ui.R or www/index.html)
|
||||
#' @export
|
||||
shinyAppDir <- function(appDir, options=list()) {
|
||||
# Most of the complexity here comes from needing to hot-reload if the .R files
|
||||
# change on disk, or are created, or are removed.
|
||||
|
||||
if (!file.exists(appDir)) {
|
||||
stop("No Shiny application exists at the path \"", appDir, "\"")
|
||||
}
|
||||
|
||||
# In case it's a relative path, convert to absolute (so we're not adversely
|
||||
# affected by future changes to the path)
|
||||
appDir <- normalizePath(appDir, mustWork = TRUE)
|
||||
|
||||
# uiHandlerSource is a function that returns an HTTP handler for serving up
|
||||
# ui.R as a webpage. The "cachedFuncWithFile" call makes sure that the closure
|
||||
# we're creating here only gets executed when ui.R's contents change.
|
||||
uiHandlerSource <- cachedFuncWithFile(appDir, "ui.R", case.sensitive = FALSE,
|
||||
function(uiR) {
|
||||
if (file.exists(uiR)) {
|
||||
# If ui.R contains a call to shinyUI (which sets .globals$ui), use that.
|
||||
# If not, then take the last expression that's returned from ui.R.
|
||||
.globals$ui <- NULL
|
||||
on.exit(.globals$ui <- NULL, add = FALSE)
|
||||
ui <- sourceUTF8(uiR, local = new.env(parent = globalenv()))$value
|
||||
if (!is.null(.globals$ui)) {
|
||||
ui <- .globals$ui[[1]]
|
||||
}
|
||||
return(uiHttpHandler(ui))
|
||||
} else {
|
||||
return(function(req) NULL)
|
||||
}
|
||||
}
|
||||
)
|
||||
uiHandler <- function(req) {
|
||||
uiHandlerSource()(req)
|
||||
}
|
||||
|
||||
wwwDir <- file.path.ci(appDir, "www")
|
||||
fallbackWWWDir <- system.file("www-dir", package = "shiny")
|
||||
serverSource <- cachedFuncWithFile(appDir, "server.R", case.sensitive = FALSE,
|
||||
function(serverR) {
|
||||
# If server.R contains a call to shinyServer (which sets .globals$server),
|
||||
# use that. If not, then take the last expression that's returned from
|
||||
# server.R.
|
||||
.globals$server <- NULL
|
||||
on.exit(.globals$server <- NULL, add = TRUE)
|
||||
result <- sourceUTF8(serverR, local = new.env(parent = globalenv()))$value
|
||||
if (!is.null(.globals$server)) {
|
||||
result <- .globals$server[[1]]
|
||||
}
|
||||
return(result)
|
||||
}
|
||||
)
|
||||
|
||||
# This function stands in for the server function, and reloads the
|
||||
# real server function as necessary whenever server.R changes
|
||||
serverFuncSource <- function() {
|
||||
serverFunction <- serverSource()
|
||||
if (is.null(serverFunction)) {
|
||||
return(function(input, output) NULL)
|
||||
} else if (is.function(serverFunction)) {
|
||||
# This is what we normally expect; run the server function
|
||||
return(serverFunction)
|
||||
} else {
|
||||
stop("server.R returned an object of unexpected type: ",
|
||||
typeof(serverFunction))
|
||||
}
|
||||
}
|
||||
|
||||
oldwd <- NULL
|
||||
onStart <- function() {
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
if (file.exists(file.path.ci(appDir, "global.R")))
|
||||
sourceUTF8(file.path.ci(appDir, "global.R"))
|
||||
}
|
||||
onEnd <- function() {
|
||||
setwd(oldwd)
|
||||
}
|
||||
|
||||
structure(
|
||||
list(
|
||||
httpHandler = joinHandlers(c(uiHandler, wwwDir, fallbackWWWDir)),
|
||||
serverFuncSource = serverFuncSource,
|
||||
onStart = onStart,
|
||||
onEnd = onEnd,
|
||||
options = options),
|
||||
class = "shiny.appobj"
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param x Object to convert to a Shiny app.
|
||||
#' @export
|
||||
as.shiny.appobj <- function(x) {
|
||||
UseMethod("as.shiny.appobj", x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
as.shiny.appobj.shiny.appobj <- function(x) {
|
||||
x
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
as.shiny.appobj.list <- function(x) {
|
||||
shinyApp(ui = x$ui, server = x$server)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
as.shiny.appobj.character <- function(x) {
|
||||
shinyAppDir(x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param ... Additional parameters to be passed to print.
|
||||
#' @export
|
||||
print.shiny.appobj <- function(x, ...) {
|
||||
opts <- x$options %OR% list()
|
||||
opts <- opts[names(opts) %in%
|
||||
c("port", "launch.browser", "host", "quiet", "display.mode")]
|
||||
|
||||
args <- c(list(x), opts)
|
||||
|
||||
do.call(runApp, args)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @method as.tags shiny.appobj
|
||||
#' @export
|
||||
as.tags.shiny.appobj <- function(x, ...) {
|
||||
# jcheng 06/06/2014: Unfortunate copy/paste between this function and
|
||||
# knit_print.shiny.appobj, but I am trying to make the most conservative
|
||||
# change possible due to upcoming release.
|
||||
opts <- x$options %OR% list()
|
||||
width <- if (is.null(opts$width)) "100%" else opts$width
|
||||
height <- if (is.null(opts$height)) "400" else opts$height
|
||||
|
||||
path <- addSubApp(x)
|
||||
tags$iframe(src=path, width=width, height=height, class="shiny-frame")
|
||||
}
|
||||
|
||||
#' Knitr S3 methods
|
||||
#'
|
||||
#' These S3 methods are necessary to help Shiny applications and UI chunks embed
|
||||
#' themselves in knitr/rmarkdown documents.
|
||||
#'
|
||||
#' @name knitr_methods
|
||||
#' @param x Object to knit_print
|
||||
#' @param ... Additional knit_print arguments
|
||||
NULL
|
||||
|
||||
# If there's an R Markdown runtime option set but it isn't set to Shiny, then
|
||||
# return a warning indicating the runtime is inappropriate for this object.
|
||||
# Returns NULL in all other cases.
|
||||
shiny_rmd_warning <- function() {
|
||||
runtime <- knitr::opts_knit$get("rmarkdown.runtime")
|
||||
if (!is.null(runtime) && runtime != "shiny")
|
||||
# note that the RStudio IDE checks for this specific string to detect Shiny
|
||||
# applications in static document
|
||||
list(structure(
|
||||
"Shiny application in a static R Markdown document",
|
||||
class = "rmd_warning"))
|
||||
else
|
||||
NULL
|
||||
}
|
||||
|
||||
#' @rdname knitr_methods
|
||||
#' @export
|
||||
knit_print.shiny.appobj <- function(x, ...) {
|
||||
opts <- x$options %OR% list()
|
||||
width <- if (is.null(opts$width)) "100%" else opts$width
|
||||
height <- if (is.null(opts$height)) "400" else opts$height
|
||||
|
||||
runtime <- knitr::opts_knit$get("rmarkdown.runtime")
|
||||
if (!is.null(runtime) && runtime != "shiny") {
|
||||
# If not rendering to a Shiny document, create a box exactly the same
|
||||
# dimensions as the Shiny app would have had (so the document continues to
|
||||
# flow as it would have with the app), and display a diagnostic message
|
||||
width <- validateCssUnit(width)
|
||||
height <- validateCssUnit(height)
|
||||
output <- tags$div(
|
||||
style=paste("width:", width, "; height:", height, "; text-align: center;",
|
||||
"box-sizing: border-box;", "-moz-box-sizing: border-box;",
|
||||
"-webkit-box-sizing: border-box;"),
|
||||
class="muted well",
|
||||
"Shiny applications not supported in static R Markdown documents")
|
||||
}
|
||||
else {
|
||||
path <- addSubApp(x)
|
||||
output <- tags$iframe(src=path, width=width, height=height,
|
||||
class="shiny-frame")
|
||||
}
|
||||
|
||||
# If embedded Shiny apps ever have JS/CSS dependencies (like pym.js) we'll
|
||||
# need to grab those and put them in meta, like in knit_print.shiny.tag. But
|
||||
# for now it's not an issue, so just return the HTML and warning.
|
||||
|
||||
knitr::asis_output(htmlPreserve(format(output, indent=FALSE)),
|
||||
meta = shiny_rmd_warning(), cacheable = FALSE)
|
||||
}
|
||||
|
||||
# Let us use a nicer syntax in knitr chunks than literally
|
||||
# calling output$value <- renderFoo(...) and fooOutput().
|
||||
#' @rdname knitr_methods
|
||||
#' @param inline Whether the object is printed inline.
|
||||
#' @export
|
||||
knit_print.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
x <- htmltools::as.tags(x, inline = inline)
|
||||
output <- knitr::knit_print(tagList(x))
|
||||
attr(output, "knit_cacheable") <- FALSE
|
||||
attr(output, "knit_meta") <- append(attr(output, "knit_meta"),
|
||||
shiny_rmd_warning())
|
||||
output
|
||||
}
|
||||
@@ -1,60 +1,60 @@
|
||||
|
||||
#' Create a page with fluid layout
|
||||
#'
|
||||
#' Functions for creating fluid page layouts. A fluid page layout consists of
|
||||
#'
|
||||
#' Functions for creating fluid page layouts. A fluid page layout consists of
|
||||
#' rows which in turn include columns. Rows exist for the purpose of making sure
|
||||
#' their elements appear on the same line (if the browser has adequate width).
|
||||
#' Columns exist for the purpose of defining how much horizontal space within a
|
||||
#' 12-unit wide grid it's elements should occupy. Fluid pages scale their
|
||||
#' their elements appear on the same line (if the browser has adequate width).
|
||||
#' Columns exist for the purpose of defining how much horizontal space within a
|
||||
#' 12-unit wide grid it's elements should occupy. Fluid pages scale their
|
||||
#' components in realtime to fill all available browser width.
|
||||
#'
|
||||
#'
|
||||
#' @param ... Elements to include within the page
|
||||
#' @param title The browser window title (defaults to the host URL of the page).
|
||||
#' Can also be set as a side effect of the \code{\link{titlePanel}} function.
|
||||
#' @param responsive \code{TRUE} to use responsive layout (automatically adapt
|
||||
#' @param responsive \code{TRUE} to use responsive layout (automatically adapt
|
||||
#' and resize page elements based on the size of the viewing device)
|
||||
#' @param theme Alternative Bootstrap stylesheet (normally a css file within the
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' \code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.
|
||||
#'
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the \link{shinyUI} function.
|
||||
#'
|
||||
#'
|
||||
#' @details To create a fluid page use the \code{fluidPage} function and include
|
||||
#' instances of \code{fluidRow} and \code{\link{column}} within it. As an
|
||||
#' alternative to low-level row and column functions you can also use
|
||||
#' instances of \code{fluidRow} and \code{\link{column}} within it. As an
|
||||
#' alternative to low-level row and column functions you can also use
|
||||
#' higher-level layout functions like \code{\link{sidebarLayout}}.
|
||||
#'
|
||||
#' @note See the
|
||||
#'
|
||||
#' @note See the
|
||||
#' \href{https://github.com/rstudio/shiny/wiki/Shiny-Application-Layout-Guide}{
|
||||
#' Shiny-Application-Layout-Guide} for additional details on laying out fluid
|
||||
#' pages.
|
||||
#'
|
||||
#'
|
||||
#' @seealso \code{\link{column}}, \code{\link{sidebarLayout}}
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fluidPage(
|
||||
#'
|
||||
#'
|
||||
#' # Application title
|
||||
#' titlePanel("Hello Shiny!"),
|
||||
#'
|
||||
#'
|
||||
#' sidebarLayout(
|
||||
#'
|
||||
#'
|
||||
#' # Sidebar with a slider input
|
||||
#' sidebarPanel(
|
||||
#' sliderInput("obs",
|
||||
#' "Number of observations:",
|
||||
#' min = 0,
|
||||
#' max = 1000,
|
||||
#' sliderInput("obs",
|
||||
#' "Number of observations:",
|
||||
#' min = 0,
|
||||
#' max = 1000,
|
||||
#' value = 500)
|
||||
#' ),
|
||||
#'
|
||||
#'
|
||||
#' # Show a plot of the generated distribution
|
||||
#' mainPanel(
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#'
|
||||
#' shinyUI(fluidPage(
|
||||
#' title = "Hello Shiny!",
|
||||
#' fluidRow(
|
||||
@@ -66,11 +66,11 @@
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#'
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidPage <- function(..., title = NULL, responsive = TRUE, theme = NULL) {
|
||||
bootstrapPage(div(class = "container-fluid", ...),
|
||||
bootstrapPage(div(class = "container-fluid", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
theme = theme)
|
||||
@@ -84,38 +84,38 @@ fluidRow <- function(...) {
|
||||
}
|
||||
|
||||
#' Create a page with a fixed layout
|
||||
#'
|
||||
#' Functions for creating fixed page layouts. A fixed page layout consists of
|
||||
#'
|
||||
#' Functions for creating fixed page layouts. A fixed page layout consists of
|
||||
#' rows which in turn include columns. Rows exist for the purpose of making sure
|
||||
#' their elements appear on the same line (if the browser has adequate width).
|
||||
#' Columns exist for the purpose of defining how much horizontal space within a
|
||||
#' 12-unit wide grid it's elements should occupy. Fixed pages limit their width
|
||||
#' their elements appear on the same line (if the browser has adequate width).
|
||||
#' Columns exist for the purpose of defining how much horizontal space within a
|
||||
#' 12-unit wide grid it's elements should occupy. Fixed pages limit their width
|
||||
#' to 940 pixels on a typical display, and 724px or 1170px on smaller and larger
|
||||
#' displays respectively.
|
||||
#'
|
||||
#'
|
||||
#' @param ... Elements to include within the container
|
||||
#' @param title The browser window title (defaults to the host URL of the page)
|
||||
#' @param responsive \code{TRUE} to use responsive layout (automatically adapt
|
||||
#' and resize page elements based on the size of the viewing device)
|
||||
#' @param theme Alternative Bootstrap stylesheet (normally a css file within the
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' \code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.
|
||||
#'
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the \link{shinyUI} function.
|
||||
#'
|
||||
#'
|
||||
#' @details To create a fixed page use the \code{fixedPage} function and include
|
||||
#' instances of \code{fixedRow} and \code{\link{column}} within it. Note that
|
||||
#' instances of \code{fixedRow} and \code{\link{column}} within it. Note that
|
||||
#' unlike \code{\link{fluidPage}}, fixed pages cannot make use of higher-level
|
||||
#' layout functions like \code{sidebarLayout}, rather, all layout must be done
|
||||
#' with \code{fixedRow} and \code{column}.
|
||||
#'
|
||||
#' @note See the
|
||||
#' \href{https://github.com/rstudio/shiny/wiki/Shiny-Application-Layout-Guide}{
|
||||
#' Shiny Application Layout Guide} for additional details on laying out fixed
|
||||
#' pages.
|
||||
#'
|
||||
#'
|
||||
#' @note See the
|
||||
#' \href{https://github.com/rstudio/shiny/wiki/Shiny-Application-Layout-Guide}{
|
||||
#' Shiny Application Layout Guide} for additional details on laying out fixed
|
||||
#' pages.
|
||||
#'
|
||||
#' @seealso \code{\link{column}}
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fixedPage(
|
||||
#' title = "Hello, Shiny!",
|
||||
@@ -128,11 +128,11 @@ fluidRow <- function(...) {
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#'
|
||||
#' @rdname fixedPage
|
||||
#' @export
|
||||
fixedPage <- function(..., title = NULL, responsive = TRUE, theme = NULL) {
|
||||
bootstrapPage(div(class = "container", ...),
|
||||
bootstrapPage(div(class = "container", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
theme = theme)
|
||||
@@ -146,32 +146,32 @@ fixedRow <- function(...) {
|
||||
|
||||
|
||||
#' Create a column within a UI definition
|
||||
#'
|
||||
#' Create a column for use within a \code{\link{fluidRow}} or
|
||||
#'
|
||||
#' Create a column for use within a \code{\link{fluidRow}} or
|
||||
#' \code{\link{fixedRow}}
|
||||
#'
|
||||
#'
|
||||
#' @param width The grid width of the column (must be between 1 and 12)
|
||||
#' @param ... Elements to include within the column
|
||||
#' @param offset The number of columns to offset this column from the end of the
|
||||
#' previous column.
|
||||
#'
|
||||
#'
|
||||
#' @return A column that can be included within a
|
||||
#' \code{\link{fluidRow}} or \code{\link{fixedRow}}.
|
||||
#'
|
||||
#'
|
||||
#'
|
||||
#' @seealso \code{\link{fluidRow}}, \code{\link{fixedRow}}.
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' fluidRow(
|
||||
#' fluidRow(
|
||||
#' column(4,
|
||||
#' sliderInput("obs", "Number of observations:",
|
||||
#' min = 1, max = 1000, value = 500)
|
||||
#' sliderInput("obs", "Number of observations:",
|
||||
#' min = 1, max = 1000, value = 500)
|
||||
#' ),
|
||||
#' column(8,
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#'
|
||||
#' fluidRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
@@ -182,10 +182,10 @@ fixedRow <- function(...) {
|
||||
#' )
|
||||
#' @export
|
||||
column <- function(width, ..., offset = 0) {
|
||||
|
||||
|
||||
if (!is.numeric(width) || (width < 1) || (width > 12))
|
||||
stop("column width must be between 1 and 12")
|
||||
|
||||
|
||||
colClass <- paste0("span", width)
|
||||
if (offset > 0)
|
||||
colClass <- paste0(colClass, " offset", offset)
|
||||
@@ -194,81 +194,81 @@ column <- function(width, ..., offset = 0) {
|
||||
|
||||
|
||||
#' Create a panel containing an application title.
|
||||
#'
|
||||
#'
|
||||
#' @param title An application title to display
|
||||
#' @param windowTitle The title that should be displayed by the browser window.
|
||||
#'
|
||||
#' @details Calling this function has the side effect of including a
|
||||
#' \code{title} tag within the head. You can also specify a page title
|
||||
#'
|
||||
#' @details Calling this function has the side effect of including a
|
||||
#' \code{title} tag within the head. You can also specify a page title
|
||||
#' explicitly using the `title` parameter of the top-level page function.
|
||||
#'
|
||||
#'
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' titlePanel("Hello Shiny!")
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
titlePanel <- function(title, windowTitle=title) {
|
||||
titlePanel <- function(title, windowTitle=title) {
|
||||
tagList(
|
||||
tags$head(tags$title(windowTitle)),
|
||||
tags$head(tags$title(windowTitle)),
|
||||
h2(style = "padding: 10px 0px;", title)
|
||||
)
|
||||
}
|
||||
|
||||
#' Layout a sidebar and main area
|
||||
#'
|
||||
#'
|
||||
#' Create a layout with a sidebar and main area. The sidebar is displayed with a
|
||||
#' distinct background color and typically contains input controls. The main
|
||||
#' area occupies 2/3 of the horizontal width and typically contains outputs.
|
||||
#'
|
||||
#'
|
||||
#' @param sidebarPanel The \link{sidebarPanel} containing input controls
|
||||
#' @param mainPanel The \link{mainPanel} containing outputs
|
||||
#' @param position The position of the sidebar relative to the main area ("left"
|
||||
#' or "right")
|
||||
#' @param fluid \code{TRUE} to use fluid layout; \code{FALSE} to use fixed
|
||||
#' layout.
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' # Define UI
|
||||
#' shinyUI(fluidPage(
|
||||
#'
|
||||
#'
|
||||
#' # Application title
|
||||
#' titlePanel("Hello Shiny!"),
|
||||
#'
|
||||
#'
|
||||
#' sidebarLayout(
|
||||
#'
|
||||
#'
|
||||
#' # Sidebar with a slider input
|
||||
#' sidebarPanel(
|
||||
#' sliderInput("obs",
|
||||
#' "Number of observations:",
|
||||
#' min = 0,
|
||||
#' max = 1000,
|
||||
#' sliderInput("obs",
|
||||
#' "Number of observations:",
|
||||
#' min = 0,
|
||||
#' max = 1000,
|
||||
#' value = 500)
|
||||
#' ),
|
||||
#'
|
||||
#'
|
||||
#' # Show a plot of the generated distribution
|
||||
#' mainPanel(
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
sidebarLayout <- function(sidebarPanel,
|
||||
mainPanel,
|
||||
position = c("left", "right"),
|
||||
fluid = TRUE) {
|
||||
|
||||
# determine the order
|
||||
|
||||
# determine the order
|
||||
position <- match.arg(position)
|
||||
if (position == "left") {
|
||||
firstPanel <- sidebarPanel
|
||||
secondPanel <- mainPanel
|
||||
}
|
||||
}
|
||||
else if (position == "right") {
|
||||
firstPanel <- mainPanel
|
||||
secondPanel <- sidebarPanel
|
||||
}
|
||||
|
||||
|
||||
# return as as row
|
||||
if (fluid)
|
||||
fluidRow(firstPanel, secondPanel)
|
||||
@@ -276,17 +276,17 @@ sidebarLayout <- function(sidebarPanel,
|
||||
fixedRow(firstPanel, secondPanel)
|
||||
}
|
||||
|
||||
#' Layout UI elements vertically
|
||||
#'
|
||||
#' Create a container that includes one or more rows of content (each element
|
||||
#' Lay out UI elements vertically
|
||||
#'
|
||||
#' Create a container that includes one or more rows of content (each element
|
||||
#' passed to the container will appear on it's own line in the UI)
|
||||
#'
|
||||
#'
|
||||
#' @param ... Elements to include within the container
|
||||
#' @param fluid \code{TRUE} to use fluid layout; \code{FALSE} to use fixed
|
||||
#' layout.
|
||||
#'
|
||||
#' @seealso \code{\link{fluidPage}}
|
||||
#'
|
||||
#'
|
||||
#' @seealso \code{\link{fluidPage}}, \code{\link{flowLayout}}
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fluidPage(
|
||||
#' verticalLayout(
|
||||
@@ -306,5 +306,116 @@ verticalLayout <- function(..., fluid = TRUE) {
|
||||
})
|
||||
}
|
||||
|
||||
#' Flow layout
|
||||
#'
|
||||
#' Lays out elements in a left-to-right, top-to-bottom arrangement. The elements
|
||||
#' on a given row will be top-aligned with each other. This layout will not work
|
||||
#' well with elements that have a percentage-based width (e.g. `plotOutput` at
|
||||
#' its default setting of `width = "100%"`).
|
||||
#'
|
||||
#' @param ... Unnamed arguments will become child elements of the layout. Named
|
||||
#' arguments will become HTML attributes on the outermost tag.
|
||||
#' @param cellArgs Any additional attributes that should be used for each cell
|
||||
#' of the layout.
|
||||
#'
|
||||
#' @seealso \code{\link{verticalLayout}}
|
||||
#'
|
||||
#' @examples
|
||||
#' flowLayout(
|
||||
#' numericInput("rows", "How many rows?", 5),
|
||||
#' selectInput("letter", "Which letter?", LETTERS),
|
||||
#' sliderInput("value", "What value?", 0, 100, 50)
|
||||
#' )
|
||||
#' @export
|
||||
flowLayout <- function(..., cellArgs = list()) {
|
||||
|
||||
children <- list(...)
|
||||
childIdx <- !nzchar(names(children) %OR% character(length(children)))
|
||||
attribs <- children[!childIdx]
|
||||
children <- children[childIdx]
|
||||
|
||||
do.call(tags$div, c(list(class = "shiny-flow-layout"),
|
||||
attribs,
|
||||
lapply(children, function(x) {
|
||||
do.call(tags$div, c(cellArgs, list(x)))
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
#' Input panel
|
||||
#'
|
||||
#' A \code{\link{flowLayout}} with a grey border and light grey background,
|
||||
#' suitable for wrapping inputs.
|
||||
#'
|
||||
#' @param ... Input controls or other HTML elements.
|
||||
#'
|
||||
#' @export
|
||||
inputPanel <- function(...) {
|
||||
div(class = "shiny-input-panel",
|
||||
flowLayout(...)
|
||||
)
|
||||
}
|
||||
|
||||
#' Split layout
|
||||
#'
|
||||
#' Lays out elements horizontally, dividing the available horizontal space into
|
||||
#' equal parts (by default).
|
||||
#'
|
||||
#' @param ... Unnamed arguments will become child elements of the layout. Named
|
||||
#' arguments will become HTML attributes on the outermost tag.
|
||||
#' @param cellWidths Character or numeric vector indicating the widths of the
|
||||
#' individual cells. Recycling will be used if needed. Character values will
|
||||
#' be interpreted as CSS lengths (see \code{\link{validateCssUnit}}), numeric
|
||||
#' values as pixels.
|
||||
#' @param cellArgs Any additional attributes that should be used for each cell
|
||||
#' of the layout.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Equal sizing
|
||||
#' splitLayout(
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2")
|
||||
#' )
|
||||
#'
|
||||
#' # Custom widths
|
||||
#' splitLayout(cellWidths = c("25%", "75%"),
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2")
|
||||
#' )
|
||||
#'
|
||||
#' # All cells at 300 pixels wide, with cell padding
|
||||
#' # and a border around everything
|
||||
#' splitLayout(
|
||||
#' style = "border: 1px solid silver;",
|
||||
#' cellWidths = 300,
|
||||
#' cellArgs = list(style = "padding: 6px"),
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2"),
|
||||
#' plotOutput("plot3")
|
||||
#' )
|
||||
#' @export
|
||||
splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) {
|
||||
|
||||
children <- list(...)
|
||||
childIdx <- !nzchar(names(children) %OR% character(length(children)))
|
||||
attribs <- children[!childIdx]
|
||||
children <- children[childIdx]
|
||||
count <- length(children)
|
||||
|
||||
if (length(cellWidths) == 0 || is.na(cellWidths)) {
|
||||
cellWidths <- sprintf("%.3f%%", 100 / count)
|
||||
}
|
||||
cellWidths <- rep(cellWidths, length.out = count)
|
||||
cellWidths <- sapply(cellWidths, validateCssUnit)
|
||||
|
||||
do.call(tags$div, c(list(class = "shiny-split-layout"),
|
||||
attribs,
|
||||
mapply(children, cellWidths, FUN = function(x, w) {
|
||||
do.call(tags$div, c(
|
||||
list(style = sprintf("width: %s;", w)),
|
||||
cellArgs,
|
||||
list(x)
|
||||
))
|
||||
}, SIMPLIFY = FALSE)
|
||||
))
|
||||
}
|
||||
|
||||
1244
R/bootstrap.R
22
R/cache.R
@@ -1,5 +1,5 @@
|
||||
# A context object for tracking a cache that needs to be dirtied when a set of
|
||||
# files changes on disk. Each time the cache is dirtied, the set of files is
|
||||
# A context object for tracking a cache that needs to be dirtied when a set of
|
||||
# files changes on disk. Each time the cache is dirtied, the set of files is
|
||||
# cleared. Therefore, the set of files needs to be re-built each time the cached
|
||||
# code executes. This approach allows for dynamic dependency graphs.
|
||||
CacheContext <- setRefClass(
|
||||
@@ -17,13 +17,13 @@ CacheContext <- setRefClass(
|
||||
addDependencyFile = function(file) {
|
||||
if (.dirty)
|
||||
return()
|
||||
|
||||
|
||||
file <- normalizePath(file)
|
||||
|
||||
mtime <- file.info(file)$mtime
|
||||
.tests <<- c(.tests, function() {
|
||||
newMtime <- try(file.info(file)$mtime, silent=TRUE)
|
||||
if (is(newMtime, 'try-error'))
|
||||
if (inherits(newMtime, 'try-error'))
|
||||
return(TRUE)
|
||||
return(!identical(mtime, newMtime))
|
||||
})
|
||||
@@ -37,14 +37,14 @@ CacheContext <- setRefClass(
|
||||
isDirty = function() {
|
||||
if (.dirty)
|
||||
return(TRUE)
|
||||
|
||||
|
||||
for (test in .tests) {
|
||||
if (test()) {
|
||||
forceDirty()
|
||||
return(TRUE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return(FALSE)
|
||||
},
|
||||
reset = function() {
|
||||
@@ -55,7 +55,7 @@ CacheContext <- setRefClass(
|
||||
oldCC <- .currentCacheContext$cc
|
||||
.currentCacheContext$cc <- .self
|
||||
on.exit(.currentCacheContext$cc <- oldCC)
|
||||
|
||||
|
||||
return(func())
|
||||
}
|
||||
)
|
||||
@@ -63,18 +63,18 @@ CacheContext <- setRefClass(
|
||||
|
||||
.currentCacheContext <- new.env()
|
||||
|
||||
# Indicates to Shiny that the given file path is part of the dependency graph
|
||||
# Indicates to Shiny that the given file path is part of the dependency graph
|
||||
# for whatever is currently executing (so far, only ui.R). By default, ui.R only
|
||||
# gets re-executed when it is detected to have changed; this function allows the
|
||||
# caller to indicate that it should also re-execute if the given file changes.
|
||||
#
|
||||
#
|
||||
# If NULL or NA is given as the argument, then ui.R will re-execute next time.
|
||||
dependsOnFile <- function(filepath) {
|
||||
if (is.null(.currentCacheContext$cc))
|
||||
return()
|
||||
|
||||
|
||||
if (is.null(filepath) || is.na(filepath))
|
||||
.currentCacheContext$cc$forceDirty()
|
||||
else
|
||||
.currentCacheContext$cc$addDependencyFile(filepath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# For HTML5-capable browsers, file uploads happen through a series of requests.
|
||||
#
|
||||
#
|
||||
# 1. Client tells server that one or more files are about to be uploaded; the
|
||||
# server responds with a "job ID" that the client should use for the rest of
|
||||
# the upload.
|
||||
#
|
||||
#
|
||||
# 2. For each file (sequentially):
|
||||
# a. Client tells server the name, size, and type of the file.
|
||||
# b. Client sends server a small-ish blob of data.
|
||||
# c. Repeat 2b until the entire file has been uploaded.
|
||||
# d. Client tells server that the current file is done.
|
||||
#
|
||||
#
|
||||
# 3. Repeat 2 until all files have been uploaded.
|
||||
#
|
||||
#
|
||||
# 4. Client tells server that all files have been uploaded, along with the
|
||||
# input ID that this data should be associated with.
|
||||
#
|
||||
#
|
||||
# Unfortunately this approach will not work for browsers that don't support
|
||||
# HTML5 File API, but the fallback approach we would like to use (multipart
|
||||
# form upload, i.e. traditional HTTP POST-based file upload) doesn't work with
|
||||
@@ -54,12 +54,12 @@ FileUploadOperation <- setRefClass(
|
||||
filename <- file.path(.dir, as.character(length(.files$name)))
|
||||
row <- data.frame(name=file$name, size=file$size, type=file$type,
|
||||
datapath=filename, stringsAsFactors=FALSE)
|
||||
|
||||
|
||||
if (length(.files$name) == 0)
|
||||
.files <<- row
|
||||
else
|
||||
.files <<- rbind(.files, row)
|
||||
|
||||
|
||||
.currentFileData <<- file(filename, open='wb')
|
||||
},
|
||||
fileChunk = function(rawdata) {
|
||||
@@ -77,6 +77,7 @@ FileUploadOperation <- setRefClass(
|
||||
)
|
||||
)
|
||||
|
||||
#' @include map.R
|
||||
FileUploadContext <- setRefClass(
|
||||
'FileUploadContext',
|
||||
fields = list(
|
||||
@@ -89,11 +90,11 @@ FileUploadContext <- setRefClass(
|
||||
},
|
||||
createUploadOperation = function(fileInfos) {
|
||||
while (TRUE) {
|
||||
id <- paste(as.raw(runif(12, min=0, max=0xFF)), collapse='')
|
||||
id <- paste(as.raw(p_runif(12, min=0, max=0xFF)), collapse='')
|
||||
dir <- file.path(.basedir, id)
|
||||
if (!dir.create(dir))
|
||||
next
|
||||
|
||||
|
||||
op <- FileUploadOperation$new(.self, id, dir, fileInfos)
|
||||
.operations$set(id, op)
|
||||
return(id)
|
||||
|
||||
9
R/globals.R
Normal file
@@ -0,0 +1,9 @@
|
||||
# A scope where we can put mutable global state
|
||||
.globals <- new.env(parent = emptyenv())
|
||||
|
||||
.onLoad <- function(libname, pkgname) {
|
||||
# R's lazy-loading package scheme causes the private seed to be cached in the
|
||||
# package itself, making our PRNG completely deterministic. This line resets
|
||||
# the private seed during load.
|
||||
withPrivateSeed(reinitializeSeed())
|
||||
}
|
||||
64
R/graph.R
@@ -3,38 +3,38 @@ writeReactLog <- function(file=stdout()) {
|
||||
}
|
||||
|
||||
#' Reactive Log Visualizer
|
||||
#'
|
||||
#' Provides an interactive browser-based tool for visualizing reactive
|
||||
#'
|
||||
#' Provides an interactive browser-based tool for visualizing reactive
|
||||
#' dependencies and execution in your application.
|
||||
#'
|
||||
#' To use the reactive log visualizer, start with a fresh R session and
|
||||
#'
|
||||
#' To use the reactive log visualizer, start with a fresh R session and
|
||||
#' run the command \code{options(shiny.reactlog=TRUE)}; then launch your
|
||||
#' application in the usual way (e.g. using \code{\link{runApp}}). At
|
||||
#' any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your
|
||||
#' application in the usual way (e.g. using \code{\link{runApp}}). At
|
||||
#' any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your
|
||||
#' web browser to launch the reactive log visualization.
|
||||
#'
|
||||
#' The reactive log visualization only includes reactive activity up
|
||||
#' until the time the report was loaded. If you want to see more recent
|
||||
#'
|
||||
#' The reactive log visualization only includes reactive activity up
|
||||
#' until the time the report was loaded. If you want to see more recent
|
||||
#' activity, refresh the browser.
|
||||
#'
|
||||
#' Note that Shiny does not distinguish between reactive dependencies
|
||||
#'
|
||||
#' Note that Shiny does not distinguish between reactive dependencies
|
||||
#' that "belong" to one Shiny user session versus another, so the
|
||||
#' visualization will include all reactive activity that has taken place
|
||||
#' in the process, not just for a particular application or session.
|
||||
#'
|
||||
#' As an alternative to pressing Ctrl/Command+F3--for example, if you
|
||||
#' are using reactives outside of the context of a Shiny
|
||||
#'
|
||||
#' As an alternative to pressing Ctrl/Command+F3--for example, if you
|
||||
#' are using reactives outside of the context of a Shiny
|
||||
#' application--you can run the \code{showReactLog} function, which will
|
||||
#' generate the reactive log visualization as a static HTML file and
|
||||
#' launch it in your default browser. In this case, refreshing your
|
||||
#' browser will not load new activity into the report; you will need to
|
||||
#' generate the reactive log visualization as a static HTML file and
|
||||
#' launch it in your default browser. In this case, refreshing your
|
||||
#' browser will not load new activity into the report; you will need to
|
||||
#' call \code{showReactLog()} explicitly.
|
||||
#'
|
||||
#' For security and performance reasons, do not enable
|
||||
#' \code{shiny.reactlog} in production environments. When the option is
|
||||
#' enabled, it's possible for any user of your app to see at least some
|
||||
#'
|
||||
#' For security and performance reasons, do not enable
|
||||
#' \code{shiny.reactlog} in production environments. When the option is
|
||||
#' enabled, it's possible for any user of your app to see at least some
|
||||
#' of the source code of your reactive expressions and observers.
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
showReactLog <- function() {
|
||||
browseURL(renderReactLog())
|
||||
@@ -54,12 +54,12 @@ renderReactLog <- function() {
|
||||
return(file)
|
||||
}
|
||||
|
||||
.graphAppend <- function(logEntry) {
|
||||
if (isTRUE(getOption('shiny.reactlog', FALSE)))
|
||||
.graphAppend <- function(logEntry, domain = getDefaultReactiveDomain()) {
|
||||
if (isTRUE(getOption('shiny.reactlog')))
|
||||
.graphEnv$log <- c(.graphEnv$log, list(logEntry))
|
||||
session <- .getShowcaseSessionContext()
|
||||
if (!is.null(session)) {
|
||||
session$.sendCustomMessage("reactlog", logEntry)
|
||||
|
||||
if (!is.null(domain)) {
|
||||
domain$reactlog(logEntry)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,12 +71,12 @@ renderReactLog <- function() {
|
||||
.graphAppend(list(action='depId', id=id, dependsOn=dependee))
|
||||
}
|
||||
|
||||
.graphCreateContext <- function(id, label, type, prevId) {
|
||||
.graphCreateContext <- function(id, label, type, prevId, domain) {
|
||||
.graphAppend(list(
|
||||
action='ctx', id=id, label=paste(label, collapse='\n'),
|
||||
action='ctx', id=id, label=paste(label, collapse='\n'),
|
||||
srcref=attr(label, "srcref"), srcfile=attr(label, "srcfile"),
|
||||
type=type, prevId=prevId
|
||||
))
|
||||
), domain = domain)
|
||||
}
|
||||
|
||||
.graphEnterContext <- function(id) {
|
||||
@@ -95,8 +95,8 @@ renderReactLog <- function() {
|
||||
))
|
||||
}
|
||||
|
||||
.graphInvalidate <- function(id) {
|
||||
.graphAppend(list(action='invalidate', id=id))
|
||||
.graphInvalidate <- function(id, domain) {
|
||||
.graphAppend(list(action='invalidate', id=id), domain)
|
||||
}
|
||||
|
||||
.graphEnv <- new.env()
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
# Call an application hook. Application hooks are provided so that front ends
|
||||
# can know when a Shiny application is running:
|
||||
#
|
||||
# shiny.onAppStart -- called when an application begins running
|
||||
# shiny.onAppStart -- called when an application begins running
|
||||
# shiny.onAppStop -- called when an appliation stops
|
||||
#
|
||||
# Both hooks are passed the url where the application is accessible (appUrl).
|
||||
# Both hooks are passed the url where the application is accessible (appUrl).
|
||||
# Note that the appUrl can be NULL if the application was run on a UNIX domain
|
||||
# socket rather than a TCP/IP port/
|
||||
callAppHook <- function(name, appUrl) {
|
||||
|
||||
15
R/html-deps.R
Normal file
@@ -0,0 +1,15 @@
|
||||
createWebDependency <- function(dependency) {
|
||||
if (is.null(dependency))
|
||||
return(NULL)
|
||||
|
||||
if (!inherits(dependency, "html_dependency"))
|
||||
stop("Unexpected non-html_dependency type")
|
||||
|
||||
if (is.null(dependency$src$href)) {
|
||||
prefix <- paste(dependency$name, "-", dependency$version, sep = "")
|
||||
addResourcePath(prefix, dependency$src$file)
|
||||
dependency$src$href <- prefix
|
||||
}
|
||||
|
||||
return(dependency)
|
||||
}
|
||||
7
R/htmltools.R
Normal file
@@ -0,0 +1,7 @@
|
||||
#' @export a br code div em h1 h2 h3 h4 h5 h6 hr HTML img p pre span strong
|
||||
#' @export includeCSS includeHTML includeMarkdown includeScript includeText
|
||||
#' @export is.singleton singleton
|
||||
#' @export tag tagAppendAttributes tagAppendChild tagAppendChildren tagList tags tagSetChildren withTags
|
||||
#' @export validateCssUnit
|
||||
#' @export knit_print.html knit_print.shiny.tag knit_print.shiny.tag.list
|
||||
NULL
|
||||
@@ -34,7 +34,7 @@ plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
||||
# Finally, if neither quartz nor Cairo, use png().
|
||||
if (capabilities("aqua")) {
|
||||
pngfun <- png
|
||||
} else if (getOption('shiny.usecairo', TRUE) &&
|
||||
} else if ((getOption('shiny.usecairo') %OR% TRUE) &&
|
||||
nchar(system.file(package = "Cairo"))) {
|
||||
pngfun <- Cairo::CairoPNG
|
||||
} else {
|
||||
@@ -42,6 +42,15 @@ plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
||||
}
|
||||
|
||||
pngfun(filename=filename, width=width, height=height, res=res, ...)
|
||||
# Call plot.new() so that even if no plotting operations are performed at
|
||||
# least we have a blank background. N.B. we need to set the margin to 0
|
||||
# temporarily before plot.new() because when the plot size is small (e.g.
|
||||
# 200x50), we will get an error "figure margin too large", which is triggered
|
||||
# by plot.new() with the default (large) margin. However, this does not
|
||||
# guarantee user's code in func() will not trigger the error -- they may have
|
||||
# to set par(mar = smaller_value) before they draw base graphics.
|
||||
op <- par(mar = rep(0, 4))
|
||||
tryCatch(plot.new(), finally = par(op))
|
||||
dv <- dev.cur()
|
||||
tryCatch(shinyCallingHandlers(func()), finally = dev.off(dv))
|
||||
|
||||
|
||||
104
R/jqueryui.R
Normal file
@@ -0,0 +1,104 @@
|
||||
#' Panel with absolute positioning
|
||||
#'
|
||||
#' Creates a panel whose contents are absolutely positioned.
|
||||
#'
|
||||
#' The \code{absolutePanel} function creates a \code{<div>} tag whose CSS
|
||||
#' position is set to \code{absolute} (or fixed if \code{fixed = TRUE}). The way
|
||||
#' absolute positioning works in HTML is that absolute coordinates are specified
|
||||
#' relative to its nearest parent element whose position is not set to
|
||||
#' \code{static} (which is the default), and if no such parent is found, then
|
||||
#' relative to the page borders. If you're not sure what that means, just keep
|
||||
#' in mind that you may get strange results if you use \code{absolutePanel} from
|
||||
#' inside of certain types of panels.
|
||||
#'
|
||||
#' The \code{fixedPanel} function is the same as \code{absolutePanel} with
|
||||
#' \code{fixed = TRUE}.
|
||||
#'
|
||||
#' The position (\code{top}, \code{left}, \code{right}, \code{bottom}) and size
|
||||
#' (\code{width}, \code{height}) parameters are all optional, but you should
|
||||
#' specify exactly two of \code{top}, \code{bottom}, and \code{height} and
|
||||
#' exactly two of \code{left}, \code{right}, and \code{width} for predictable
|
||||
#' results.
|
||||
#'
|
||||
#' Like most other distance parameters in Shiny, the position and size
|
||||
#' parameters take a number (interpreted as pixels) or a valid CSS size string,
|
||||
#' such as \code{"100px"} (100 pixels) or \code{"25\%"}.
|
||||
#'
|
||||
#' For arcane HTML reasons, to have the panel fill the page or parent you should
|
||||
#' specify \code{0} for \code{top}, \code{left}, \code{right}, and \code{bottom}
|
||||
#' rather than the more obvious \code{width = "100\%"} and \code{height =
|
||||
#' "100\%"}.
|
||||
#'
|
||||
#' @param ... Attributes (named arguments) or children (unnamed arguments) that
|
||||
#' should be included in the panel.
|
||||
#'
|
||||
#' @param top Distance between the top of the panel, and the top of the page or
|
||||
#' parent container.
|
||||
#' @param left Distance between the left side of the panel, and the left of the
|
||||
#' page or parent container.
|
||||
#' @param right Distance between the right side of the panel, and the right of
|
||||
#' the page or parent container.
|
||||
#' @param bottom Distance between the bottom of the panel, and the bottom of the
|
||||
#' page or parent container.
|
||||
#' @param width Width of the panel.
|
||||
#' @param height Height of the panel.
|
||||
#' @param draggable If \code{TRUE}, allows the user to move the panel by
|
||||
#' clicking and dragging.
|
||||
#' @param fixed Positions the panel relative to the browser window and prevents
|
||||
#' it from being scrolled with the rest of the page.
|
||||
#' @param cursor The type of cursor that should appear when the user mouses over
|
||||
#' the panel. Use \code{"move"} for a north-east-south-west icon,
|
||||
#' \code{"default"} for the usual cursor arrow, or \code{"inherit"} for the
|
||||
#' usual cursor behavior (including changing to an I-beam when the cursor is
|
||||
#' over text). The default is \code{"auto"}, which is equivalent to
|
||||
#' \code{ifelse(draggable, "move", "inherit")}.
|
||||
#' @return An HTML element or list of elements.
|
||||
#'
|
||||
#' @export
|
||||
absolutePanel <- function(...,
|
||||
top = NULL, left = NULL, right = NULL, bottom = NULL,
|
||||
width = NULL, height = NULL,
|
||||
draggable = FALSE, fixed = FALSE,
|
||||
cursor = c('auto', 'move', 'default', 'inherit')) {
|
||||
cssProps <- list(
|
||||
top = top,
|
||||
left = left,
|
||||
right = right,
|
||||
bottom = bottom,
|
||||
width = width,
|
||||
height = height
|
||||
)
|
||||
cssProps <- cssProps[!sapply(cssProps, is.null)]
|
||||
cssProps <- sapply(cssProps, validateCssUnit)
|
||||
cssProps[['position']] <- ifelse(fixed, 'fixed', 'absolute')
|
||||
cssProps[['cursor']] <- match.arg(cursor)
|
||||
if (identical(cssProps[['cursor']], 'auto'))
|
||||
cssProps[['cursor']] <- ifelse(draggable, 'move', 'inherit')
|
||||
|
||||
style <- paste(paste(names(cssProps), cssProps, sep = ':', collapse = ';'), ';', sep='')
|
||||
divTag <- tags$div(style=style, ...)
|
||||
if (isTRUE(draggable)) {
|
||||
divTag <- tagAppendAttributes(divTag, class='draggable')
|
||||
return(tagList(
|
||||
# IMPORTANT NOTE: If you update jqueryui, make sure you DON'T include the datepicker,
|
||||
# as it collides with our bootstrap datepicker!
|
||||
singleton(tags$head(tags$script(src='shared/jqueryui/1.10.4/jquery-ui.min.js'))),
|
||||
divTag,
|
||||
tags$script('$(".draggable").draggable();')
|
||||
))
|
||||
} else {
|
||||
return(divTag)
|
||||
}
|
||||
}
|
||||
|
||||
#' @rdname absolutePanel
|
||||
#' @export
|
||||
fixedPanel <- function(...,
|
||||
top = NULL, left = NULL, right = NULL, bottom = NULL,
|
||||
width = NULL, height = NULL,
|
||||
draggable = FALSE,
|
||||
cursor = c('move', 'default', 'inherit')) {
|
||||
absolutePanel(..., top=top, left=left, right=right, bottom=bottom,
|
||||
width=width, height=height, draggable=draggable, cursor=cursor,
|
||||
fixed=TRUE)
|
||||
}
|
||||
10
R/map.R
@@ -20,27 +20,23 @@ Map <- setRefClass(
|
||||
},
|
||||
get = function(key) {
|
||||
if (.self$containsKey(key))
|
||||
return(base::get(key, pos=.env, inherits=FALSE))
|
||||
else
|
||||
return(NULL)
|
||||
base::get(key, pos=.env, inherits=FALSE)
|
||||
},
|
||||
set = function(key, value) {
|
||||
assign(key, value, pos=.env, inherits=FALSE)
|
||||
return(value)
|
||||
value
|
||||
},
|
||||
mset = function(...) {
|
||||
args <- list(...)
|
||||
for (key in names(args))
|
||||
set(key, args[[key]])
|
||||
return()
|
||||
},
|
||||
remove = function(key) {
|
||||
if (.self$containsKey(key)) {
|
||||
result <- .self$get(key)
|
||||
rm(list = key, pos=.env, inherits=FALSE)
|
||||
return(result)
|
||||
result
|
||||
}
|
||||
return(NULL)
|
||||
},
|
||||
containsKey = function(key) {
|
||||
exists(key, where=.env, inherits=FALSE)
|
||||
|
||||
71
R/middleware-shiny.R
Normal file
@@ -0,0 +1,71 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
reactLogHandler <- function(req) {
|
||||
if (!identical(req$PATH_INFO, '/reactlog'))
|
||||
return(NULL)
|
||||
|
||||
if (!isTRUE(getOption('shiny.reactlog'))) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
return(httpResponse(
|
||||
status=200,
|
||||
content=list(file=renderReactLog(), owned=TRUE)
|
||||
))
|
||||
}
|
||||
|
||||
sessionHandler <- function(req) {
|
||||
path <- req$PATH_INFO
|
||||
if (is.null(path))
|
||||
return(NULL)
|
||||
|
||||
matches <- regmatches(path, regexec('^(/session/([0-9a-f]+))(/.*)$', path))
|
||||
if (length(matches[[1]]) == 0)
|
||||
return(NULL)
|
||||
|
||||
session <- matches[[1]][3]
|
||||
subpath <- matches[[1]][4]
|
||||
|
||||
shinysession <- appsByToken$get(session)
|
||||
if (is.null(shinysession))
|
||||
return(NULL)
|
||||
|
||||
subreq <- as.environment(as.list(req, all.names=TRUE))
|
||||
subreq$PATH_INFO <- subpath
|
||||
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, matches[[1]][2], sep='')
|
||||
|
||||
return(shinysession$handleRequest(subreq))
|
||||
}
|
||||
|
||||
dynamicHandler <- function(filePath, dependencyFiles=filePath) {
|
||||
lastKnownTimestamps <- NA
|
||||
metaHandler <- function(req) NULL
|
||||
|
||||
if (!file.exists(filePath))
|
||||
return(metaHandler)
|
||||
|
||||
cacheContext <- CacheContext$new()
|
||||
|
||||
return (function(req) {
|
||||
# Check if we need to rebuild
|
||||
if (cacheContext$isDirty()) {
|
||||
cacheContext$reset()
|
||||
for (dep in dependencyFiles)
|
||||
cacheContext$addDependencyFile(dep)
|
||||
|
||||
clearClients()
|
||||
if (file.exists(filePath)) {
|
||||
local({
|
||||
cacheContext$with(function() {
|
||||
sys.source(filePath, envir=new.env(parent=globalenv()), keep.source=TRUE)
|
||||
})
|
||||
})
|
||||
}
|
||||
metaHandler <<- joinHandlers(.globals$clients)
|
||||
clearClients()
|
||||
}
|
||||
|
||||
return(metaHandler(req))
|
||||
})
|
||||
}
|
||||
354
R/middleware.R
Normal file
@@ -0,0 +1,354 @@
|
||||
# This file contains a general toolkit for routing and combining bits of
|
||||
# HTTP-handling logic. It is similar in spirit to Rook (and Rack, and WSGI, and
|
||||
# Connect, and...) but adds cascading and routing.
|
||||
#
|
||||
# This file is called "middleware" because that's the term used for these bits
|
||||
# of logic in these other frameworks. However, our code uses the word "handler"
|
||||
# so we'll stick to that for the rest of this document; just know that they're
|
||||
# basically the same concept.
|
||||
#
|
||||
# ## Intro to handlers
|
||||
#
|
||||
# A **handler** (or sometimes, **httpHandler**) is a function that takes a
|
||||
# `req` parameter--a request object as described in the Rook specification--and
|
||||
# returns `NULL`, or an `httpResponse`.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
httpResponse <- function(status = 200,
|
||||
content_type = "text/html; charset=UTF-8",
|
||||
content = "",
|
||||
headers = list()) {
|
||||
# Make sure it's a list, not a vector
|
||||
headers <- as.list(headers)
|
||||
if (is.null(headers$`X-UA-Compatible`))
|
||||
headers$`X-UA-Compatible` <- "chrome=1"
|
||||
resp <- list(status = status, content_type = content_type, content = content,
|
||||
headers = headers)
|
||||
class(resp) <- 'httpResponse'
|
||||
return(resp)
|
||||
}
|
||||
|
||||
#
|
||||
# You can think of a web application as being simply an aggregation of these
|
||||
# functions, each of which performs one kind of duty. Each handler in turn gets
|
||||
# a look at the request and can decide whether it knows how to handle it. If
|
||||
# so, it returns an `httpResponse` and processing terminates; if not, it
|
||||
# returns `NULL` and the next handler gets to execute. If the final handler
|
||||
# returns `NULL`, a 404 response should be returned.
|
||||
#
|
||||
# We have a similar construct for websockets: **websocket handlers** or
|
||||
# **wsHandlers**. These take a single `ws` argument which is the websocket
|
||||
# connection that was just opened, and they can either return `TRUE` if they
|
||||
# are handling the connection, and `NULL` to pass responsibility on to the next
|
||||
# wsHandler.
|
||||
#
|
||||
# ### Combining handlers
|
||||
#
|
||||
# Since it's so common for httpHandlers to be invoked in this "cascading"
|
||||
# fashion, we'll introduce a function that takes zero or more handlers and
|
||||
# returns a single handler. And while we're at it, making a directory of static
|
||||
# content available is such a common thing to do, we'll allow strings
|
||||
# representing paths to be used instead of handlers; any such strings we
|
||||
# encounter will be converted into `staticHandler` objects.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
joinHandlers <- function(handlers) {
|
||||
# Zero handlers; return a null handler
|
||||
if (length(handlers) == 0)
|
||||
return(function(req) NULL)
|
||||
|
||||
# Just one handler (function)? Return it.
|
||||
if (is.function(handlers))
|
||||
return(handlers)
|
||||
|
||||
handlers <- lapply(handlers, function(h) {
|
||||
if (is.character(h))
|
||||
return(staticHandler(h))
|
||||
else
|
||||
return(h)
|
||||
})
|
||||
|
||||
# Filter out NULL
|
||||
handlers <- handlers[!sapply(handlers, is.null)]
|
||||
|
||||
if (length(handlers) == 0)
|
||||
return(function(req) NULL)
|
||||
if (length(handlers) == 1)
|
||||
return(handlers[[1]])
|
||||
|
||||
function(req) {
|
||||
for (handler in handlers) {
|
||||
response <- handler(req)
|
||||
if (!is.null(response))
|
||||
return(response)
|
||||
}
|
||||
return(NULL)
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Note that we don't have an equivalent of `joinHandlers` for wsHandlers. It's
|
||||
# easy to imagine it, we just haven't needed one.
|
||||
#
|
||||
# ### Handler routing
|
||||
#
|
||||
# Handlers do not have a built-in notion of routing. Conceptually, given a list
|
||||
# of handlers, all the handlers are peers and they all get to see every request
|
||||
# (well, up until the point that a handler returns a response).
|
||||
#
|
||||
# You could implement routing in each handler by checking the request's
|
||||
# `PATH_INFO` field, but since it's such a common need, let's make it simple by
|
||||
# introducing a `routeHandler` function. This is a handler
|
||||
# [decorator](http://en.wikipedia.org/wiki/Decorator_pattern) and it's
|
||||
# responsible for 1) filtering out requests that don't match the given route,
|
||||
# and 2) temporarily modifying the request object to take the matched part of
|
||||
# the route off of the `PATH_INFO` (and add it to the end of `SCRIPT_NAME`).
|
||||
# This way, the handler doesn't need to figure out about what part of its URL
|
||||
# path has already been matched via routing.
|
||||
#
|
||||
# (BTW, it's safe for `routeHandler` calls to nest.)
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
routeHandler <- function(prefix, handler) {
|
||||
force(prefix)
|
||||
force(handler)
|
||||
|
||||
if (identical("", prefix))
|
||||
return(handler)
|
||||
|
||||
if (length(prefix) != 1 || !isTRUE(grepl("^/[^\\]+$", prefix))) {
|
||||
stop("Invalid URL prefix \"", prefix, "\"")
|
||||
}
|
||||
|
||||
pathPattern <- paste("^\\Q", prefix, "\\E/", sep = "")
|
||||
function(req) {
|
||||
if (isTRUE(grepl(pathPattern, req$PATH_INFO))) {
|
||||
origScript <- req$SCRIPT_NAME
|
||||
origPath <- req$PATH_INFO
|
||||
on.exit({
|
||||
req$SCRIPT_NAME <- origScript
|
||||
req$PATH_INFO <- origPath
|
||||
}, add = TRUE)
|
||||
pathInfo <- substr(req$PATH_INFO, nchar(prefix)+1, nchar(req$PATH_INFO))
|
||||
req$SCRIPT_NAME <- paste(req$SCRIPT_NAME, prefix, sep = "")
|
||||
req$PATH_INFO <- pathInfo
|
||||
return(handler(req))
|
||||
} else {
|
||||
return(NULL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# We have a version for websocket handlers as well. Pity about the copy/paste
|
||||
# job.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
routeWSHandler <- function(prefix, wshandler) {
|
||||
force(prefix)
|
||||
force(wshandler)
|
||||
|
||||
if (identical("", prefix))
|
||||
return(wshandler)
|
||||
|
||||
if (length(prefix) != 1 || !isTRUE(grepl("^/[^\\]+$", prefix))) {
|
||||
stop("Invalid URL prefix \"", prefix, "\"")
|
||||
}
|
||||
|
||||
pathPattern <- paste("^\\Q", prefix, "\\E/", sep = "")
|
||||
function(ws) {
|
||||
req <- ws$request
|
||||
if (isTRUE(grepl(pathPattern, req$PATH_INFO))) {
|
||||
origScript <- req$SCRIPT_NAME
|
||||
origPath <- req$PATH_INFO
|
||||
on.exit({
|
||||
req$SCRIPT_NAME <- origScript
|
||||
req$PATH_INFO <- origPath
|
||||
}, add = TRUE)
|
||||
pathInfo <- substr(req$PATH_INFO, nchar(prefix)+1, nchar(req$PATH_INFO))
|
||||
req$SCRIPT_NAME <- paste(req$SCRIPT_NAME, prefix, sep = "")
|
||||
req$PATH_INFO <- pathInfo
|
||||
return(wshandler(ws))
|
||||
} else {
|
||||
return(NULL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# ### Handler implementations
|
||||
#
|
||||
# Now let's actually write some handlers. Note that these functions aren't
|
||||
# *themselves* handlers, you call them and they *return* a handler. Handler
|
||||
# factory functions, if you will.
|
||||
#
|
||||
# Here's one that serves up static assets from a directory.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
staticHandler <- function(root) {
|
||||
force(root)
|
||||
return(function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
path <- req$PATH_INFO
|
||||
|
||||
if (is.null(path))
|
||||
return(httpResponse(400, content="<h1>Bad Request</h1>"))
|
||||
|
||||
if (path == '/')
|
||||
path <- '/index.html'
|
||||
|
||||
abs.path <- resolve(root, path)
|
||||
if (is.null(abs.path))
|
||||
return(NULL)
|
||||
|
||||
ext <- tools::file_ext(abs.path)
|
||||
content.type <- getContentType(ext)
|
||||
response.content <- readBin(abs.path, 'raw', n=file.info(abs.path)$size)
|
||||
return(httpResponse(200, content.type, response.content))
|
||||
})
|
||||
}
|
||||
|
||||
#
|
||||
# ## Handler manager
|
||||
#
|
||||
# The handler manager gives you a place to register handlers (of both http and
|
||||
# websocket varieties) and provides an httpuv-compatible set of callbacks for
|
||||
# invoking them.
|
||||
#
|
||||
# Create one of these, make zero or more calls to `addHandler` and
|
||||
# `addWSHandler` methods (order matters--first one wins!), and then pass the
|
||||
# return value of `createHttpuvApp` to httpuv's `startServer` function.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
HandlerList <- setRefClass("HandlerList",
|
||||
fields = list(
|
||||
handlers = "list"
|
||||
),
|
||||
methods = list(
|
||||
add = function(handler, key, tail = FALSE) {
|
||||
if (!is.null(handlers[[key]]))
|
||||
stop("Key ", key, " already in use")
|
||||
newList <- structure(names=key, list(handler))
|
||||
|
||||
if (length(handlers) == 0)
|
||||
handlers <<- newList
|
||||
else if (tail)
|
||||
handlers <<- c(handlers, newList)
|
||||
else
|
||||
handlers <<- c(newList, handlers)
|
||||
},
|
||||
remove = function(key) {
|
||||
handlers[key] <<- NULL
|
||||
},
|
||||
clear = function() {
|
||||
handlers <<- list()
|
||||
},
|
||||
invoke = function(...) {
|
||||
for (handler in handlers) {
|
||||
result <- handler(...)
|
||||
if (!is.null(result))
|
||||
return(result)
|
||||
}
|
||||
return(NULL)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
HandlerManager <- setRefClass("HandlerManager",
|
||||
fields = list(
|
||||
handlers = "HandlerList",
|
||||
wsHandlers = "HandlerList"
|
||||
),
|
||||
methods = list(
|
||||
addHandler = function(handler, key, tail = FALSE) {
|
||||
handlers$add(handler, key, tail)
|
||||
},
|
||||
removeHandler = function(key) {
|
||||
handlers$remove(key)
|
||||
},
|
||||
addWSHandler = function(wsHandler, key, tail = FALSE) {
|
||||
wsHandlers$add(wsHandler, key, tail)
|
||||
},
|
||||
removeWSHandler = function(key) {
|
||||
wsHandlers$remove(key)
|
||||
},
|
||||
clear = function() {
|
||||
handlers$clear()
|
||||
wsHandlers$clear()
|
||||
},
|
||||
createHttpuvApp = function() {
|
||||
list(
|
||||
onHeaders = function(req) {
|
||||
maxSize <- getOption('shiny.maxRequestSize') %OR% (5 * 1024 * 1024)
|
||||
if (maxSize <= 0)
|
||||
return(NULL)
|
||||
|
||||
reqSize <- 0
|
||||
if (length(req$CONTENT_LENGTH) > 0)
|
||||
reqSize <- as.numeric(req$CONTENT_LENGTH)
|
||||
else if (length(req$HTTP_TRANSFER_ENCODING) > 0)
|
||||
reqSize <- Inf
|
||||
|
||||
if (reqSize > maxSize) {
|
||||
return(list(status = 413L,
|
||||
headers = list(
|
||||
'Content-Type' = 'text/plain'
|
||||
),
|
||||
body = 'Maximum upload size exceeded'))
|
||||
}
|
||||
else {
|
||||
return(NULL)
|
||||
}
|
||||
},
|
||||
call = .httpServer(
|
||||
function (req) {
|
||||
return(handlers$invoke(req))
|
||||
},
|
||||
getOption('shiny.sharedSecret')
|
||||
),
|
||||
onWSOpen = function(ws) {
|
||||
return(wsHandlers$invoke(ws))
|
||||
}
|
||||
)
|
||||
},
|
||||
.httpServer = function(handler, sharedSecret) {
|
||||
filter <- getOption('shiny.http.response.filter')
|
||||
if (is.null(filter))
|
||||
filter <- function(req, response) response
|
||||
|
||||
function(req) {
|
||||
if (!is.null(sharedSecret)
|
||||
&& !identical(sharedSecret, req$HTTP_SHINY_SHARED_SECRET)) {
|
||||
return(list(status=403,
|
||||
body='<h1>403 Forbidden</h1><p>Shared secret mismatch</p>',
|
||||
headers=list('Content-Type' = 'text/html')))
|
||||
}
|
||||
|
||||
response <- handler(req)
|
||||
if (is.null(response))
|
||||
response <- httpResponse(404, content="<h1>Not Found</h1>")
|
||||
|
||||
if (inherits(response, "httpResponse")) {
|
||||
headers <- as.list(response$headers)
|
||||
headers$'Content-Type' <- response$content_type
|
||||
|
||||
response <- filter(req, response)
|
||||
return(list(status=response$status,
|
||||
body=response$content,
|
||||
headers=headers))
|
||||
} else {
|
||||
# Assume it's a Rook-compatible response
|
||||
return(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#
|
||||
# ## Next steps
|
||||
#
|
||||
# See server.R and middleware-shiny.R to see actual implementation and usage of
|
||||
# handlers in the context of Shiny.
|
||||
@@ -10,14 +10,14 @@ PriorityQueue <- setRefClass(
|
||||
# Keys are priorities, values are subqueues (implemented as list)
|
||||
.itemsByPriority = 'Map',
|
||||
# Sorted vector (largest first)
|
||||
.priorities = 'numeric'
|
||||
.priorities = 'numeric'
|
||||
),
|
||||
methods = list(
|
||||
# Enqueue an item, with the given priority level (must be integer). Higher
|
||||
# Enqueue an item, with the given priority level (must be integer). Higher
|
||||
# priority numbers are dequeued earlier than lower.
|
||||
enqueue = function(item, priority) {
|
||||
priority <- normalizePriority(priority)
|
||||
|
||||
|
||||
if (!(priority %in% .priorities)) {
|
||||
.priorities <<- c(.priorities, priority)
|
||||
.priorities <<- sort(.priorities, decreasing=TRUE)
|
||||
@@ -30,14 +30,14 @@ PriorityQueue <- setRefClass(
|
||||
}
|
||||
return(invisible())
|
||||
},
|
||||
# Retrieve a single item by 1) priority number (highest first) and then 2)
|
||||
# insertion order (first in, first out). If there are no items to be
|
||||
# Retrieve a single item by 1) priority number (highest first) and then 2)
|
||||
# insertion order (first in, first out). If there are no items to be
|
||||
# dequeued, then NULL is returned. If it is necessary to distinguish between
|
||||
# a NULL value and the empty case, call isEmpty() before dequeue().
|
||||
dequeue = function() {
|
||||
if (length(.priorities) == 0)
|
||||
return(NULL)
|
||||
|
||||
|
||||
maxPriority <- .priorities[[1]]
|
||||
items <- .itemsByPriority$get(.key(maxPriority))
|
||||
firstItem <- items[[1]]
|
||||
@@ -67,17 +67,17 @@ PriorityQueue <- setRefClass(
|
||||
)
|
||||
|
||||
normalizePriority <- function(priority) {
|
||||
|
||||
|
||||
if (is.null(priority))
|
||||
priority <- 0
|
||||
|
||||
|
||||
# Cast integers to numeric to prevent any inconsistencies
|
||||
if (is.integer(priority))
|
||||
priority <- as.numeric(priority)
|
||||
|
||||
|
||||
if (!is.numeric(priority))
|
||||
stop('priority must be an integer or numeric')
|
||||
|
||||
|
||||
# Check length
|
||||
if (length(priority) == 0) {
|
||||
warning('Zero-length priority vector was passed; using 0')
|
||||
@@ -86,7 +86,7 @@ normalizePriority <- function(priority) {
|
||||
warning('Priority has length > 1 and only the first element will be used')
|
||||
priority <- priority[1]
|
||||
}
|
||||
|
||||
|
||||
# NA == 0
|
||||
if (is.na(priority))
|
||||
priority <- 0
|
||||
|
||||
51
R/react.R
@@ -5,23 +5,29 @@ Context <- setRefClass(
|
||||
.label = 'character', # For debug purposes
|
||||
.invalidated = 'logical',
|
||||
.invalidateCallbacks = 'list',
|
||||
.flushCallbacks = 'list'
|
||||
.flushCallbacks = 'list',
|
||||
.domain = 'ANY'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function(label='', type='other', prevId='') {
|
||||
initialize = function(domain, label='', type='other', prevId='') {
|
||||
id <<- .getReactiveEnvironment()$nextId()
|
||||
.invalidated <<- FALSE
|
||||
.invalidateCallbacks <<- list()
|
||||
.flushCallbacks <<- list()
|
||||
.label <<- label
|
||||
.graphCreateContext(id, label, type, prevId)
|
||||
.domain <<- domain
|
||||
.graphCreateContext(id, label, type, prevId, domain)
|
||||
},
|
||||
run = function(func) {
|
||||
"Run the provided function under this context."
|
||||
env <- .getReactiveEnvironment()
|
||||
.graphEnterContext(id)
|
||||
on.exit(.graphExitContext(id))
|
||||
env$runWith(.self, func)
|
||||
withReactiveDomain(.domain, {
|
||||
env <- .getReactiveEnvironment()
|
||||
.graphEnterContext(id)
|
||||
tryCatch(
|
||||
env$runWith(.self, func),
|
||||
finally = .graphExitContext(id)
|
||||
)
|
||||
})
|
||||
},
|
||||
invalidate = function() {
|
||||
"Invalidate this context. It will immediately call the callbacks
|
||||
@@ -30,10 +36,11 @@ Context <- setRefClass(
|
||||
return()
|
||||
.invalidated <<- TRUE
|
||||
|
||||
.graphInvalidate(id)
|
||||
.graphInvalidate(id, .domain)
|
||||
lapply(.invalidateCallbacks, function(func) {
|
||||
func()
|
||||
})
|
||||
.invalidateCallbacks <<- list()
|
||||
NULL
|
||||
},
|
||||
onInvalidate = function(func) {
|
||||
@@ -91,8 +98,8 @@ ReactiveEnvironment <- setRefClass(
|
||||
},
|
||||
currentContext = function() {
|
||||
if (is.null(.currentContext)) {
|
||||
if (isTRUE(getOption('shiny.suppressMissingContextError', FALSE))) {
|
||||
return(dummyContext)
|
||||
if (isTRUE(getOption('shiny.suppressMissingContextError'))) {
|
||||
return(getDummyContext())
|
||||
} else {
|
||||
stop('Operation not allowed without an active reactive context. ',
|
||||
'(You tried to do something that can only be done from inside a ',
|
||||
@@ -124,10 +131,14 @@ ReactiveEnvironment <- setRefClass(
|
||||
)
|
||||
)
|
||||
|
||||
.reactiveEnvironment <- ReactiveEnvironment$new()
|
||||
.getReactiveEnvironment <- function() {
|
||||
.reactiveEnvironment
|
||||
}
|
||||
.getReactiveEnvironment <- local({
|
||||
reactiveEnvironment <- NULL
|
||||
function() {
|
||||
if (is.null(reactiveEnvironment))
|
||||
reactiveEnvironment <<- ReactiveEnvironment$new()
|
||||
return(reactiveEnvironment)
|
||||
}
|
||||
})
|
||||
|
||||
# Causes any pending invalidations to run.
|
||||
flushReact <- function() {
|
||||
@@ -140,4 +151,14 @@ getCurrentContext <- function() {
|
||||
.getReactiveEnvironment()$currentContext()
|
||||
}
|
||||
|
||||
delayedAssign("dummyContext", Context$new('[none]', type='isolate'))
|
||||
getDummyContext <- function() {}
|
||||
local({
|
||||
dummyContext <- NULL
|
||||
getDummyContext <<- function() {
|
||||
if (is.null(dummyContext)) {
|
||||
dummyContext <<- Context$new(getDefaultReactiveDomain(), '[none]',
|
||||
type='isolate')
|
||||
}
|
||||
return(dummyContext)
|
||||
}
|
||||
})
|
||||
|
||||
253
R/reactive-domains.R
Normal file
@@ -0,0 +1,253 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
#
|
||||
# Over the last few months we've seen a number of cases where it'd be helpful
|
||||
# for objects that are instantiated within a Shiny app to know what Shiny
|
||||
# session they are "owned" by. I put "owned" in quotes because there isn't a
|
||||
# built-in notion of object ownership in Shiny today, any more than there is a
|
||||
# notion of one object owning another in R.
|
||||
#
|
||||
# But it's intuitive to everyone, I think, that the outputs for a session are
|
||||
# owned by that session, and any logic that is executed as part of the output
|
||||
# is done on behalf of that session. And it seems like in the vast majority of
|
||||
# cases, observers that are created inside a shinyServer function (i.e. one per
|
||||
# session) are also intuitively owned by the session that's starting up.
|
||||
#
|
||||
# This notion of ownership is important/helpful for a few scenarios that have
|
||||
# come up in recent months:
|
||||
#
|
||||
# 1. The showcase mode that Jonathan implemented recently highlights
|
||||
# observers/reactives as they execute. In order for sessions to only receive
|
||||
# highlights for their own code execution, we need to know which sessions own
|
||||
# which observers. 2. We've seen a number of apps crash out when observers
|
||||
# outlive their sessions and then try to do things with their sessions (the
|
||||
# most common error message was something like "Can't write to a closed
|
||||
# websocket", but we now silently ignore writes to closed websockets). It'd be
|
||||
# convenient for the default behavior of observers to be that they don't
|
||||
# outlive their parent sessions. 3. The reactive log visualizer currently
|
||||
# visualizes all reactivity in the process; it would be great if by default it
|
||||
# only visualized the current session. 4. When an observer has an error, it
|
||||
# would be great to be able to send the error to the session so it can do its
|
||||
# own handling (such as sending the error info to the client so the user can be
|
||||
# notified). 5. Shiny Server Pro wants to show the admin how much time is being
|
||||
# spent servicing each session.
|
||||
#
|
||||
# So what are the rules for establishing ownership?
|
||||
#
|
||||
# 1. Define the "current domain" as a global variable whose value will own any
|
||||
# newly created observer (by default). A domain is a reference class or
|
||||
# environment that contains the functions `onEnded(callback)`, `isEnded()`, and
|
||||
# `reactlog(logEntry)`.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
createMockDomain <- function() {
|
||||
callbacks <- list()
|
||||
ended <- FALSE
|
||||
domain <- new.env(parent = emptyenv())
|
||||
domain$onEnded <- function(callback) {
|
||||
callbacks <<- c(callbacks, callback)
|
||||
}
|
||||
domain$isEnded <- function() {
|
||||
ended
|
||||
}
|
||||
domain$reactlog <- function(logEntry) NULL
|
||||
domain$end <- function() {
|
||||
if (!ended) {
|
||||
ended <<- TRUE
|
||||
lapply(callbacks, do.call, list())
|
||||
}
|
||||
invisible()
|
||||
}
|
||||
return(domain)
|
||||
}
|
||||
|
||||
#
|
||||
# 2. The initial value of "current domain" is null.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
.globals$domain <- NULL
|
||||
|
||||
#
|
||||
# 3. Objects that can be owned include observers, reactive expressions,
|
||||
# invalidateLater instances, reactiveTimer instances. Whenever one of these is
|
||||
# created, by default its owner will be the current domain.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' @name domains
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
getDefaultReactiveDomain <- function() {
|
||||
.globals$domain
|
||||
}
|
||||
|
||||
#
|
||||
# 4. While a session is being created and the shinyServer function is executed,
|
||||
# the current domain is set to the new session. When the shinyServer function
|
||||
# is done executing, the previous value of the current domain is restored. This
|
||||
# is made foolproof using a `withReactiveDomain` function.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
withReactiveDomain <- function(domain, expr) {
|
||||
oldValue <- .globals$domain
|
||||
.globals$domain <- domain
|
||||
on.exit(.globals$domain <- oldValue)
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
#
|
||||
# 5. While an observer or reactive expression is executing, the current domain
|
||||
# is set to the owner of the observer. When the observer completes, the
|
||||
# previous value of the current domain is restored.
|
||||
#
|
||||
# 6. Note that once created, an observer/reactive expression belongs to the
|
||||
# same domain forever, regardless of how many times it is invalidated and
|
||||
# re-executed, and regardless of what caused the invalidation to happen.
|
||||
#
|
||||
# 7. When a session ends, any observers that it owns are suspended, any
|
||||
# invalidateLater/reactiveTimers are stopped.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
onReactiveDomainEnded <- function(domain, callback, failIfNull = FALSE) {
|
||||
if (is.null(domain)) {
|
||||
if (isTRUE(failIfNull))
|
||||
stop("onReactiveDomainEnded called with null domain and failIfNull=TRUE")
|
||||
else
|
||||
return()
|
||||
}
|
||||
domain$onEnded(callback)
|
||||
}
|
||||
|
||||
#
|
||||
# 8. If an uncaught error occurs while executing an observer, the session gets
|
||||
# a chance to handle it. I suppose the default behavior would be to send the
|
||||
# message to the client if possible, and then perhaps end the session (or not,
|
||||
# I could argue either way).
|
||||
#
|
||||
# The basic idea here is inspired by Node.js domains, which you can think of as
|
||||
# a way to track execution contexts across callback- or listener-oriented
|
||||
# asynchronous code. They use it to unify error handling code across a graph of
|
||||
# related objects. Our domains will be to unify both lifetime and error
|
||||
# handling across a graph of related reactive primitives.
|
||||
#
|
||||
# (You could imagine that as a client update is being processed, the session
|
||||
# associated with that client would become the current domain. IIRC this is how
|
||||
# showcase mode is implemented today. I don't think this would cover any cases
|
||||
# not covered by rule 5 above, and the absence of rule 5 would leave cases that
|
||||
# this rule would not cover.)
|
||||
#
|
||||
# Pitfalls/open issues:
|
||||
#
|
||||
# 1. Our current approach has the issue of observers staying alive longer than
|
||||
# they ought to. This proposal introduces the opposite risk: that
|
||||
# observers/invalidateLater/reactiveTimer instances, having implicitly been
|
||||
# assigned a parent, are suspended/disposed earlier than they ought to have
|
||||
# been. I find this especially worrisome for invalidateLater/reactiveTimer,
|
||||
# which will often be called in a reactive expression, and thus execute under
|
||||
# unpredictable circumstances. Perhaps those should continue to accept an
|
||||
# explicit "session=" parameter that the user is warned about if they don't
|
||||
# provide a value.
|
||||
#
|
||||
# 2. Are there situations where it is ambiguous what the right thing to do is,
|
||||
# and we should warn/error to ask the user to provide a domain explicitly?
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' Reactive domains
|
||||
#'
|
||||
#' Reactive domains are a mechanism for establishing ownership over reactive
|
||||
#' primitives (like reactive expressions and observers), even if the set of
|
||||
#' reactive primitives is dynamically created. This is useful for lifetime
|
||||
#' management (i.e. destroying observers when the Shiny session that created
|
||||
#' them ends) and error handling.
|
||||
#'
|
||||
#' At any given time, there can be either a single "default" reactive domain
|
||||
#' object, or none (i.e. the reactive domain object is \code{NULL}). You can
|
||||
#' access the current default reactive domain by calling
|
||||
#' \code{getDefaultReactiveDomain}.
|
||||
#'
|
||||
#' Unless you specify otherwise, newly created observers and reactive
|
||||
#' expressions will be assigned to the current default domain (if any). You can
|
||||
#' override this assignment by providing an explicit \code{domain} argument to
|
||||
#' \code{\link{reactive}} or \code{\link{observe}}.
|
||||
#'
|
||||
#' For advanced usage, it's possible to override the default domain using
|
||||
#' \code{withReactiveDomain}. The \code{domain} argument will be made the
|
||||
#' default domain while \code{expr} is evaluated.
|
||||
#'
|
||||
#' Implementers of new reactive primitives can use \code{onReactiveDomainEnded}
|
||||
#' as a convenience function for registering callbacks. If the reactive domain
|
||||
#' is \code{NULL} and \code{failIfNull} is \code{FALSE}, then the callback will
|
||||
#' never be invoked.
|
||||
#'
|
||||
#' @name domains
|
||||
#' @param domain A valid domain object (for example, a Shiny session), or
|
||||
#' \code{NULL}
|
||||
#' @param expr An expression to evaluate under \code{domain}
|
||||
#' @param callback A callback function to be invoked
|
||||
#' @param failIfNull If \code{TRUE} then an error is given if the \code{domain}
|
||||
#' is \code{NULL}
|
||||
NULL
|
||||
|
||||
#
|
||||
# Example 1
|
||||
# ---
|
||||
# ```
|
||||
# obs1 <- observe({
|
||||
# })
|
||||
# shinyServer(function(input, output) {
|
||||
# obs2 <- observe({
|
||||
# obs3 <- observe({
|
||||
# })
|
||||
# })
|
||||
# })
|
||||
# # obs1 would have no domain, obs2 and obs3 would be owned by the session
|
||||
# ```
|
||||
#
|
||||
# Example 2
|
||||
# ---
|
||||
# ```
|
||||
# globalValues <- reactiveValues(broadcast="")
|
||||
# shinyServer(function(input, output) {
|
||||
# sessionValues <- reactiveValues()
|
||||
# output$messageOutput <- renderText({
|
||||
# globalValues$broadcast
|
||||
# obs1 <- observe({...})
|
||||
# })
|
||||
# observe({
|
||||
# if (input$goButton == 0) return()
|
||||
# isolate( globalValues$broadcast <- input$messageInput )
|
||||
# })
|
||||
# })
|
||||
# # The observer behind messageOutput would be owned by the session,
|
||||
# # as would all the many instances of obs1 that were created.
|
||||
# ```
|
||||
# ---
|
||||
#
|
||||
# Example 3
|
||||
# ---
|
||||
# ```
|
||||
# rexpr1 <- reactive({
|
||||
# invalidateLater(1000)
|
||||
# obs1 <- observe({...})
|
||||
# })
|
||||
# observeSomething <- function() {
|
||||
# obs2 <- observe({...})
|
||||
# })
|
||||
# shinyServer(function(input, output) {
|
||||
# obs3 <- observe({
|
||||
# observeSomething()
|
||||
# rexpr1()
|
||||
# })
|
||||
# })
|
||||
# # rexpr1, the invalidateLater call, and obs1 would all have no owner;
|
||||
# # obs2 and obs3 would be owned by the session.
|
||||
# ```
|
||||
397
R/reactives.R
@@ -1,3 +1,6 @@
|
||||
#' @include utils.R
|
||||
NULL
|
||||
|
||||
Dependents <- setRefClass(
|
||||
'Dependents',
|
||||
fields = list(
|
||||
@@ -11,7 +14,7 @@ Dependents <- setRefClass(
|
||||
ctx$onInvalidate(function() {
|
||||
.dependents$remove(ctx$id)
|
||||
})
|
||||
|
||||
|
||||
if (!is.null(depId) && nchar(depId) > 0)
|
||||
.graphDependsOnId(ctx$id, depId)
|
||||
if (!is.null(depLabel))
|
||||
@@ -49,7 +52,8 @@ ReactiveValues <- setRefClass(
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
.label <<- paste('reactiveValues', runif(1, min=1000, max=9999),
|
||||
.label <<- paste('reactiveValues',
|
||||
p_randomInt(1000, 10000),
|
||||
sep="")
|
||||
.values <<- new.env(parent=emptyenv())
|
||||
.dependents <<- new.env(parent=emptyenv())
|
||||
@@ -64,7 +68,7 @@ ReactiveValues <- setRefClass(
|
||||
rm(list=dep.key, pos=.dependents, inherits=FALSE)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if (!exists(key, where=.values, inherits=FALSE))
|
||||
NULL
|
||||
else
|
||||
@@ -208,15 +212,15 @@ setOldClass("reactivevalues")
|
||||
#' @export
|
||||
is.reactivevalues <- function(x) inherits(x, 'reactivevalues')
|
||||
|
||||
#' @S3method $ reactivevalues
|
||||
#' @export
|
||||
`$.reactivevalues` <- function(x, name) {
|
||||
.subset2(x, 'impl')$get(name)
|
||||
}
|
||||
|
||||
#' @S3method [[ reactivevalues
|
||||
#' @export
|
||||
`[[.reactivevalues` <- `$.reactivevalues`
|
||||
|
||||
#' @S3method $<- reactivevalues
|
||||
#' @export
|
||||
`$<-.reactivevalues` <- function(x, name, value) {
|
||||
if (attr(x, 'readonly')) {
|
||||
stop("Attempted to assign value to a read-only reactivevalues object")
|
||||
@@ -228,30 +232,30 @@ is.reactivevalues <- function(x) inherits(x, 'reactivevalues')
|
||||
}
|
||||
}
|
||||
|
||||
#' @S3method [[<- reactivevalues
|
||||
#' @export
|
||||
`[[<-.reactivevalues` <- `$<-.reactivevalues`
|
||||
|
||||
#' @S3method [ reactivevalues
|
||||
#' @export
|
||||
`[.reactivevalues` <- function(values, name) {
|
||||
stop("Single-bracket indexing of reactivevalues object is not allowed.")
|
||||
}
|
||||
|
||||
#' @S3method [<- reactivevalues
|
||||
#' @export
|
||||
`[<-.reactivevalues` <- function(values, name, value) {
|
||||
stop("Single-bracket indexing of reactivevalues object is not allowed.")
|
||||
}
|
||||
|
||||
#' @S3method names reactivevalues
|
||||
#' @export
|
||||
names.reactivevalues <- function(x) {
|
||||
.subset2(x, 'impl')$names()
|
||||
}
|
||||
|
||||
#' @S3method names<- reactivevalues
|
||||
#' @export
|
||||
`names<-.reactivevalues` <- function(x, value) {
|
||||
stop("Can't assign names to reactivevalues object")
|
||||
}
|
||||
|
||||
#' @S3method as.list reactivevalues
|
||||
#' @export
|
||||
as.list.reactivevalues <- function(x, all.names=FALSE, ...) {
|
||||
shinyDeprecated("reactiveValuesToList",
|
||||
msg = paste("'as.list.reactivevalues' is deprecated. ",
|
||||
@@ -293,6 +297,17 @@ reactiveValuesToList <- function(x, all.names=FALSE) {
|
||||
.subset2(x, 'impl')$toList(all.names)
|
||||
}
|
||||
|
||||
# This function is needed because str() on a reactivevalues object will call
|
||||
# [[.reactivevalues(), which will give an error when it tries to access
|
||||
# x[['impl']].
|
||||
#' @export
|
||||
str.reactivevalues <- function(object, indent.str = " ", ...) {
|
||||
str(unclass(object), indent.str = indent.str, ...)
|
||||
# Need to manually print out the class field,
|
||||
cat(indent.str, '- attr(*, "class")=', sep = "")
|
||||
str(class(object))
|
||||
}
|
||||
|
||||
# Observable ----------------------------------------------------------------
|
||||
|
||||
Observable <- setRefClass(
|
||||
@@ -300,6 +315,7 @@ Observable <- setRefClass(
|
||||
fields = list(
|
||||
.func = 'function',
|
||||
.label = 'character',
|
||||
.domain = 'ANY',
|
||||
.dependents = 'Dependents',
|
||||
.invalidated = 'logical',
|
||||
.running = 'logical',
|
||||
@@ -309,7 +325,8 @@ Observable <- setRefClass(
|
||||
.mostRecentCtxId = 'character'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function(func, label=deparse(substitute(func))) {
|
||||
initialize = function(func, label = deparse(substitute(func)),
|
||||
domain = getDefaultReactiveDomain()) {
|
||||
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 ",
|
||||
@@ -318,6 +335,7 @@ Observable <- setRefClass(
|
||||
.invalidated <<- TRUE
|
||||
.running <<- FALSE
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.execCount <<- 0L
|
||||
.mostRecentCtxId <<- ""
|
||||
},
|
||||
@@ -329,7 +347,7 @@ Observable <- setRefClass(
|
||||
}
|
||||
|
||||
.graphDependsOnId(getCurrentContext()$id, .mostRecentCtxId)
|
||||
|
||||
|
||||
if (identical(class(.value), 'try-error'))
|
||||
stop(attr(.value, 'condition'))
|
||||
|
||||
@@ -339,7 +357,8 @@ Observable <- setRefClass(
|
||||
invisible(.value)
|
||||
},
|
||||
.updateValue = function() {
|
||||
ctx <- Context$new(.label, type='observable', prevId=.mostRecentCtxId)
|
||||
ctx <- Context$new(.domain, .label, type = 'observable',
|
||||
prevId = .mostRecentCtxId)
|
||||
.mostRecentCtxId <<- ctx$id
|
||||
ctx$onInvalidate(function() {
|
||||
.invalidated <<- TRUE
|
||||
@@ -354,7 +373,7 @@ Observable <- setRefClass(
|
||||
on.exit(.running <<- wasRunning)
|
||||
|
||||
ctx$run(function() {
|
||||
result <- withVisible(try(shinyCallingHandlers(.func()), silent=FALSE))
|
||||
result <- withVisible(try(shinyCallingHandlers(.func()), silent=TRUE))
|
||||
.visible <<- result$visible
|
||||
.value <<- result$value
|
||||
})
|
||||
@@ -378,7 +397,7 @@ Observable <- setRefClass(
|
||||
#' 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
|
||||
#' @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
|
||||
@@ -387,6 +406,7 @@ Observable <- setRefClass(
|
||||
#' 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()`.
|
||||
#' @param label A label for the reactive expression, useful for debugging.
|
||||
#' @param domain See \link{domains}.
|
||||
#' @return a function, wrapped in a S3 class "reactive"
|
||||
#'
|
||||
#' @examples
|
||||
@@ -409,20 +429,21 @@ Observable <- setRefClass(
|
||||
#' isolate(reactiveD())
|
||||
#'
|
||||
#' @export
|
||||
reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL) {
|
||||
reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL,
|
||||
domain = getDefaultReactiveDomain()) {
|
||||
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")
|
||||
attr(label, "srcref") <- srcref[[2]]
|
||||
if (length(srcref) >= 2) attr(label, "srcref") <- srcref[[2]]
|
||||
attr(label, "srcfile") <- srcFileOfRef(srcref[[1]])
|
||||
o <- Observable$new(fun, label)
|
||||
o <- Observable$new(fun, label, domain)
|
||||
registerDebugHook(".func", o, "Reactive")
|
||||
structure(o$getValue@.Data, observable = o, class = "reactive")
|
||||
}
|
||||
|
||||
#' @S3method print reactive
|
||||
#' @export
|
||||
print.reactive <- function(x, ...) {
|
||||
label <- attr(x, "observable")$.label
|
||||
cat(label, "\n")
|
||||
@@ -436,7 +457,7 @@ is.reactive <- function(x) inherits(x, "reactive")
|
||||
execCount <- function(x) {
|
||||
if (is.function(x))
|
||||
return(environment(x)$.execCount)
|
||||
else if (is(x, 'Observer'))
|
||||
else if (inherits(x, 'Observer'))
|
||||
return(x$.execCount)
|
||||
else
|
||||
stop('Unexpected argument to execCount')
|
||||
@@ -449,32 +470,42 @@ Observer <- setRefClass(
|
||||
fields = list(
|
||||
.func = 'function',
|
||||
.label = 'character',
|
||||
.domain = 'ANY',
|
||||
.priority = 'numeric',
|
||||
.autoDestroy = 'logical',
|
||||
.invalidateCallbacks = 'list',
|
||||
.execCount = 'integer',
|
||||
.onResume = 'function',
|
||||
.suspended = 'logical',
|
||||
.destroyed = 'logical',
|
||||
.prevId = 'character'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function(func, label, suspended = FALSE, priority = 0) {
|
||||
initialize = function(func, label, suspended = FALSE, priority = 0,
|
||||
domain = getDefaultReactiveDomain(),
|
||||
autoDestroy = TRUE) {
|
||||
if (length(formals(func)) > 0)
|
||||
stop("Can't make an observer from a function that takes parameters; ",
|
||||
"only functions without parameters can be reactive.")
|
||||
|
||||
.func <<- func
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.autoDestroy <<- autoDestroy
|
||||
.priority <<- normalizePriority(priority)
|
||||
.execCount <<- 0L
|
||||
.suspended <<- suspended
|
||||
.onResume <<- function() NULL
|
||||
.destroyed <<- FALSE
|
||||
.prevId <<- ''
|
||||
|
||||
onReactiveDomainEnded(.domain, .self$.onDomainEnded)
|
||||
|
||||
# Defer the first running of this until flushReact is called
|
||||
.createContext()$invalidate()
|
||||
},
|
||||
.createContext = function() {
|
||||
ctx <- Context$new(.label, type='observer', prevId=.prevId)
|
||||
ctx <- Context$new(.domain, .label, type='observer', prevId=.prevId)
|
||||
.prevId <<- ctx$id
|
||||
|
||||
ctx$onInvalidate(function() {
|
||||
@@ -486,17 +517,18 @@ Observer <- setRefClass(
|
||||
continue <- function() {
|
||||
ctx$addPendingFlush(.priority)
|
||||
}
|
||||
|
||||
|
||||
if (.suspended == FALSE)
|
||||
continue()
|
||||
else
|
||||
.onResume <<- continue
|
||||
})
|
||||
|
||||
|
||||
ctx$onFlush(function() {
|
||||
run()
|
||||
if (!.destroyed)
|
||||
run()
|
||||
})
|
||||
|
||||
|
||||
return(ctx)
|
||||
},
|
||||
run = function() {
|
||||
@@ -517,6 +549,17 @@ Observer <- setRefClass(
|
||||
which case the priority change will be effective upon resume."
|
||||
.priority <<- normalizePriority(priority)
|
||||
},
|
||||
setAutoDestroy = function(autoDestroy) {
|
||||
"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."
|
||||
oldValue <- .autoDestroy
|
||||
.autoDestroy <<- autoDestroy
|
||||
if (!is.null(.domain) && .domain$isEnded()) {
|
||||
destroy()
|
||||
}
|
||||
invisible(oldValue)
|
||||
},
|
||||
suspend = function() {
|
||||
"Causes this observer to stop scheduling flushes (re-executions) in
|
||||
response to invalidations. If the observer was invalidated prior to this
|
||||
@@ -535,31 +578,48 @@ Observer <- setRefClass(
|
||||
.onResume <<- function() NULL
|
||||
}
|
||||
invisible()
|
||||
},
|
||||
destroy = function() {
|
||||
"Prevents this observer from ever executing again (even if a flush has
|
||||
already been scheduled)."
|
||||
|
||||
suspend()
|
||||
.destroyed <<- TRUE
|
||||
},
|
||||
.onDomainEnded = function() {
|
||||
if (isTRUE(.autoDestroy)) {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#' Create a reactive observer
|
||||
#'
|
||||
#'
|
||||
#' Creates an observer from the given expression.
|
||||
#'
|
||||
#' An observer is like a reactive
|
||||
#' expression in that it can read reactive values and call reactive expressions, and
|
||||
#' will automatically re-execute when those dependencies change. But unlike
|
||||
#' reactive expressions, it doesn't yield a result and can't be used as an input
|
||||
#' to other reactive expressions. Thus, observers are only useful for their side
|
||||
#' effects (for example, performing I/O).
|
||||
#'
|
||||
#' Another contrast between reactive expressions and observers is their execution
|
||||
#' strategy. Reactive expressions use lazy evaluation; that is, when their
|
||||
#' dependencies change, they don't re-execute right away but rather wait until
|
||||
#' they are called by someone else. Indeed, if they are not called then they
|
||||
#' will never re-execute. In contrast, observers use eager evaluation; as soon
|
||||
#' as their dependencies change, they schedule themselves to re-execute.
|
||||
#'
|
||||
#' @param x An expression (quoted or unquoted). Any return value will be ignored.
|
||||
#' @param env The parent environment for the reactive expression. By default, this
|
||||
#' is the calling environment, the same as when defining an ordinary
|
||||
#'
|
||||
#' An observer is like a reactive expression in that it can read reactive values
|
||||
#' and call reactive expressions, and will automatically re-execute when those
|
||||
#' dependencies change. But unlike reactive expressions, it doesn't yield a
|
||||
#' result and can't be used as an input to other reactive expressions. Thus,
|
||||
#' observers are only useful for their side effects (for example, performing
|
||||
#' I/O).
|
||||
#'
|
||||
#' Another contrast between reactive expressions and observers is their
|
||||
#' execution strategy. Reactive expressions use lazy evaluation; that is, when
|
||||
#' their dependencies change, they don't re-execute right away but rather wait
|
||||
#' until they are called by someone else. Indeed, if they are not called then
|
||||
#' they will never re-execute. In contrast, observers use eager evaluation; as
|
||||
#' soon as their dependencies change, they schedule themselves to re-execute.
|
||||
#'
|
||||
#' Starting with Shiny 0.10.0, observers are automatically destroyed by default
|
||||
#' when the \link[=domains]{domain} that owns them ends (e.g. when a Shiny session
|
||||
#' ends).
|
||||
#'
|
||||
#' @param x An expression (quoted or unquoted). Any return value will be
|
||||
#' ignored.
|
||||
#' @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
|
||||
@@ -569,14 +629,17 @@ Observer <- setRefClass(
|
||||
#' 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.
|
||||
#' will always execute sooner than all observers with a lower priority level.
|
||||
#' Positive, negative, and zero values are allowed.
|
||||
#' @return An observer reference class object. This object has the following
|
||||
#' @param domain See \link{domains}.
|
||||
#' @param autoDestroy If \code{TRUE} (the default), the observer will be
|
||||
#' automatically destroyed when its domain (if any) ends.
|
||||
#' @return An observer reference class object. This object has the following
|
||||
#' methods:
|
||||
#' \describe{
|
||||
#' \item{\code{suspend()}}{
|
||||
#' Causes this observer to stop scheduling flushes (re-executions) in
|
||||
#' response to invalidations. If the observer was invalidated prior to
|
||||
#' response to invalidations. If the observer was invalidated prior to
|
||||
#' this call but it has not re-executed yet then that re-execution will
|
||||
#' still occur, because the flush is already scheduled.
|
||||
#' }
|
||||
@@ -585,12 +648,21 @@ Observer <- setRefClass(
|
||||
#' invalidations. If the observer was invalidated while suspended, then it
|
||||
#' will schedule itself for re-execution.
|
||||
#' }
|
||||
#' \item{\code{destroy()}}{
|
||||
#' Stops the observer from executing ever again, even if it is currently
|
||||
#' scheduled for re-execution.
|
||||
#' }
|
||||
#' \item{\code{setPriority(priority = 0)}}{
|
||||
#' Change this observer's priority. Note that if the observer is currently
|
||||
#' Change this observer's priority. Note that if the observer is currently
|
||||
#' invalidated, then the change in priority will not take effect until the
|
||||
#' next invalidation--unless the observer is also currently suspended, in
|
||||
#' next invalidation--unless the observer is also currently suspended, in
|
||||
#' which case the priority change will be effective upon resume.
|
||||
#' }
|
||||
#' \item{\code{setAutoDestroy(autoDestroy)}}{
|
||||
#' 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."
|
||||
#' }
|
||||
#' \item{\code{onInvalidate(callback)}}{
|
||||
#' Register a callback function to run when this observer is invalidated.
|
||||
#' No arguments will be provided to the callback function when it is
|
||||
@@ -618,30 +690,32 @@ Observer <- setRefClass(
|
||||
#'
|
||||
#' @export
|
||||
observe <- function(x, env=parent.frame(), quoted=FALSE, label=NULL,
|
||||
suspended=FALSE, priority=0) {
|
||||
suspended=FALSE, priority=0,
|
||||
domain=getDefaultReactiveDomain(), autoDestroy = 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)
|
||||
o <- Observer$new(fun, label=label, suspended=suspended, priority=priority,
|
||||
domain=domain, autoDestroy=autoDestroy)
|
||||
registerDebugHook(".func", o, "Observer")
|
||||
invisible(o)
|
||||
}
|
||||
|
||||
#' Make a reactive variable
|
||||
#'
|
||||
#' Turns a normal variable into a reactive variable, that is, one that has
|
||||
#' reactive semantics when assigned or read in the usual ways. The variable may
|
||||
#' already exist; if so, its value will be used as the initial value of the
|
||||
#'
|
||||
#' Turns a normal variable into a reactive variable, that is, one that has
|
||||
#' reactive semantics when assigned or read in the usual ways. The variable may
|
||||
#' already exist; if so, its value will be used as the initial value of the
|
||||
#' reactive variable (or \code{NULL} if the variable did not exist).
|
||||
#'
|
||||
#' @param symbol A character string indicating the name of the variable that
|
||||
#'
|
||||
#' @param symbol A character string indicating the name of the variable that
|
||||
#' should be made reactive
|
||||
#' @param env The environment that will contain the reactive variable
|
||||
#'
|
||||
#'
|
||||
#' @return None.
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' a <- 10
|
||||
@@ -649,7 +723,7 @@ observe <- function(x, env=parent.frame(), quoted=FALSE, label=NULL,
|
||||
#' b <- reactive(a * -1)
|
||||
#' observe(print(b()))
|
||||
#' a <- 20
|
||||
#' }
|
||||
#' }
|
||||
#' @export
|
||||
makeReactiveBinding <- function(symbol, env = parent.frame()) {
|
||||
if (exists(symbol, where = env, inherits = FALSE)) {
|
||||
@@ -665,7 +739,7 @@ makeReactiveBinding <- function(symbol, env = parent.frame()) {
|
||||
else
|
||||
values$value <- v
|
||||
})
|
||||
|
||||
|
||||
invisible()
|
||||
}
|
||||
|
||||
@@ -680,12 +754,12 @@ makeReactiveBinding <- function(symbol, env = parent.frame()) {
|
||||
# entered into the top-level prompt
|
||||
setAutoflush <- local({
|
||||
callbackId <- NULL
|
||||
|
||||
|
||||
function(enable) {
|
||||
if (xor(is.null(callbackId), isTRUE(enable))) {
|
||||
return(invisible())
|
||||
}
|
||||
|
||||
|
||||
if (isTRUE(enable)) {
|
||||
callbackId <<- addTaskCallback(function(expr, value, ok, visible) {
|
||||
timerCallbacks$executeElapsed()
|
||||
@@ -703,26 +777,26 @@ setAutoflush <- local({
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
#' Timer
|
||||
#'
|
||||
#' Creates a reactive timer with the given interval. A reactive timer is like a
|
||||
#'
|
||||
#' Creates a reactive timer with the given interval. A reactive timer is like a
|
||||
#' reactive value, except reactive values are triggered when they are set, while
|
||||
#' reactive timers are triggered simply by the passage of time.
|
||||
#'
|
||||
#' \link[=reactive]{Reactive expressions} and observers that want to be
|
||||
#' invalidated by the timer need to call the timer function that
|
||||
#' \code{reactiveTimer} returns, even if the current time value is not actually
|
||||
#'
|
||||
#' \link[=reactive]{Reactive expressions} and observers that want to be
|
||||
#' invalidated by the timer need to call the timer function that
|
||||
#' \code{reactiveTimer} returns, even if the current time value is not actually
|
||||
#' needed.
|
||||
#'
|
||||
#'
|
||||
#' See \code{\link{invalidateLater}} as a safer and simpler alternative.
|
||||
#'
|
||||
#'
|
||||
#' @param intervalMs How often to fire, in milliseconds
|
||||
#' @param session A session object. This is needed to cancel any scheduled
|
||||
#' invalidations after a user has ended the session. If \code{NULL}, then
|
||||
#' this invalidation will not be tied to any session, and so it will still
|
||||
#' occur.
|
||||
#' @return A no-parameter function that can be called from a reactive context,
|
||||
#' in order to cause that context to be invalidated the next time the timer
|
||||
#' interval elapses. Calling the returned function also happens to yield the
|
||||
#' @return A no-parameter function that can be called from a reactive context,
|
||||
#' in order to cause that context to be invalidated the next time the timer
|
||||
#' interval elapses. Calling the returned function also happens to yield the
|
||||
#' current time (as in \code{\link{Sys.time}}).
|
||||
#' @seealso \code{\link{invalidateLater}}
|
||||
#'
|
||||
@@ -789,8 +863,8 @@ reactiveTimer <- function(intervalMs=1000, session) {
|
||||
}
|
||||
|
||||
#' Scheduled Invalidation
|
||||
#'
|
||||
#' Schedules the current reactive context to be invalidated in the given number
|
||||
#'
|
||||
#' Schedules the current reactive context to be invalidated in the given number
|
||||
#' of milliseconds.
|
||||
#'
|
||||
#' If this is placed within an observer or reactive expression, that object will
|
||||
@@ -860,52 +934,52 @@ coerceToFunc <- function(x) {
|
||||
}
|
||||
|
||||
#' Reactive polling
|
||||
#'
|
||||
#' Used to create a reactive data source, which works by periodically polling a
|
||||
#'
|
||||
#' Used to create a reactive data source, which works by periodically polling a
|
||||
#' non-reactive data source.
|
||||
#'
|
||||
#'
|
||||
#' \code{reactivePoll} works by pairing a relatively cheap "check" function with
|
||||
#' a more expensive value retrieval function. The check function will be
|
||||
#' executed periodically and should always return a consistent value until the
|
||||
#' data changes. When the check function returns a different value, then the
|
||||
#' a more expensive value retrieval function. The check function will be
|
||||
#' executed periodically and should always return a consistent value until the
|
||||
#' data changes. When the check function returns a different value, then the
|
||||
#' value retrieval function will be used to re-populate the data.
|
||||
#'
|
||||
#' Note that the check function doesn't return \code{TRUE} or \code{FALSE} to
|
||||
#' indicate whether the underlying data has changed. Rather, the check function
|
||||
#'
|
||||
#' Note that the check function doesn't return \code{TRUE} or \code{FALSE} to
|
||||
#' indicate whether the underlying data has changed. Rather, the check function
|
||||
#' indicates change by returning a different value from the previous time it was
|
||||
#' called.
|
||||
#'
|
||||
#' For example, \code{reactivePoll} is used to implement
|
||||
#'
|
||||
#' For example, \code{reactivePoll} is used to implement
|
||||
#' \code{reactiveFileReader} by pairing a check function that simply returns the
|
||||
#' last modified timestamp of a file, and a value retrieval function that
|
||||
#' last modified timestamp of a file, and a value retrieval function that
|
||||
#' actually reads the contents of the file.
|
||||
#'
|
||||
#' As another example, one might read a relational database table reactively by
|
||||
#' using a check function that does \code{SELECT MAX(timestamp) FROM table} and
|
||||
#'
|
||||
#' As another example, one might read a relational database table reactively by
|
||||
#' using a check function that does \code{SELECT MAX(timestamp) FROM table} and
|
||||
#' a value retrieval function that does \code{SELECT * FROM table}.
|
||||
#'
|
||||
#'
|
||||
#' The \code{intervalMillis}, \code{checkFunc}, and \code{valueFunc} functions
|
||||
#' will be executed in a reactive context; therefore, they may read reactive
|
||||
#' values and reactive expressions.
|
||||
#'
|
||||
#' @param intervalMillis Approximate number of milliseconds to wait between
|
||||
#' calls to \code{checkFunc}. This can be either a numeric value, or a
|
||||
#'
|
||||
#' @param intervalMillis Approximate number of milliseconds to wait between
|
||||
#' calls to \code{checkFunc}. This can be either a numeric value, or a
|
||||
#' function that returns a numeric value.
|
||||
#' @param session The user session to associate this file reader with, or
|
||||
#' \code{NULL} if none. If non-null, the reader will automatically stop when
|
||||
#' @param session The user session to associate this file reader with, or
|
||||
#' \code{NULL} if none. If non-null, the reader will automatically stop when
|
||||
#' the session ends.
|
||||
#' @param checkFunc A relatively cheap function whose values over time will be
|
||||
#' tested for equality; inequality indicates that the underlying value has
|
||||
#' @param checkFunc A relatively cheap function whose values over time will be
|
||||
#' tested for equality; inequality indicates that the underlying value has
|
||||
#' changed and needs to be invalidated and re-read using \code{valueFunc}. See
|
||||
#' Details.
|
||||
#' @param valueFunc A function that calculates the underlying value. See
|
||||
#' @param valueFunc A function that calculates the underlying value. See
|
||||
#' Details.
|
||||
#'
|
||||
#' @return A reactive expression that returns the result of \code{valueFunc},
|
||||
#'
|
||||
#' @return A reactive expression that returns the result of \code{valueFunc},
|
||||
#' and invalidates when \code{checkFunc} changes.
|
||||
#'
|
||||
#'
|
||||
#' @seealso \code{\link{reactiveFileReader}}
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Assume the existence of readTimestamp and readValue functions
|
||||
@@ -916,74 +990,74 @@ coerceToFunc <- function(x) {
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
|
||||
intervalMillis <- coerceToFunc(intervalMillis)
|
||||
|
||||
|
||||
rv <- reactiveValues(cookie = isolate(checkFunc()))
|
||||
|
||||
|
||||
observe({
|
||||
rv$cookie <- checkFunc()
|
||||
invalidateLater(intervalMillis(), session)
|
||||
})
|
||||
|
||||
|
||||
# TODO: what to use for a label?
|
||||
re <- reactive({
|
||||
rv$cookie
|
||||
|
||||
|
||||
valueFunc()
|
||||
|
||||
|
||||
}, label = NULL)
|
||||
|
||||
|
||||
return(re)
|
||||
}
|
||||
|
||||
#' Reactive file reader
|
||||
#'
|
||||
#' Given a file path and read function, returns a reactive data source for the
|
||||
#'
|
||||
#' Given a file path and read function, returns a reactive data source for the
|
||||
#' contents of the file.
|
||||
#'
|
||||
#' \code{reactiveFileReader} works by periodically checking the file's last
|
||||
#' modified time; if it has changed, then the file is re-read and any reactive
|
||||
#'
|
||||
#' \code{reactiveFileReader} works by periodically checking the file's last
|
||||
#' modified time; if it has changed, then the file is re-read and any reactive
|
||||
#' dependents are invalidated.
|
||||
#'
|
||||
#' The \code{intervalMillis}, \code{filePath}, and \code{readFunc} functions
|
||||
#'
|
||||
#' The \code{intervalMillis}, \code{filePath}, and \code{readFunc} functions
|
||||
#' will each be executed in a reactive context; therefore, they may read
|
||||
#' reactive values and reactive expressions.
|
||||
#'
|
||||
#' @param intervalMillis Approximate number of milliseconds to wait between
|
||||
#' checks of the file's last modified time. This can be a numeric value, or a
|
||||
#'
|
||||
#' @param intervalMillis Approximate number of milliseconds to wait between
|
||||
#' checks of the file's last modified time. This can be a numeric value, or a
|
||||
#' function that returns a numeric value.
|
||||
#' @param session The user session to associate this file reader with, or
|
||||
#' \code{NULL} if none. If non-null, the reader will automatically stop when
|
||||
#' @param session The user session to associate this file reader with, or
|
||||
#' \code{NULL} if none. If non-null, the reader will automatically stop when
|
||||
#' the session ends.
|
||||
#' @param filePath The file path to poll against and to pass to \code{readFunc}.
|
||||
#' This can either be a single-element character vector, or a function that
|
||||
#' This can either be a single-element character vector, or a function that
|
||||
#' returns one.
|
||||
#' @param readFunc The function to use to read the file; must expect the first
|
||||
#' argument to be the file path to read. The return value of this function is
|
||||
#' @param readFunc The function to use to read the file; must expect the first
|
||||
#' argument to be the file path to read. The return value of this function is
|
||||
#' used as the value of the reactive file reader.
|
||||
#' @param ... Any additional arguments to pass to \code{readFunc} whenever it is
|
||||
#' invoked.
|
||||
#'
|
||||
#' @return A reactive expression that returns the contents of the file, and
|
||||
#' automatically invalidates when the file changes on disk (as determined by
|
||||
#'
|
||||
#' @return A reactive expression that returns the contents of the file, and
|
||||
#' automatically invalidates when the file changes on disk (as determined by
|
||||
#' last modified time).
|
||||
#'
|
||||
#'
|
||||
#' @seealso \code{\link{reactivePoll}}
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Per-session reactive file reader
|
||||
#' shinyServer(function(input, output, session)) {
|
||||
#' fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
|
||||
#'
|
||||
#'
|
||||
#' output$data <- renderTable({
|
||||
#' fileData()
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
#' # Cross-session reactive file reader. In this example, all sessions share
|
||||
#' # the same reader, so read.csv only gets executed once no matter how many
|
||||
#' # user sessions are connected.
|
||||
@@ -994,12 +1068,12 @@ reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
|
||||
#' })
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...) {
|
||||
filePath <- coerceToFunc(filePath)
|
||||
extraArgs <- list(...)
|
||||
|
||||
|
||||
reactivePoll(
|
||||
intervalMillis, session,
|
||||
function() {
|
||||
@@ -1014,18 +1088,18 @@ reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...)
|
||||
}
|
||||
|
||||
#' Create a non-reactive scope for an expression
|
||||
#'
|
||||
#' Executes the given expression in a scope where reactive values or expression
|
||||
#' can be read, but they cannot cause the reactive scope of the caller to be
|
||||
#'
|
||||
#' Executes the given expression in a scope where reactive values or expression
|
||||
#' can be read, but they cannot cause the reactive scope of the caller to be
|
||||
#' re-evaluated when they change.
|
||||
#'
|
||||
#' Ordinarily, the simple act of reading a reactive value causes a relationship
|
||||
#' to be established between the caller and the reactive value, where a change
|
||||
#' to the reactive value will cause the caller to re-execute. (The same applies
|
||||
#' for the act of getting a reactive expression's value.) The \code{isolate}
|
||||
#'
|
||||
#' Ordinarily, the simple act of reading a reactive value causes a relationship
|
||||
#' to be established between the caller and the reactive value, where a change
|
||||
#' to the reactive value will cause the caller to re-execute. (The same applies
|
||||
#' for the act of getting a reactive expression's value.) The \code{isolate}
|
||||
#' function lets you read a reactive value or expression without establishing this
|
||||
#' relationship.
|
||||
#'
|
||||
#'
|
||||
#' The expression given to \code{isolate()} is evaluated in the calling
|
||||
#' environment. This means that if you assign a variable inside the
|
||||
#' \code{isolate()}, its value will be visible outside of the \code{isolate()}.
|
||||
@@ -1037,20 +1111,20 @@ reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...)
|
||||
#' calls to the reactive expression with \code{isolate()}.
|
||||
#'
|
||||
#' @param expr An expression that can access reactive values or expressions.
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' observe({
|
||||
#' input$saveButton # Do take a dependency on input$saveButton
|
||||
#'
|
||||
#'
|
||||
#' # isolate a simple expression
|
||||
#' data <- get(isolate(input$dataset)) # No dependency on input$dataset
|
||||
#' writeToDatabase(data)
|
||||
#' })
|
||||
#'
|
||||
#'
|
||||
#' observe({
|
||||
#' input$saveButton # Do take a dependency on input$saveButton
|
||||
#'
|
||||
#'
|
||||
#' # isolate a whole block
|
||||
#' data <- isolate({
|
||||
#' a <- input$valueA # No dependency on input$valueA or input$valueB
|
||||
@@ -1085,9 +1159,28 @@ reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...)
|
||||
#'
|
||||
#' @export
|
||||
isolate <- function(expr) {
|
||||
ctx <- Context$new('[isolate]', type='isolate')
|
||||
ctx <- Context$new(getDefaultReactiveDomain(), '[isolate]', type='isolate')
|
||||
on.exit(ctx$invalidate())
|
||||
ctx$run(function() {
|
||||
expr
|
||||
})
|
||||
}
|
||||
|
||||
#' Evaluate an expression without a reactive context
|
||||
#'
|
||||
#' Temporarily blocks the current reactive context and evaluates the given
|
||||
#' expression. Any attempt to directly access reactive values or expressions in
|
||||
#' \code{expr} will give the same results as doing it at the top-level (by
|
||||
#' default, an error).
|
||||
#'
|
||||
#' @param expr An expression to evaluate.
|
||||
#' @return The value of \code{expr}.
|
||||
#'
|
||||
#' @seealso \code{\link{isolate}}
|
||||
#'
|
||||
#' @export
|
||||
maskReactiveContext <- function(expr) {
|
||||
.getReactiveEnvironment()$runWith(NULL, function() {
|
||||
expr
|
||||
})
|
||||
}
|
||||
|
||||
198
R/run-url.R
@@ -1,112 +1,22 @@
|
||||
#' Run a Shiny application from https://gist.github.com
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted on GitHub as a gist.
|
||||
#'
|
||||
#' @param gist The identifier of the gist. For example, if the gist is
|
||||
#' https://gist.github.com/jcheng5/3239667, then \code{3239667},
|
||||
#' \code{'3239667'}, and \code{'https://gist.github.com/jcheng5/3239667'}
|
||||
#' are all valid values.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGist(3239667)
|
||||
#' runGist("https://gist.github.com/jcheng5/3239667")
|
||||
#'
|
||||
#' # Old URL format without username
|
||||
#' runGist("https://gist.github.com/3239667")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runGist <- function(gist,
|
||||
port=NULL,
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive())) {
|
||||
|
||||
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
|
||||
sprintf('https://gist.github.com/%s/download', gist)
|
||||
} else if(grepl('^https://gist.github.com/([^/]+/)?([0-9a-f]+)$', gist)) {
|
||||
paste(gist, '/download', sep='')
|
||||
} else {
|
||||
stop('Unrecognized gist identifier format')
|
||||
}
|
||||
|
||||
runUrl(gistUrl, filetype=".tar.gz", subdir=NULL, port=port,
|
||||
launch.browser=launch.browser)
|
||||
}
|
||||
|
||||
|
||||
#' Run a Shiny application from a GitHub repository
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted in a GitHub repository.
|
||||
#'
|
||||
#' @param repo Name of the repository
|
||||
#' @param username GitHub username
|
||||
#' @param ref Desired git reference. Could be a commit, tag, or branch
|
||||
#' name. Defaults to \code{"master"}.
|
||||
#' @param subdir A subdirectory in the repository that contains the app. By
|
||||
#' default, this function will run an app from the top level of the repo, but
|
||||
#' you can use a path such as `\code{"inst/shinyapp"}.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGitHub("shiny_example", "rstudio")
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the repo
|
||||
#' runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runGitHub <- function(repo, username = getOption("github.user"),
|
||||
ref = "master", subdir = NULL, port = NULL,
|
||||
launch.browser = getOption('shiny.launch.browser', interactive())) {
|
||||
|
||||
if (is.null(ref)) {
|
||||
stop("Must specify either a ref. ")
|
||||
}
|
||||
|
||||
message("Downloading github repo(s) ",
|
||||
paste(repo, ref, sep = "/", collapse = ", "),
|
||||
" from ",
|
||||
paste(username, collapse = ", "))
|
||||
name <- paste(username, "-", repo, sep = "")
|
||||
|
||||
url <- paste("https://github.com/", username, "/", repo, "/archive/",
|
||||
ref, ".tar.gz", sep = "")
|
||||
|
||||
runUrl(url, subdir=subdir, port=port, launch.browser=launch.browser)
|
||||
}
|
||||
|
||||
|
||||
#' Run a Shiny application from a URL
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted at a downloadable
|
||||
#' URL. The Shiny application must be saved in a .zip, .tar, or .tar.gz file.
|
||||
#' The Shiny application files must be contained in a subdirectory in the
|
||||
#' archive. For example, the files might be \code{myapp/server.r} and
|
||||
#' \code{myapp/ui.r}.
|
||||
#'
|
||||
#' \code{runUrl()} downloads and launches a Shiny application that is hosted at
|
||||
#' a downloadable URL. The Shiny application must be saved in a .zip, .tar, or
|
||||
#' .tar.gz file. The Shiny application files must be contained in the root
|
||||
#' directory or a subdirectory in the archive. For example, the files might be
|
||||
#' \code{myapp/server.r} and \code{myapp/ui.r}. The functions \code{runGitHub()}
|
||||
#' and \code{runGist()} are based on \code{runUrl()}, using URL's from GitHub
|
||||
#' (\url{https://github.com}) and GitHub gists (\url{https://gist.github.com}),
|
||||
#' respectively.
|
||||
#' @param url URL of the application.
|
||||
#' @param filetype The file type (\code{".zip"}, \code{".tar"}, or
|
||||
#' \code{".tar.gz"}. Defaults to the file extension taken from the url.
|
||||
#' @param subdir A subdirectory in the repository that contains the app. By
|
||||
#' default, this function will run an app from the top level of the repo, but
|
||||
#' you can use a path such as `\code{"inst/shinyapp"}.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#'
|
||||
#' @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')
|
||||
@@ -115,10 +25,7 @@ runGitHub <- function(repo, username = getOption("github.user"),
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
#' subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, port = NULL,
|
||||
launch.browser = getOption('shiny.launch.browser', interactive())) {
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, ...) {
|
||||
|
||||
if (!is.null(subdir) && ".." %in% strsplit(subdir, '/')[[1]])
|
||||
stop("'..' not allowed in subdir")
|
||||
@@ -137,6 +44,8 @@ runUrl <- function(url, filetype = NULL, subdir = NULL, port = NULL,
|
||||
|
||||
message("Downloading ", url)
|
||||
filePath <- tempfile('shinyapp', fileext=fileext)
|
||||
fileDir <- tempfile('shinyapp')
|
||||
dir.create(fileDir, showWarnings = FALSE)
|
||||
if (download(url, filePath, mode = "wb", quiet = TRUE) != 0)
|
||||
stop("Failed to download URL ", url)
|
||||
on.exit(unlink(filePath))
|
||||
@@ -148,17 +57,78 @@ runUrl <- function(url, filetype = NULL, subdir = NULL, port = NULL,
|
||||
# 2) If the internal untar implementation is used, it chokes on the 'g'
|
||||
# type flag that github uses (to stash their commit hash info).
|
||||
# By using our own forked/modified untar2 we sidestep both issues.
|
||||
dirname <- untar2(filePath, list=TRUE)[1]
|
||||
untar2(filePath, exdir = dirname(filePath))
|
||||
first <- untar2(filePath, list=TRUE)[1]
|
||||
untar2(filePath, exdir = fileDir)
|
||||
|
||||
} else if (fileext == ".zip") {
|
||||
dirname <- as.character(unzip(filePath, list=TRUE)$Name[1])
|
||||
unzip(filePath, exdir = dirname(filePath))
|
||||
first <- as.character(unzip(filePath, list=TRUE)$Name)[1]
|
||||
unzip(filePath, exdir = fileDir)
|
||||
}
|
||||
on.exit(unlink(fileDir, recursive = TRUE), add = TRUE)
|
||||
|
||||
appdir <- file.path(fileDir, first)
|
||||
if (!file_test('-d', appdir)) appdir <- dirname(appdir)
|
||||
|
||||
if (!is.null(subdir)) appdir <- file.path(appdir, subdir)
|
||||
runApp(appdir, ...)
|
||||
}
|
||||
|
||||
#' @rdname runUrl
|
||||
#' @param gist The identifier of the gist. For example, if the gist is
|
||||
#' https://gist.github.com/jcheng5/3239667, then \code{3239667},
|
||||
#' \code{'3239667'}, and \code{'https://gist.github.com/jcheng5/3239667'} are
|
||||
#' all valid values.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGist(3239667)
|
||||
#' runGist("https://gist.github.com/jcheng5/3239667")
|
||||
#'
|
||||
#' # Old URL format without username
|
||||
#' runGist("https://gist.github.com/3239667")
|
||||
#' }
|
||||
#'
|
||||
runGist <- function(gist, ...) {
|
||||
|
||||
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
|
||||
sprintf('https://gist.github.com/%s/download', gist)
|
||||
} else if(grepl('^https://gist.github.com/([^/]+/)?([0-9a-f]+)$', gist)) {
|
||||
paste(gist, '/download', sep='')
|
||||
} else {
|
||||
stop('Unrecognized gist identifier format')
|
||||
}
|
||||
|
||||
appdir <- file.path(dirname(filePath), dirname)
|
||||
on.exit(unlink(appdir, recursive = TRUE), add = TRUE)
|
||||
|
||||
appsubdir <- ifelse(is.null(subdir), appdir, file.path(appdir, subdir))
|
||||
runApp(appsubdir, port=port, launch.browser=launch.browser)
|
||||
runUrl(gistUrl, filetype=".tar.gz", ...)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname runUrl
|
||||
#' @param repo Name of the repository.
|
||||
#' @param username GitHub username. If \code{repo} is of the form
|
||||
#' \code{"username/repo"}, \code{username} will be taken from \code{repo}.
|
||||
#' @param ref Desired git reference. Could be a commit, tag, or branch name.
|
||||
#' Defaults to \code{"master"}.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' 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/")
|
||||
#' }
|
||||
runGitHub <- function(repo, username = getOption("github.user"),
|
||||
ref = "master", subdir = NULL, ...) {
|
||||
|
||||
if (grepl('/', repo)) {
|
||||
res <- strsplit(repo, '/')[[1]]
|
||||
if (length(res) != 2) stop("'repo' must be of the form 'username/repo'")
|
||||
username <- res[1]
|
||||
repo <- res[2]
|
||||
}
|
||||
|
||||
url <- paste("https://github.com/", username, "/", repo, "/archive/",
|
||||
ref, ".tar.gz", sep = "")
|
||||
|
||||
runUrl(url, subdir=subdir, ...)
|
||||
}
|
||||
|
||||
806
R/server.R
Normal file
@@ -0,0 +1,806 @@
|
||||
#' @include globals.R
|
||||
|
||||
appsByToken <- Map$new()
|
||||
|
||||
# Create a map for input handlers and register the defaults.
|
||||
inputHandlers <- Map$new()
|
||||
|
||||
#' Register an Input Handler
|
||||
#'
|
||||
#' Adds an input handler for data of this type. When called, Shiny will use the
|
||||
#' function provided to refine the data passed back from the client (after being
|
||||
#' deserialized by RJSONIO) before making it available in the \code{input}
|
||||
#' variable of the \code{server.R} file.
|
||||
#'
|
||||
#' This function will register the handler for the duration of the R process
|
||||
#' (unless Shiny is explicitly reloaded). For that reason, the \code{type} used
|
||||
#' should be very specific to this package to minimize the risk of colliding
|
||||
#' with another Shiny package which might use this data type name. We recommend
|
||||
#' the format of "packageName.widgetName".
|
||||
#'
|
||||
#' Currently Shiny registers the following handlers: \code{shiny.matrix},
|
||||
#' \code{shiny.number}, and \code{shiny.date}.
|
||||
#'
|
||||
#' The \code{type} of a custom Shiny Input widget will be deduced using the
|
||||
#' \code{getType()} JavaScript function on the registered Shiny inputBinding.
|
||||
#' @param type The type for which the handler should be added -- should be a
|
||||
#' single-element character vector.
|
||||
#' @param fun The handler function. This is the function that will be used to
|
||||
#' parse the data delivered from the client before it is available in the
|
||||
#' \code{input} variable. The function will be called with the following three
|
||||
#' parameters:
|
||||
#' \enumerate{
|
||||
#' \item{The value of this input as provided by the client, deserialized
|
||||
#' using RJSONIO.}
|
||||
#' \item{The \code{shinysession} in which the input exists.}
|
||||
#' \item{The name of the input.}
|
||||
#' }
|
||||
#' @param force If \code{TRUE}, will overwrite any existing handler without
|
||||
#' warning. If \code{FALSE}, will throw an error if this class already has
|
||||
#' a handler defined.
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Register an input handler which rounds a input number to the nearest integer
|
||||
#' registerInputHandler("mypackage.validint", function(x, shinysession, name) {
|
||||
#' if (is.null(x)) return(NA)
|
||||
#' round(x)
|
||||
#' })
|
||||
#'
|
||||
#' ## On the Javascript side, the associated input binding must have a corresponding getType method:
|
||||
#' getType: function(el) {
|
||||
#' return "mypackage.validint";
|
||||
#' }
|
||||
#'
|
||||
#' }
|
||||
#' @seealso \code{\link{removeInputHandler}}
|
||||
#' @export
|
||||
registerInputHandler <- function(type, fun, force=FALSE){
|
||||
if (inputHandlers$containsKey(type) && !force){
|
||||
stop("There is already an input handler for type: ", type)
|
||||
}
|
||||
inputHandlers$set(type, fun)
|
||||
}
|
||||
|
||||
#' Deregister an Input Handler
|
||||
#'
|
||||
#' Removes an Input Handler. Rather than using the previously specified handler
|
||||
#' for data of this type, the default RJSONIO serialization will be used.
|
||||
#'
|
||||
#' @param type The type for which handlers should be removed.
|
||||
#' @return The handler previously associated with this \code{type}, if one
|
||||
#' existed. Otherwise, \code{NULL}.
|
||||
#' @seealso \code{\link{registerInputHandler}}
|
||||
#' @export
|
||||
removeInputHandler <- function(type){
|
||||
inputHandlers$remove(type)
|
||||
}
|
||||
|
||||
# Takes a list-of-lists and returns a matrix. The lists
|
||||
# must all be the same length. NULL is replaced by NA.
|
||||
registerInputHandler("shiny.matrix", function(data, ...) {
|
||||
if (length(data) == 0)
|
||||
return(matrix(nrow=0, ncol=0))
|
||||
|
||||
m <- matrix(unlist(lapply(data, function(x) {
|
||||
sapply(x, function(y) {
|
||||
ifelse(is.null(y), NA, y)
|
||||
})
|
||||
})), nrow = length(data[[1]]), ncol = length(data))
|
||||
return(m)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.number", function(val, ...){
|
||||
ifelse(is.null(val), NA, val)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.date", function(val, ...){
|
||||
# First replace NULLs with NA, then convert to Date vector
|
||||
datelist <- ifelse(lapply(val, is.null), NA, val)
|
||||
as.Date(unlist(datelist))
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.action", function(val, ...) {
|
||||
# mark up the action button value with a special class so we can recognize it later
|
||||
class(val) <- c(class(val), "shinyActionButtonValue")
|
||||
val
|
||||
})
|
||||
|
||||
# Provide a character representation of the WS that can be used
|
||||
# as a key in a Map.
|
||||
wsToKey <- function(WS) {
|
||||
as.character(WS$socket)
|
||||
}
|
||||
|
||||
.globals$clients <- function(req) NULL
|
||||
|
||||
|
||||
clearClients <- function() {
|
||||
.globals$clients <- function(req) NULL
|
||||
}
|
||||
|
||||
|
||||
registerClient <- function(client) {
|
||||
.globals$clients <- append(.globals$clients, client)
|
||||
}
|
||||
|
||||
|
||||
.globals$resources <- list()
|
||||
|
||||
.globals$showcaseDefault <- 0
|
||||
|
||||
.globals$showcaseOverride <- FALSE
|
||||
|
||||
#' Resource Publishing
|
||||
#'
|
||||
#' Adds a directory of static resources to Shiny's web server, with the given
|
||||
#' path prefix. Primarily intended for package authors to make supporting
|
||||
#' JavaScript/CSS files available to their components.
|
||||
#'
|
||||
#' @param prefix The URL prefix (without slashes). Valid characters are a-z,
|
||||
#' A-Z, 0-9, hyphen, period, and underscore; and must begin with a-z or A-Z.
|
||||
#' For example, a value of 'foo' means that any request paths that begin with
|
||||
#' '/foo' will be mapped to the given directory.
|
||||
#' @param directoryPath The directory that contains the static resources to be
|
||||
#' served.
|
||||
#'
|
||||
#' @details You can call \code{addResourcePath} multiple times for a given
|
||||
#' \code{prefix}; only the most recent value will be retained. If the
|
||||
#' normalized \code{directoryPath} is different than the directory that's
|
||||
#' currently mapped to the \code{prefix}, a warning will be issued.
|
||||
#'
|
||||
#' @seealso \code{\link{singleton}}
|
||||
#'
|
||||
#' @examples
|
||||
#' addResourcePath('datasets', system.file('data', package='datasets'))
|
||||
#'
|
||||
#' @export
|
||||
addResourcePath <- function(prefix, directoryPath) {
|
||||
prefix <- prefix[1]
|
||||
if (!grepl('^[a-z][a-z0-9\\-_.]*$', prefix, ignore.case=TRUE, perl=TRUE)) {
|
||||
stop("addResourcePath called with invalid prefix; please see documentation")
|
||||
}
|
||||
|
||||
if (prefix %in% c('shared')) {
|
||||
stop("addResourcePath called with the reserved prefix '", prefix, "'; ",
|
||||
"please use a different prefix")
|
||||
}
|
||||
|
||||
directoryPath <- normalizePath(directoryPath, mustWork=TRUE)
|
||||
|
||||
existing <- .globals$resources[[prefix]]
|
||||
|
||||
if (!is.null(existing)) {
|
||||
if (!identical(existing$directoryPath, directoryPath)) {
|
||||
warning("Overriding existing prefix ", prefix, " => ",
|
||||
existing$directoryPath)
|
||||
}
|
||||
}
|
||||
|
||||
.globals$resources[[prefix]] <- list(directoryPath=directoryPath,
|
||||
func=staticHandler(directoryPath))
|
||||
}
|
||||
|
||||
resourcePathHandler <- function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
path <- req$PATH_INFO
|
||||
|
||||
match <- regexpr('^/([^/]+)/', path, perl=TRUE)
|
||||
if (match == -1)
|
||||
return(NULL)
|
||||
len <- attr(match, 'capture.length')
|
||||
prefix <- substr(path, 2, 2 + len - 1)
|
||||
|
||||
resInfo <- .globals$resources[[prefix]]
|
||||
if (is.null(resInfo))
|
||||
return(NULL)
|
||||
|
||||
suffix <- substr(path, 2 + len, nchar(path))
|
||||
|
||||
subreq <- as.environment(as.list(req, all.names=TRUE))
|
||||
subreq$PATH_INFO <- suffix
|
||||
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, substr(path, 1, 2 + len), sep='')
|
||||
|
||||
return(resInfo$func(subreq))
|
||||
}
|
||||
|
||||
#' Define Server Functionality
|
||||
#'
|
||||
#' Defines the server-side logic of the Shiny application. This generally
|
||||
#' involves creating functions that map user inputs to various kinds of output.
|
||||
#'
|
||||
#' @param func The server function for this application. See the details section
|
||||
#' for more information.
|
||||
#'
|
||||
#' @details
|
||||
#' Call \code{shinyServer} from your application's \code{server.R} file, passing
|
||||
#' in a "server function" that provides the server-side logic of your
|
||||
#' application.
|
||||
#'
|
||||
#' The server function will be called when each client (web browser) first loads
|
||||
#' the Shiny application's page. It must take an \code{input} and an
|
||||
#' \code{output} parameter. Any return value will be ignored. It also takes an
|
||||
#' optional \code{session} parameter, which is used when greater control is
|
||||
#' needed.
|
||||
#'
|
||||
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{tutorial} for more
|
||||
#' on how to write a server function.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # A very simple Shiny app that takes a message from the user
|
||||
#' # and outputs an uppercase version of it.
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' output$uppercase <- renderText({
|
||||
#' toupper(input$message)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
shinyServer <- function(func) {
|
||||
.globals$server <- list(func)
|
||||
invisible(func)
|
||||
}
|
||||
|
||||
decodeMessage <- function(data) {
|
||||
readInt <- function(pos) {
|
||||
packBits(rawToBits(data[pos:(pos+3)]), type='integer')
|
||||
}
|
||||
|
||||
if (readInt(1) != 0x01020202L) {
|
||||
# use native encoding for the message
|
||||
nativeData <- iconv(rawToChar(data), 'UTF-8')
|
||||
return(fromJSON(nativeData, asText=TRUE, simplify=FALSE))
|
||||
}
|
||||
|
||||
i <- 5
|
||||
parts <- list()
|
||||
while (i <= length(data)) {
|
||||
length <- readInt(i)
|
||||
i <- i + 4
|
||||
if (length != 0)
|
||||
parts <- append(parts, list(data[i:(i+length-1)]))
|
||||
else
|
||||
parts <- append(parts, list(raw(0)))
|
||||
i <- i + length
|
||||
}
|
||||
|
||||
mainMessage <- decodeMessage(parts[[1]])
|
||||
mainMessage$blobs <- parts[2:length(parts)]
|
||||
return(mainMessage)
|
||||
}
|
||||
|
||||
createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
appvars <- new.env()
|
||||
appvars$server <- NULL
|
||||
|
||||
sys.www.root <- system.file('www', package='shiny')
|
||||
|
||||
# This value, if non-NULL, must be present on all HTTP and WebSocket
|
||||
# requests as the Shiny-Shared-Secret header or else access will be
|
||||
# denied (403 response for HTTP, and instant close for websocket).
|
||||
sharedSecret <- getOption('shiny.sharedSecret')
|
||||
|
||||
appHandlers <- list(
|
||||
http = joinHandlers(c(
|
||||
sessionHandler,
|
||||
httpHandlers,
|
||||
sys.www.root,
|
||||
resourcePathHandler,
|
||||
reactLogHandler)),
|
||||
ws = function(ws) {
|
||||
if (!is.null(sharedSecret)
|
||||
&& !identical(sharedSecret, ws$request$HTTP_SHINY_SHARED_SECRET)) {
|
||||
ws$close()
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
shinysession <- ShinySession$new(ws)
|
||||
appsByToken$set(shinysession$token, shinysession)
|
||||
shinysession$setShowcase(.globals$showcaseDefault)
|
||||
|
||||
ws$onMessage(function(binary, msg) {
|
||||
# To ease transition from websockets-based code. Should remove once we're stable.
|
||||
if (is.character(msg))
|
||||
msg <- charToRaw(msg)
|
||||
|
||||
if (isTRUE(getOption('shiny.trace'))) {
|
||||
if (binary)
|
||||
message("RECV ", '$$binary data$$')
|
||||
else
|
||||
message("RECV ", rawToChar(msg))
|
||||
}
|
||||
|
||||
if (identical(charToRaw("\003\xe9"), msg))
|
||||
return()
|
||||
|
||||
msg <- decodeMessage(msg)
|
||||
|
||||
# Do our own list simplifying here. sapply/simplify2array give names to
|
||||
# character vectors, which is rarely what we want.
|
||||
if (!is.null(msg$data)) {
|
||||
for (name in names(msg$data)) {
|
||||
val <- msg$data[[name]]
|
||||
|
||||
splitName <- strsplit(name, ':')[[1]]
|
||||
if (length(splitName) > 1) {
|
||||
msg$data[[name]] <- NULL
|
||||
|
||||
if (!inputHandlers$containsKey(splitName[[2]])){
|
||||
# No input handler registered for this type
|
||||
stop("No handler registered for for type ", name)
|
||||
}
|
||||
|
||||
msg$data[[ splitName[[1]] ]] <-
|
||||
inputHandlers$get(splitName[[2]])(
|
||||
val,
|
||||
shinysession,
|
||||
splitName[[1]] )
|
||||
}
|
||||
else if (is.list(val) && is.null(names(val))) {
|
||||
val_flat <- unlist(val, recursive = TRUE)
|
||||
|
||||
if (is.null(val_flat)) {
|
||||
# This is to assign NULL instead of deleting the item
|
||||
msg$data[name] <- list(NULL)
|
||||
} else {
|
||||
msg$data[[name]] <- val_flat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(
|
||||
msg$method,
|
||||
init = {
|
||||
|
||||
serverFunc <- serverFuncSource()
|
||||
if (!identicalFunctionBodies(serverFunc, appvars$server)) {
|
||||
appvars$server <- serverFunc
|
||||
if (!is.null(appvars$server))
|
||||
{
|
||||
# Tag this function as the Shiny server function. A debugger may use this
|
||||
# tag to give this function special treatment.
|
||||
# It's very important that it's appvars$server itself and NOT a copy that
|
||||
# is invoked, otherwise new breakpoints won't be picked up.
|
||||
attr(appvars$server, "shinyServerFunction") <- TRUE
|
||||
registerDebugHook("server", appvars, "Server Function")
|
||||
}
|
||||
}
|
||||
|
||||
# Check for switching into/out of showcase mode
|
||||
if (.globals$showcaseOverride &&
|
||||
exists(".clientdata_url_search", where = msg$data)) {
|
||||
mode <- showcaseModeOfQuerystring(msg$data$.clientdata_url_search)
|
||||
if (!is.null(mode))
|
||||
shinysession$setShowcase(mode)
|
||||
}
|
||||
|
||||
shinysession$manageInputs(msg$data)
|
||||
|
||||
# The client tells us what singletons were rendered into
|
||||
# the initial page
|
||||
if (!is.null(msg$data$.clientdata_singletons)) {
|
||||
shinysession$singletons <<- strsplit(
|
||||
msg$data$.clientdata_singletons, ',')[[1]]
|
||||
}
|
||||
|
||||
local({
|
||||
args <- list(
|
||||
input=shinysession$input,
|
||||
output=.createOutputWriter(shinysession))
|
||||
|
||||
# The clientData and session arguments are optional; check if
|
||||
# each exists
|
||||
if ('clientData' %in% names(formals(serverFunc)))
|
||||
args$clientData <- shinysession$clientData
|
||||
|
||||
if ('session' %in% names(formals(serverFunc)))
|
||||
args$session <- shinysession$session
|
||||
|
||||
withReactiveDomain(shinysession$session, {
|
||||
do.call(appvars$server, args)
|
||||
})
|
||||
})
|
||||
},
|
||||
update = {
|
||||
shinysession$manageInputs(msg$data)
|
||||
},
|
||||
shinysession$dispatch(msg)
|
||||
)
|
||||
shinysession$manageHiddenOutputs()
|
||||
|
||||
if (exists(".shiny__stdout", globalenv()) &&
|
||||
exists("HTTP_GUID", ws$request)) {
|
||||
# safe to assume we're in shiny-server
|
||||
shiny_stdout <- get(".shiny__stdout", globalenv())
|
||||
|
||||
# eNter a flushReact
|
||||
writeLines(paste("_n_flushReact ", get("HTTP_GUID", ws$request),
|
||||
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
|
||||
sep=""), con=shiny_stdout)
|
||||
flush(shiny_stdout)
|
||||
|
||||
flushReact()
|
||||
|
||||
# eXit a flushReact
|
||||
writeLines(paste("_x_flushReact ", get("HTTP_GUID", ws$request),
|
||||
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
|
||||
sep=""), con=shiny_stdout)
|
||||
flush(shiny_stdout)
|
||||
} else {
|
||||
flushReact()
|
||||
}
|
||||
lapply(appsByToken$values(), function(shinysession) {
|
||||
shinysession$flushOutput()
|
||||
NULL
|
||||
})
|
||||
})
|
||||
|
||||
ws$onClose(function() {
|
||||
shinysession$close()
|
||||
appsByToken$remove(shinysession$token)
|
||||
})
|
||||
|
||||
return(TRUE)
|
||||
}
|
||||
)
|
||||
return(appHandlers)
|
||||
}
|
||||
|
||||
getEffectiveBody <- function(func) {
|
||||
# Note: NULL values are OK. isS4(NULL) returns FALSE, body(NULL)
|
||||
# returns NULL.
|
||||
if (isS4(func) && class(func) == "functionWithTrace")
|
||||
body(func@original)
|
||||
else
|
||||
body(func)
|
||||
}
|
||||
|
||||
identicalFunctionBodies <- function(a, b) {
|
||||
identical(getEffectiveBody(a), getEffectiveBody(b))
|
||||
}
|
||||
|
||||
handlerManager <- HandlerManager$new()
|
||||
|
||||
addSubApp <- function(appObj, autoRemove = TRUE) {
|
||||
path <- createUniqueId(16, "/app")
|
||||
appHandlers <- createAppHandlers(appObj$httpHandler, appObj$serverFuncSource)
|
||||
|
||||
# remove the leading / from the path so a relative path is returned
|
||||
# (needed for the case where the root URL for the Shiny app isn't /, such
|
||||
# as portmapped URLs)
|
||||
finalPath <- paste(
|
||||
substr(path, 2, nchar(path)),
|
||||
"/?w=", workerId(),
|
||||
"&__subapp__=1",
|
||||
sep="")
|
||||
handlerManager$addHandler(routeHandler(path, appHandlers$http), finalPath)
|
||||
handlerManager$addWSHandler(routeWSHandler(path, appHandlers$ws), finalPath)
|
||||
|
||||
if (autoRemove) {
|
||||
# If a session is currently active, remove this subapp automatically when
|
||||
# the current session ends
|
||||
onReactiveDomainEnded(getDefaultReactiveDomain(), function() {
|
||||
removeSubApp(finalPath)
|
||||
})
|
||||
}
|
||||
|
||||
return(finalPath)
|
||||
}
|
||||
|
||||
removeSubApp <- function(path) {
|
||||
handlerManager$removeHandler(path)
|
||||
handlerManager$removeWSHandler(path)
|
||||
}
|
||||
|
||||
startApp <- function(appObj, port, host, quiet) {
|
||||
appHandlers <- createAppHandlers(appObj$httpHandler, appObj$serverFuncSource)
|
||||
handlerManager$addHandler(appHandlers$http, "/", tail = TRUE)
|
||||
handlerManager$addWSHandler(appHandlers$ws, "/", tail = TRUE)
|
||||
|
||||
if (is.numeric(port) || is.integer(port)) {
|
||||
if (!quiet) {
|
||||
message('\n', 'Listening on http://', host, ':', port)
|
||||
}
|
||||
return(startServer(host, port, handlerManager$createHttpuvApp()))
|
||||
} else if (is.character(port)) {
|
||||
if (!quiet) {
|
||||
message('\n', 'Listening on domain socket ', port)
|
||||
}
|
||||
mask <- attr(port, 'mask')
|
||||
return(startPipeServer(port, mask, handlerManager$createHttpuvApp()))
|
||||
}
|
||||
}
|
||||
|
||||
# Run an application that was created by \code{\link{startApp}}. This
|
||||
# function should normally be called in a \code{while(TRUE)} loop.
|
||||
serviceApp <- function() {
|
||||
if (timerCallbacks$executeElapsed()) {
|
||||
for (shinysession in appsByToken$values()) {
|
||||
shinysession$manageHiddenOutputs()
|
||||
}
|
||||
|
||||
flushReact()
|
||||
|
||||
for (shinysession in appsByToken$values()) {
|
||||
shinysession$flushOutput()
|
||||
}
|
||||
}
|
||||
|
||||
# If this R session is interactive, then call service() with a short timeout
|
||||
# to keep the session responsive to user input
|
||||
maxTimeout <- ifelse(interactive(), 100, 1000)
|
||||
|
||||
timeout <- max(1, min(maxTimeout, timerCallbacks$timeToNextEvent()))
|
||||
service(timeout)
|
||||
}
|
||||
|
||||
.shinyServerMinVersion <- '0.3.4'
|
||||
|
||||
#' Run Shiny Application
|
||||
#'
|
||||
#' Runs a Shiny application. This function normally does not return; interrupt
|
||||
#' R to stop the application (usually by pressing Ctrl+C or Esc).
|
||||
#'
|
||||
#' The host parameter was introduced in Shiny 0.9.0. Its default value of
|
||||
#' \code{"127.0.0.1"} means that, contrary to previous versions of Shiny, only
|
||||
#' the current machine can access locally hosted Shiny apps. To allow other
|
||||
#' clients to connect, use the value \code{"0.0.0.0"} instead (which was the
|
||||
#' value that was hard-coded into Shiny in 0.8.0 and earlier).
|
||||
#'
|
||||
#' @param appDir The directory of the application. Should contain
|
||||
#' \code{server.R}, plus, either \code{ui.R} or a \code{www} directory that
|
||||
#' contains the file \code{index.html}. Defaults to the working directory.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only. This value of this parameter can also be a
|
||||
#' function to call with the application's URL.
|
||||
#' @param host The IPv4 address that the application should listen on. Defaults
|
||||
#' to the \code{shiny.host} option, if set, or \code{"127.0.0.1"} if not. See
|
||||
#' Details.
|
||||
#' @param workerId Can generally be ignored. Exists to help some editions of
|
||||
#' Shiny Server Pro route requests to the correct process.
|
||||
#' @param quiet Should Shiny status messages be shown? Defaults to FALSE.
|
||||
#' @param display.mode The mode in which to display the application. If set to
|
||||
#' the value \code{"showcase"}, shows application code and metadata from a
|
||||
#' \code{DESCRIPTION} file in the application directory alongside the
|
||||
#' application. If set to \code{"normal"}, displays the application normally.
|
||||
#' Defaults to \code{"auto"}, which displays the application in the mode
|
||||
#' given in its \code{DESCRIPTION} file, if any.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Start app in the current working directory
|
||||
#' runApp()
|
||||
#'
|
||||
#' # Start app in a subdirectory called myapp
|
||||
#' runApp("myapp")
|
||||
#'
|
||||
#'
|
||||
#' # Apps can be run without a server.r and ui.r file
|
||||
#' runApp(list(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' ))
|
||||
#' }
|
||||
#' @export
|
||||
runApp <- function(appDir=getwd(),
|
||||
port=NULL,
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
workerId="", quiet=FALSE,
|
||||
display.mode=c("auto", "normal", "showcase")) {
|
||||
on.exit({
|
||||
handlerManager$clear()
|
||||
}, add = TRUE)
|
||||
|
||||
|
||||
if (is.null(host) || is.na(host))
|
||||
host <- '0.0.0.0'
|
||||
|
||||
# Make warnings print immediately
|
||||
ops <- options(warn = 1)
|
||||
on.exit(options(ops), add = TRUE)
|
||||
|
||||
workerId(workerId)
|
||||
|
||||
if (nzchar(Sys.getenv('SHINY_PORT'))) {
|
||||
# If SHINY_PORT is set, we're running under Shiny Server. Check the version
|
||||
# to make sure it is compatible. Older versions of Shiny Server don't set
|
||||
# SHINY_SERVER_VERSION, those will return "" which is considered less than
|
||||
# any valid version.
|
||||
ver <- Sys.getenv('SHINY_SERVER_VERSION')
|
||||
if (compareVersion(ver, .shinyServerMinVersion) < 0) {
|
||||
warning('Shiny Server v', .shinyServerMinVersion,
|
||||
' or later is required; please upgrade!')
|
||||
}
|
||||
}
|
||||
|
||||
# Showcase mode is disabled by default; it must be explicitly enabled in
|
||||
# either the DESCRIPTION file for directory-based apps, or via
|
||||
# the display.mode parameter. The latter takes precedence.
|
||||
setShowcaseDefault(0)
|
||||
|
||||
# If appDir specifies a path, and display mode is specified in the
|
||||
# DESCRIPTION file at that path, apply it here.
|
||||
if (is.character(appDir)) {
|
||||
desc <- file.path.ci(appDir, "DESCRIPTION")
|
||||
if (file.exists(desc)) {
|
||||
con <- file(desc, encoding = checkEncoding(desc))
|
||||
on.exit(close(con), add = TRUE)
|
||||
settings <- read.dcf(con)
|
||||
if ("DisplayMode" %in% colnames(settings)) {
|
||||
mode <- settings[1,"DisplayMode"]
|
||||
if (mode == "Showcase") {
|
||||
setShowcaseDefault(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# If display mode is specified as an argument, apply it (overriding the
|
||||
# value specified in DESCRIPTION, if any).
|
||||
display.mode <- match.arg(display.mode)
|
||||
if (display.mode == "normal")
|
||||
setShowcaseDefault(0)
|
||||
else if (display.mode == "showcase")
|
||||
setShowcaseDefault(1)
|
||||
|
||||
require(shiny)
|
||||
|
||||
# determine port if we need to
|
||||
if (is.null(port)) {
|
||||
|
||||
# Try up to 20 random ports. If we don't succeed just plow ahead
|
||||
# with the final value we tried, and let the "real" startServer
|
||||
# somewhere down the line fail and throw the error to the user.
|
||||
#
|
||||
# If we (think we) succeed, save the value as .globals$lastPort,
|
||||
# and try that first next time the user wants a random port.
|
||||
|
||||
for (i in 1:20) {
|
||||
if (!is.null(.globals$lastPort)) {
|
||||
port <- .globals$lastPort
|
||||
.globals$lastPort <- NULL
|
||||
}
|
||||
else {
|
||||
# Try up to 20 random ports
|
||||
port <- p_randomInt(3000, 8000)
|
||||
}
|
||||
|
||||
# Test port to see if we can use it
|
||||
tmp <- try(startServer(host, port, list()), silent=TRUE)
|
||||
if (!inherits(tmp, 'try-error')) {
|
||||
stopServer(tmp)
|
||||
.globals$lastPort <- port
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appParts <- as.shiny.appobj(appDir)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
if (!is.null(appParts$onEnd))
|
||||
on.exit(appParts$onEnd(), add = TRUE)
|
||||
|
||||
server <- startApp(appParts, port, host, quiet)
|
||||
|
||||
on.exit({
|
||||
stopServer(server)
|
||||
}, add = TRUE)
|
||||
|
||||
if (!is.character(port)) {
|
||||
# http://0.0.0.0/ doesn't work on QtWebKit (i.e. RStudio viewer)
|
||||
browseHost <- if (identical(host, "0.0.0.0")) "127.0.0.1" else host
|
||||
|
||||
appUrl <- paste("http://", browseHost, ":", port, sep="")
|
||||
if (is.function(launch.browser))
|
||||
launch.browser(appUrl)
|
||||
else if (launch.browser)
|
||||
utils::browseURL(appUrl)
|
||||
} else {
|
||||
appUrl <- NULL
|
||||
}
|
||||
|
||||
# call application hooks
|
||||
callAppHook("onAppStart", appUrl)
|
||||
on.exit({
|
||||
callAppHook("onAppStop", appUrl)
|
||||
}, add = TRUE)
|
||||
|
||||
.globals$retval <- NULL
|
||||
.globals$stopped <- FALSE
|
||||
shinyCallingHandlers(
|
||||
while (!.globals$stopped) {
|
||||
serviceApp()
|
||||
Sys.sleep(0.001)
|
||||
}
|
||||
)
|
||||
|
||||
return(.globals$retval)
|
||||
}
|
||||
|
||||
#' Stop the currently running Shiny app
|
||||
#'
|
||||
#' Stops the currently running Shiny app, returning control to the caller of
|
||||
#' \code{\link{runApp}}.
|
||||
#'
|
||||
#' @param returnValue The value that should be returned from
|
||||
#' \code{\link{runApp}}.
|
||||
#'
|
||||
#' @export
|
||||
stopApp <- function(returnValue = NULL) {
|
||||
.globals$retval <- returnValue
|
||||
.globals$stopped <- TRUE
|
||||
httpuv::interrupt()
|
||||
}
|
||||
|
||||
#' Run Shiny Example Applications
|
||||
#'
|
||||
#' Launch Shiny example applications, and optionally, your system's web browser.
|
||||
#'
|
||||
#' @param example The name of the example to run, or \code{NA} (the default) to
|
||||
#' list the available examples.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#' @param host The IPv4 address that the application should listen on. Defaults
|
||||
#' to the \code{shiny.host} option, if set, or \code{"127.0.0.1"} if not.
|
||||
#' @param display.mode The mode in which to display the example. Defaults to
|
||||
#' \code{showcase}, but may be set to \code{normal} to see the example without
|
||||
#' code or commentary.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # List all available examples
|
||||
#' runExample()
|
||||
#'
|
||||
#' # Run one of the examples
|
||||
#' runExample("01_hello")
|
||||
#'
|
||||
#' # Print the directory containing the code for all examples
|
||||
#' system.file("examples", package="shiny")
|
||||
#' }
|
||||
#' @export
|
||||
runExample <- function(example=NA,
|
||||
port=NULL,
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
display.mode=c("auto", "normal", "showcase")) {
|
||||
examplesDir <- system.file('examples', package='shiny')
|
||||
dir <- resolve(examplesDir, example)
|
||||
if (is.null(dir)) {
|
||||
if (is.na(example)) {
|
||||
errFun <- message
|
||||
errMsg <- ''
|
||||
}
|
||||
else {
|
||||
errFun <- stop
|
||||
errMsg <- paste('Example', example, 'does not exist. ')
|
||||
}
|
||||
|
||||
errFun(errMsg,
|
||||
'Valid examples are "',
|
||||
paste(list.files(examplesDir), collapse='", "'),
|
||||
'"')
|
||||
}
|
||||
else {
|
||||
runApp(dir, port = port, host = host, launch.browser = launch.browser,
|
||||
display.mode = display.mode)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
# Keeps the context associated with a ShinySession reference object for the
|
||||
# duration of a request. Used to emit reactive evaluation information to the
|
||||
# appropriate session when showcase mode is enabled.
|
||||
|
||||
.sessionContext <- new.env(parent=emptyenv())
|
||||
.beginShowcaseSessionContext <- function(session) {
|
||||
assign("session", session, envir = .sessionContext)
|
||||
}
|
||||
|
||||
.endShowcaseSessionContext <- function() {
|
||||
if (exists("session", where = .sessionContext))
|
||||
remove("session", envir = .sessionContext)
|
||||
}
|
||||
|
||||
.getShowcaseSessionContext <- function() {
|
||||
if (exists("session", where = .sessionContext))
|
||||
.sessionContext$session
|
||||
else
|
||||
NULL
|
||||
}
|
||||
298
R/shinyui.R
@@ -1,160 +1,66 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
#' @export
|
||||
p <- function(...) tags$p(...)
|
||||
|
||||
#' @export
|
||||
h1 <- function(...) tags$h1(...)
|
||||
|
||||
#' @export
|
||||
h2 <- function(...) tags$h2(...)
|
||||
|
||||
#' @export
|
||||
h3 <- function(...) tags$h3(...)
|
||||
|
||||
#' @export
|
||||
h4 <- function(...) tags$h4(...)
|
||||
|
||||
#' @export
|
||||
h5 <- function(...) tags$h5(...)
|
||||
|
||||
#' @export
|
||||
h6 <- function(...) tags$h6(...)
|
||||
|
||||
#' @export
|
||||
a <- function(...) tags$a(...)
|
||||
|
||||
#' @export
|
||||
br <- function(...) tags$br(...)
|
||||
|
||||
#' @export
|
||||
div <- function(...) tags$div(...)
|
||||
|
||||
#' @export
|
||||
span <- function(...) tags$span(...)
|
||||
|
||||
#' @export
|
||||
pre <- function(...) tags$pre(...)
|
||||
|
||||
#' @export
|
||||
code <- function(...) tags$code(...)
|
||||
|
||||
#' @export
|
||||
img <- function(...) tags$img(...)
|
||||
|
||||
#' @export
|
||||
strong <- function(...) tags$strong(...)
|
||||
|
||||
#' @export
|
||||
em <- function(...) tags$em(...)
|
||||
|
||||
#' @export
|
||||
hr <- function(...) tags$hr(...)
|
||||
|
||||
#' Include Content From a File
|
||||
#'
|
||||
#' Include HTML, text, or rendered Markdown into a \link[=shinyUI]{Shiny UI}.
|
||||
#'
|
||||
#' These functions provide a convenient way to include an extensive amount of
|
||||
#' HTML, textual, Markdown, CSS, or JavaScript content, rather than using a
|
||||
#' large literal R string.
|
||||
#'
|
||||
#' @note \code{includeText} escapes its contents, but does no other processing.
|
||||
#' This means that hard breaks and multiple spaces will be rendered as they
|
||||
#' usually are in HTML: as a single space character. If you are looking for
|
||||
#' preformatted text, wrap the call with \code{\link{pre}}, or consider using
|
||||
#' \code{includeMarkdown} instead.
|
||||
#'
|
||||
#' @note The \code{includeMarkdown} function requires the \code{markdown}
|
||||
#' package.
|
||||
#'
|
||||
#' @param path The path of the file to be included. It is highly recommended to
|
||||
#' use a relative path (the base path being the Shiny application directory),
|
||||
#' not an absolute path.
|
||||
#'
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeHTML <- function(path) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
return(HTML(paste(lines, collapse='\r\n')))
|
||||
}
|
||||
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeText <- function(path) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
return(paste(lines, collapse='\r\n'))
|
||||
}
|
||||
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeMarkdown <- function(path) {
|
||||
if (!require(markdown))
|
||||
stop("Markdown package is not installed")
|
||||
|
||||
dependsOnFile(path)
|
||||
html <- markdown::markdownToHTML(path, fragment.only=TRUE)
|
||||
Encoding(html) <- 'UTF-8'
|
||||
return(HTML(html))
|
||||
}
|
||||
|
||||
#' @param ... Any additional attributes to be applied to the generated tag.
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeCSS <- function(path, ...) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
args <- list(...)
|
||||
if (is.null(args$type))
|
||||
args$type <- 'text/css'
|
||||
return(do.call(tags$style,
|
||||
c(list(HTML(paste(lines, collapse='\r\n'))), args)))
|
||||
}
|
||||
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeScript <- function(path, ...) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
return(tags$script(HTML(paste(lines, collapse='\r\n')), ...))
|
||||
}
|
||||
|
||||
#' Include Content Only Once
|
||||
#'
|
||||
#' Use \code{singleton} to wrap contents (tag, text, HTML, or lists) that should
|
||||
#' be included in the generated document only once, yet may appear in the
|
||||
#' document-generating code more than once. Only the first appearance of the
|
||||
#' content (in document order) will be used. Useful for custom components that
|
||||
#' have JavaScript files or stylesheets.
|
||||
#'
|
||||
#' @param x A \code{\link{tag}}, text, \code{\link{HTML}}, or list.
|
||||
#' Load the MathJax library and typeset math expressions
|
||||
#'
|
||||
#' This function adds MathJax to the page and typeset the math expressions (if
|
||||
#' found) in the content \code{...}. It only needs to be called once in an app
|
||||
#' unless the content is rendered \emph{after} the page is loaded, e.g. via
|
||||
#' \code{\link{renderUI}}, in which case we have to call it explicitly every
|
||||
#' time we write math expressions to the output.
|
||||
#' @param ... any HTML elements to apply MathJax to
|
||||
#' @export
|
||||
singleton <- function(x) {
|
||||
class(x) <- c(class(x), 'shiny.singleton')
|
||||
return(x)
|
||||
#' @examples withMathJax(helpText("Some math here $$\\alpha+\\beta$$"))
|
||||
#' # now we can just write "static" content without withMathJax()
|
||||
#' div("more math here $$\\sqrt{2}$$")
|
||||
withMathJax <- function(...) {
|
||||
path <- 'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
|
||||
tagList(
|
||||
tags$head(
|
||||
singleton(tags$script(src = path, type = 'text/javascript'))
|
||||
),
|
||||
...,
|
||||
tags$script(HTML('MathJax.Hub.Queue(["Typeset", MathJax.Hub]);'))
|
||||
)
|
||||
}
|
||||
|
||||
renderPage <- function(ui, connection, showcase=0) {
|
||||
|
||||
|
||||
if (showcase > 0)
|
||||
ui <- tagList(tags$head(showcaseHead()), 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"/>',
|
||||
' <script src="shared/jquery.js" type="text/javascript"></script>',
|
||||
' <script src="shared/shiny.js" type="text/javascript"></script>',
|
||||
' <link rel="stylesheet" type="text/css" href="shared/shiny.css"/>',
|
||||
sprintf(' <script type="application/shiny-singletons">%s</script>',
|
||||
paste(result$singletons, collapse = ',')
|
||||
)),
|
||||
),
|
||||
sprintf(' <script type="application/html-dependencies">%s</script>',
|
||||
depStr
|
||||
),
|
||||
depHtml
|
||||
),
|
||||
con = connection)
|
||||
if (showcase > 0) {
|
||||
writeLines(as.character(showcaseHead()), con = connection)
|
||||
}
|
||||
writeLines(c(result$head,
|
||||
'</head>',
|
||||
'<body>',
|
||||
@@ -163,13 +69,13 @@ renderPage <- function(ui, connection, showcase=0) {
|
||||
|
||||
if (showcase > 0) {
|
||||
# in showcase mode, emit containing elements and app HTML
|
||||
writeLines(as.character(showcaseBody(result$html)),
|
||||
writeLines(as.character(showcaseBody(result$html)),
|
||||
con = connection)
|
||||
} else {
|
||||
# in normal mode, write UI html directly to connection
|
||||
writeLines(result$html, con = connection)
|
||||
}
|
||||
|
||||
|
||||
# write end document
|
||||
writeLines(c('</body>',
|
||||
'</html>'),
|
||||
@@ -177,68 +83,52 @@ renderPage <- function(ui, connection, showcase=0) {
|
||||
}
|
||||
|
||||
#' Create a Shiny UI handler
|
||||
#'
|
||||
#' Register a UI handler by providing a UI definition (created with e.g.
|
||||
#' \link{pageWithSidebar}) and web server path (typically "/", the default
|
||||
#' value).
|
||||
#'
|
||||
#' @param ui A user-interace definition
|
||||
#' @param path The web server path to server the UI from
|
||||
#' @return Called for its side-effect of registering a UI handler
|
||||
#'
|
||||
#' @examples
|
||||
#' el <- div(HTML("I like <u>turtles</u>"))
|
||||
#' cat(as.character(el))
|
||||
#'
|
||||
#' @examples
|
||||
#' # Define UI
|
||||
#' shinyUI(pageWithSidebar(
|
||||
#'
|
||||
#' # Application title
|
||||
#' headerPanel("Hello Shiny!"),
|
||||
#'
|
||||
#' # Sidebar with a slider input
|
||||
#' sidebarPanel(
|
||||
#' sliderInput("obs",
|
||||
#' "Number of observations:",
|
||||
#' min = 0,
|
||||
#' max = 1000,
|
||||
#' value = 500)
|
||||
#' ),
|
||||
#'
|
||||
#' # Show a plot of the generated distribution
|
||||
#' mainPanel(
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' Historically this function was used in ui.R files to register a user
|
||||
#' interface with Shiny. It is no longer required; simply ensure that the last
|
||||
#' expression to be returned from ui.R is a user interface. This function is
|
||||
#' kept for backwards compatibility with older applications. It returns the
|
||||
#' value that is passed to it.
|
||||
#'
|
||||
#' @param ui A user interace definition
|
||||
#' @return The user interface definition, without modifications or side effects.
|
||||
#'
|
||||
#' @export
|
||||
shinyUI <- function(ui, path='/') {
|
||||
|
||||
force(ui)
|
||||
|
||||
registerClient({
|
||||
|
||||
function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
if (req$PATH_INFO != path)
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
on.exit(close(textConn))
|
||||
|
||||
showcaseMode <- .globals$showcaseDefault
|
||||
if (.globals$showcaseOverride) {
|
||||
mode <- showcaseModeOfReq(req)
|
||||
if (!is.null(mode))
|
||||
showcaseMode <- mode
|
||||
}
|
||||
renderPage(ui, textConn, showcaseMode)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(httpResponse(200, content=html))
|
||||
}
|
||||
})
|
||||
shinyUI <- function(ui) {
|
||||
.globals$ui <- list(ui)
|
||||
ui
|
||||
}
|
||||
|
||||
uiHttpHandler <- function(ui, path = "/") {
|
||||
|
||||
force(ui)
|
||||
|
||||
function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
if (req$PATH_INFO != path)
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
on.exit(close(textConn))
|
||||
|
||||
showcaseMode <- .globals$showcaseDefault
|
||||
if (.globals$showcaseOverride) {
|
||||
mode <- showcaseModeOfReq(req)
|
||||
if (!is.null(mode))
|
||||
showcaseMode <- mode
|
||||
}
|
||||
uiValue <- if (is.function(ui)) {
|
||||
if (length(formals(ui)) > 0)
|
||||
ui(req)
|
||||
else
|
||||
ui()
|
||||
}
|
||||
else
|
||||
ui
|
||||
renderPage(uiValue, textConn, showcaseMode)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(httpResponse(200, content=enc2utf8(html)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,48 @@
|
||||
suppressPackageStartupMessages({
|
||||
library(caTools)
|
||||
library(xtable)
|
||||
})
|
||||
globalVariables('func')
|
||||
|
||||
#' Mark a function as a render function
|
||||
#'
|
||||
#' Should be called by implementers of \code{renderXXX} functions in order to
|
||||
#' mark their return values as Shiny render functions, and to provide a hint to
|
||||
#' Shiny regarding what UI function is most commonly used with this type of
|
||||
#' render function. This can be used in R Markdown documents to create complete
|
||||
#' output widgets out of just the render function.
|
||||
#'
|
||||
#' @param uiFunc A function that renders Shiny UI. Must take a single argument:
|
||||
#' an output ID.
|
||||
#' @param renderFunc A function that is suitable for assigning to a Shiny output
|
||||
#' slot.
|
||||
#' @return The \code{renderFunc} function, with annotations.
|
||||
#'
|
||||
#' @export
|
||||
markRenderFunction <- function(uiFunc, renderFunc) {
|
||||
structure(renderFunc,
|
||||
class = c("shiny.render.function", "function"),
|
||||
outputFunc = uiFunc)
|
||||
}
|
||||
|
||||
useRenderFunction <- function(renderFunc, inline = FALSE) {
|
||||
outputFunction <- attr(renderFunc, "outputFunc")
|
||||
id <- createUniqueId(8, "out")
|
||||
o <- getDefaultReactiveDomain()$output
|
||||
if (!is.null(o))
|
||||
o[[id]] <- renderFunc
|
||||
if (is.logical(formals(outputFunction)[["inline"]])) {
|
||||
outputFunction(id, inline = inline)
|
||||
} else outputFunction(id)
|
||||
}
|
||||
|
||||
#' @export
|
||||
#' @method as.tags shiny.render.function
|
||||
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}
|
||||
#'
|
||||
#' 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}.
|
||||
#'
|
||||
@@ -16,27 +50,24 @@ globalVariables('func')
|
||||
#' the output, see \code{\link{plotPNG}}.
|
||||
#'
|
||||
#' @param expr An expression that generates a plot.
|
||||
#' @param width The width of the rendered plot, in pixels; or \code{'auto'} to
|
||||
#' use the \code{offsetWidth} of the HTML element that is bound to this plot.
|
||||
#' You can also pass in a function that returns the width in pixels or
|
||||
#' \code{'auto'}; in the body of the function you may reference reactive
|
||||
#' values and functions.
|
||||
#' @param height The height of the rendered plot, in pixels; or \code{'auto'} to
|
||||
#' use the \code{offsetHeight} of the HTML element that is bound to this plot.
|
||||
#' You can also pass in a function that returns the width in pixels or
|
||||
#' \code{'auto'}; in the body of the function you may reference reactive
|
||||
#' values and functions.
|
||||
#' @param 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}}.
|
||||
#' @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) {
|
||||
@@ -47,7 +78,7 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
}
|
||||
|
||||
args <- list(...)
|
||||
|
||||
|
||||
if (is.function(width))
|
||||
widthWrapper <- reactive({ width() })
|
||||
else
|
||||
@@ -58,21 +89,28 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
else
|
||||
heightWrapper <- NULL
|
||||
|
||||
return(function(shinysession, name, ...) {
|
||||
# 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
|
||||
# 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)
|
||||
|
||||
@@ -80,11 +118,16 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
pixelratio <- shinysession$clientData$pixelratio
|
||||
if (is.null(pixelratio))
|
||||
pixelratio <- 1
|
||||
|
||||
|
||||
coordmap <- NULL
|
||||
plotFunc <- function() {
|
||||
# Actually perform the plotting
|
||||
func()
|
||||
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')
|
||||
@@ -95,7 +138,7 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
if (par('ylog')) {
|
||||
usrBounds[c(3,4)] <- 10 ^ usrBounds[c(3,4)]
|
||||
}
|
||||
|
||||
|
||||
coordmap <<- list(
|
||||
usr = c(
|
||||
left = usrCoords[1],
|
||||
@@ -121,18 +164,18 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
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}
|
||||
#' Renders a reactive image that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#' The expression \code{expr} must return a list containing the attributes for
|
||||
@@ -221,8 +264,8 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
deleteFile=TRUE) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
return(function(shinysession, name, ...) {
|
||||
|
||||
return(markRenderFunction(imageOutput, function(shinysession, name, ...) {
|
||||
imageinfo <- func()
|
||||
# Should the file be deleted after being sent? If .deleteFile not set or if
|
||||
# TRUE, then delete; otherwise don't delete.
|
||||
@@ -243,28 +286,28 @@ 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}
|
||||
#'
|
||||
#' 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
|
||||
#'
|
||||
#' @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
|
||||
#' @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)) {
|
||||
@@ -273,85 +316,84 @@ renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL)
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
function() {
|
||||
classNames <- getOption('shiny.table.class', 'data table table-bordered table-condensed')
|
||||
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',
|
||||
print(xtable(data, ...),
|
||||
type='html',
|
||||
html.table.attributes=paste('class="',
|
||||
htmlEscape(classNames, TRUE),
|
||||
'"',
|
||||
sep=''), ...)),
|
||||
collapse="\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#' Printable Output
|
||||
#'
|
||||
#' Makes a reactive version of the given function that captures any printed
|
||||
#' output, and also captures its printable result (unless
|
||||
#' \code{\link{invisible}}), into a string. The resulting function is suitable
|
||||
#'
|
||||
#' Makes a reactive version of the given function that captures any printed
|
||||
#' output, and also captures its printable result (unless
|
||||
#' \code{\link{invisible}}), into a string. The resulting function is suitable
|
||||
#' for assigning to an \code{output} slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag can be anything (though \code{pre} is
|
||||
#'
|
||||
#' The corresponding HTML output tag can be anything (though \code{pre} is
|
||||
#' recommended if you need a monospace font and whitespace preserved) and should
|
||||
#' have the CSS class name \code{shiny-text-output}.
|
||||
#'
|
||||
#' The result of executing \code{func} will be printed inside a
|
||||
#'
|
||||
#' The result of executing \code{func} will be printed inside a
|
||||
#' \code{\link[utils]{capture.output}} call.
|
||||
#'
|
||||
#' Note that unlike most other Shiny output functions, if the given function
|
||||
#' returns \code{NULL} then \code{NULL} will actually be visible in the output.
|
||||
#'
|
||||
#' Note that unlike most other Shiny output functions, if the given function
|
||||
#' returns \code{NULL} then \code{NULL} will actually be visible in the output.
|
||||
#' To display nothing, make your function return \code{\link{invisible}()}.
|
||||
#'
|
||||
#' @param expr An expression that may print output and/or return a printable R
|
||||
#'
|
||||
#' @param expr An expression that may print output and/or return a printable R
|
||||
#' object.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' @param func A function that may print output and/or return a printable R
|
||||
#' @param func A function that may print output and/or return a printable R
|
||||
#' object (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @seealso \code{\link{renderText}} for displaying the value returned from a
|
||||
#' @param width The value for \code{\link{options}('width')}.
|
||||
#' @seealso \code{\link{renderText}} for displaying the value returned from a
|
||||
#' function, instead of the printed output.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
renderPrint <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
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)
|
||||
}
|
||||
|
||||
function() {
|
||||
return(paste(capture.output({
|
||||
result <- withVisible(func())
|
||||
if (result$visible)
|
||||
print(result$value)
|
||||
}), collapse="\n"))
|
||||
}
|
||||
markRenderFunction(verbatimTextOutput, function() {
|
||||
op <- options(width = width)
|
||||
on.exit(options(op), add = TRUE)
|
||||
paste(capture.output(func()), collapse = "\n")
|
||||
})
|
||||
}
|
||||
|
||||
#' Text Output
|
||||
#'
|
||||
#' Makes a reactive version of the given function that also uses
|
||||
#' \code{\link[base]{cat}} to turn its result into a single-element character
|
||||
#'
|
||||
#' Makes a reactive version of the given function that also uses
|
||||
#' \code{\link[base]{cat}} to turn its result into a single-element character
|
||||
#' vector.
|
||||
#'
|
||||
#' The corresponding HTML output tag can be anything (though \code{pre} is
|
||||
#'
|
||||
#' The corresponding HTML output tag can be anything (though \code{pre} is
|
||||
#' recommended if you need a monospace font and whitespace preserved) and should
|
||||
#' have the CSS class name \code{shiny-text-output}.
|
||||
#'
|
||||
#' The result of executing \code{func} will passed to \code{cat}, inside a
|
||||
#'
|
||||
#' The result of executing \code{func} will passed to \code{cat}, inside a
|
||||
#' \code{\link[utils]{capture.output}} call.
|
||||
#'
|
||||
#'
|
||||
#' @param expr An expression that returns an R object that can be used as an
|
||||
#' argument to \code{cat}.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
@@ -359,12 +401,12 @@ renderPrint <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns an R object that can be used as an
|
||||
#' argument to \code{cat}.(deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#'
|
||||
#' @seealso \code{\link{renderPrint}} for capturing the print output of a
|
||||
#' function, rather than the returned text value.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
@@ -373,36 +415,36 @@ renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
function() {
|
||||
markRenderFunction(textOutput, function() {
|
||||
value <- func()
|
||||
return(paste(capture.output(cat(value)), collapse="\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#' UI Output
|
||||
#'
|
||||
#'
|
||||
#' \bold{Experimental feature.} Makes a reactive version of a function that
|
||||
#' generates HTML using the Shiny UI library.
|
||||
#'
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} and have the CSS class
|
||||
#' name \code{shiny-html-output} (or use \code{\link{uiOutput}}).
|
||||
#'
|
||||
#' @param expr An expression that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#'
|
||||
#' @param expr An expression that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' or a list of such objects.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' @param func A function that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' or a list of such objects (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#'
|
||||
#' @seealso conditionalPanel
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' output$moreControls <- renderUI({
|
||||
#' list(
|
||||
#'
|
||||
#'
|
||||
#' )
|
||||
#' })
|
||||
#' }
|
||||
@@ -413,48 +455,51 @@ renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
function(shinysession, name, ...) {
|
||||
markRenderFunction(uiOutput, function(shinysession, name, ...) {
|
||||
result <- func()
|
||||
if (is.null(result) || length(result) == 0)
|
||||
return(NULL)
|
||||
|
||||
result <- takeSingletons(result, shinysession$singletons, desingleton=FALSE)$ui
|
||||
result <- surroundSingletons(result)
|
||||
dependencies <- lapply(resolveDependencies(findDependencies(result)),
|
||||
createWebDependency)
|
||||
names(dependencies) <- NULL
|
||||
|
||||
# renderTags returns a list with head, singletons, and html
|
||||
output <- renderTags(result, shinysession$singletons)
|
||||
shinysession$singletons <- output$singletons
|
||||
output$singletons <- NULL
|
||||
|
||||
# If there's stuff in head, then return a list; otherwise, just a string.
|
||||
if (isTRUE(nchar(output$head) > 0))
|
||||
return(output)
|
||||
else
|
||||
return(output$html)
|
||||
}
|
||||
output <- list(
|
||||
html = doRenderTags(result),
|
||||
deps = dependencies
|
||||
)
|
||||
|
||||
return(output)
|
||||
})
|
||||
}
|
||||
|
||||
#' File Downloads
|
||||
#'
|
||||
#'
|
||||
#' Allows content from the Shiny application to be made available to the user as
|
||||
#' file downloads (for example, downloading the currently visible data as a CSV
|
||||
#' file). Both filename and contents can be calculated dynamically at the time
|
||||
#' the user initiates the download. Assign the return value to a slot on
|
||||
#' \code{output} in your server function, and in the UI use
|
||||
#' file downloads (for example, downloading the currently visible data as a CSV
|
||||
#' file). Both filename and contents can be calculated dynamically at the time
|
||||
#' the user initiates the download. Assign the return value to a slot on
|
||||
#' \code{output} in your server function, and in the UI use
|
||||
#' \code{\link{downloadButton}} or \code{\link{downloadLink}} to make the
|
||||
#' download available.
|
||||
#'
|
||||
#' @param filename A string of the filename, including extension, that the
|
||||
#' user's web browser should default to when downloading the file; or a
|
||||
#' function that returns such a string. (Reactive values and functions may be
|
||||
#'
|
||||
#' @param filename A string of the filename, including extension, that the
|
||||
#' user's web browser should default to when downloading the file; or a
|
||||
#' function that returns such a string. (Reactive values and functions may be
|
||||
#' used from this function.)
|
||||
#' @param content A function that takes a single argument \code{file} that is a
|
||||
#' @param content A function that takes a single argument \code{file} that is a
|
||||
#' file path (string) of a nonexistent temp file, and writes the content to
|
||||
#' that file path. (Reactive values and functions may be used from this
|
||||
#' function.)
|
||||
#' @param contentType A string of the download's
|
||||
#' \href{http://en.wikipedia.org/wiki/Internet_media_type}{content type}, for
|
||||
#' example \code{"text/csv"} or \code{"image/png"}. If \code{NULL} or
|
||||
#' \code{NA}, the content type will be guessed based on the filename
|
||||
#' @param contentType A string of the download's
|
||||
#' \href{http://en.wikipedia.org/wiki/Internet_media_type}{content type}, for
|
||||
#' example \code{"text/csv"} or \code{"image/png"}. If \code{NULL} or
|
||||
#' \code{NA}, the content type will be guessed based on the filename
|
||||
#' extension, or \code{application/octet-stream} if the extension is unknown.
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # In server.R:
|
||||
@@ -466,16 +511,16 @@ renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
#' write.csv(data, file)
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#'
|
||||
#' # In ui.R:
|
||||
#' downloadLink('downloadData', 'Download')
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
downloadHandler <- function(filename, content, contentType=NA) {
|
||||
return(function(shinysession, name, ...) {
|
||||
return(markRenderFunction(downloadButton, function(shinysession, name, ...) {
|
||||
shinysession$registerDownload(name, filename, contentType, content)
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
#' Table output with the JavaScript library DataTables
|
||||
@@ -484,24 +529,46 @@ downloadHandler <- function(filename, content, contentType=NA) {
|
||||
#' matrix), which will be rendered with the DataTables library. Paging,
|
||||
#' searching, filtering, and sorting can be done on the R side using Shiny as
|
||||
#' the server infrastructure.
|
||||
#'
|
||||
#' For the \code{options} argument, the character elements that have the class
|
||||
#' \code{"AsIs"} (usually returned from \code{\link{I}()}) will be evaluated in
|
||||
#' JavaScript. This is useful when the type of the option value is not supported
|
||||
#' in JSON, e.g., a JavaScript function, which can be obtained by evaluating a
|
||||
#' character string.
|
||||
#' @param expr An expression that returns a data frame or a matrix.
|
||||
#' @param options A list of initialization options to be passed to DataTables.
|
||||
#' @param options A list of initialization options to be passed to DataTables,
|
||||
#' or a function to return such a list.
|
||||
#' @param searchDelay The delay for searching, in milliseconds (to avoid too
|
||||
#' frequent search requests).
|
||||
#' @param callback A JavaScript function to be applied to the DataTable object.
|
||||
#' This is useful for DataTables plug-ins, which often require the DataTable
|
||||
#' instance to be available (\url{http://datatables.net/extras/}).
|
||||
#' @references \url{http://datatables.net}
|
||||
#' @export
|
||||
#' @inheritParams renderPlot
|
||||
#' @examples # pass a callback function to DataTables using I()
|
||||
#' renderDataTable(iris,
|
||||
#' options = list(
|
||||
#' iDisplayLength = 5,
|
||||
#' fnInitComplete = I("function(oSettings, json) {alert('Done.');}")
|
||||
#' )
|
||||
#' )
|
||||
renderDataTable <- function(expr, options = NULL, searchDelay = 500,
|
||||
env=parent.frame(), quoted=FALSE) {
|
||||
callback = 'function(oTable) {}',
|
||||
env = parent.frame(), quoted = FALSE) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
function(shinysession, name, ...) {
|
||||
markRenderFunction(dataTableOutput, function(shinysession, name, ...) {
|
||||
res <- checkAsIs(if (is.function(options)) options() else options)
|
||||
data <- func()
|
||||
if (length(dim(data)) != 2) return() # expects a rectangular data object
|
||||
action <- shinysession$registerDataTable(name, data)
|
||||
list(colnames = colnames(data), action = action, options = options,
|
||||
searchDelay = searchDelay)
|
||||
}
|
||||
action <- shinysession$registerDataObj(name, data, dataTablesJSON)
|
||||
list(
|
||||
colnames = colnames(data), action = action, options = res$options,
|
||||
evalOptions = if (length(res$eval)) I(res$eval), searchDelay = searchDelay,
|
||||
callback = paste(callback, collapse = '\n')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
120
R/showcase.R
@@ -1,17 +1,19 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
# Given the name of a license, return the appropriate link HTML for the
|
||||
# license, which may just be the name of the license if the name is
|
||||
# unrecognized.
|
||||
#
|
||||
# Recognizes the 'standard' set of licenses used for R packages
|
||||
# Recognizes the 'standard' set of licenses used for R packages
|
||||
# (see http://cran.r-project.org/doc/manuals/R-exts.html)
|
||||
licenseLink <- function(licenseName) {
|
||||
licenses <- list(
|
||||
"GPL-2" = "https://gnu.org/licenses/gpl-2.0.txt",
|
||||
"GPL-3" = "https://gnu.org/licenses/gpl-3.0.txt",
|
||||
"GPL-2" = "https://gnu.org/licenses/gpl-2.0.txt",
|
||||
"GPL-3" = "https://gnu.org/licenses/gpl-3.0.txt",
|
||||
"LGPL-3" = "https://www.gnu.org/licenses/lgpl-3.0.txt",
|
||||
"LGPL-2" = "http://www.gnu.org/licenses/old-licenses/lgpl-2.0.txt",
|
||||
"LGPL-2.1" = "http://www.gnu.org/licenses/lgpl-2.1.txt",
|
||||
"LGPL-2.1" = "http://www.gnu.org/licenses/lgpl-2.1.txt",
|
||||
"AGPL-3" = "http://www.gnu.org/licenses/agpl-3.0.txt",
|
||||
"Artistic-2.0" = "http://www.r-project.org/Licenses/Artistic-2.0",
|
||||
"BSD_2_clause" = "http://www.r-project.org/Licenses/BSD_2_clause",
|
||||
@@ -20,30 +22,39 @@ licenseLink <- function(licenseName) {
|
||||
if (exists(licenseName, where = licenses)) {
|
||||
tags$a(href=licenses[[licenseName]], licenseName)
|
||||
} else {
|
||||
licenseName
|
||||
licenseName
|
||||
}
|
||||
}
|
||||
|
||||
# Returns tags containing showcase directives intended for the <HEAD> of the
|
||||
# document.
|
||||
# document.
|
||||
showcaseHead <- function() {
|
||||
|
||||
deps <- list(
|
||||
htmlDependency("jqueryui", "1.10.4", c(href="shared/jqueryui/1.10.4"),
|
||||
script = "jquery-ui.min.js"),
|
||||
htmlDependency("showdown", "0.3.1", c(href="shared/showdown/compressed"),
|
||||
script = "showdown.js"),
|
||||
htmlDependency("font-awesome", "4.0.3", c(href="shared/font-awesome"),
|
||||
stylesheet = "css/font-awesome.min.css"),
|
||||
htmlDependency("highlight.js", "6.2", c(href="shared/highlight"),
|
||||
script = "highlight.pack.js")
|
||||
)
|
||||
|
||||
mdfile <- file.path.ci(getwd(), 'Readme.md')
|
||||
with(tags, tagList(
|
||||
script(src="shared/highlight/highlight.pack.js"),
|
||||
script(src="shared/showdown/compressed/showdown.js"),
|
||||
script(src="shared/jquery-ui/jquery-ui-min.js"),
|
||||
html <- with(tags, tagList(
|
||||
script(src="shared/shiny-showcase.js"),
|
||||
link(rel="stylesheet", type="text/css",
|
||||
link(rel="stylesheet", type="text/css",
|
||||
href="shared/highlight/rstudio.css"),
|
||||
link(rel="stylesheet", type="text/css",
|
||||
link(rel="stylesheet", type="text/css",
|
||||
href="shared/shiny-showcase.css"),
|
||||
link(rel="stylesheet", type="text/css",
|
||||
href="shared/font-awesome/css/font-awesome.min.css"),
|
||||
if (file.exists(mdfile))
|
||||
script(type="text/markdown", id="showcase-markdown-content",
|
||||
paste(readLines(mdfile), collapse="\n"))
|
||||
if (file.exists(mdfile))
|
||||
script(type="text/markdown", id="showcase-markdown-content",
|
||||
paste(readUTF8(mdfile), collapse="\n"))
|
||||
else ""
|
||||
))
|
||||
|
||||
return(attachDependencies(html, deps))
|
||||
}
|
||||
|
||||
# Returns tags containing the application metadata (title and author) in
|
||||
@@ -51,17 +62,17 @@ 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="muted shiny-showcase-apptitle", desc[1,"Title"],
|
||||
if ("Author" %in% cols) small(
|
||||
br(), "by",
|
||||
if ("AuthorUrl" %in% cols)
|
||||
a(href=desc[1,"AuthorUrl"], class="shiny-showcase-appauthor",
|
||||
a(href=desc[1,"AuthorUrl"], class="shiny-showcase-appauthor",
|
||||
desc[1,"Author"])
|
||||
else
|
||||
desc[1,"Author"],
|
||||
if ("AuthorEmail" %in% cols)
|
||||
if ("AuthorEmail" %in% cols)
|
||||
a(href=paste("mailto:", desc[1,"AuthorEmail"], sep = ''),
|
||||
class="shiny-showcase-appauthoreemail",
|
||||
class="shiny-showcase-appauthoreemail",
|
||||
desc[1,"AuthorEmail"])
|
||||
else "")
|
||||
else ""))
|
||||
@@ -71,34 +82,33 @@ appMetadata <- function(desc) {
|
||||
# Returns tags containing the application's code in Bootstrap-style tabs in
|
||||
# showcase mode.
|
||||
showcaseCodeTabs <- function(codeLicense) {
|
||||
rFiles <- list.files(pattern = "\\.R$")
|
||||
with(tags, div(id="showcase-code-tabs",
|
||||
button(id="showcase-code-position-toggle",
|
||||
class="btn btn-default btn-small",
|
||||
onclick="toggleCodePosition()",
|
||||
i(class="fa fa-level-up", "show with app")),
|
||||
ul(class="nav nav-tabs",
|
||||
rFiles <- list.files(pattern = "\\.[rR]$")
|
||||
with(tags, div(id="showcase-code-tabs",
|
||||
a(id="showcase-code-position-toggle",
|
||||
class="btn btn-default btn-small",
|
||||
onclick="toggleCodePosition()",
|
||||
i(class="fa fa-level-up", "show with app")),
|
||||
ul(class="nav nav-tabs",
|
||||
lapply(rFiles, function(rFile) {
|
||||
li(class=if (rFile == "server.R") "active" else "",
|
||||
li(class=if (tolower(rFile) == "server.r") "active" else "",
|
||||
a(href=paste("#", gsub(".", "_", rFile, fixed=TRUE),
|
||||
"_code", sep=""),
|
||||
"_code", sep=""),
|
||||
"data-toggle"="tab", rFile))
|
||||
})),
|
||||
div(class="tab-content", id="showcase-code-content",
|
||||
div(class="tab-content", id="showcase-code-content",
|
||||
lapply(rFiles, function(rFile) {
|
||||
div(class=paste("tab-pane",
|
||||
if (rFile == "server.R") " active" else "",
|
||||
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 can't use tag$code here since we need to prevent
|
||||
# whitespace from being emitted between <code> ... </code>
|
||||
HTML(paste('<code class="language-r">',
|
||||
paste(readLines(file.path.ci(getwd(), rFile)),
|
||||
collapse="\n"),
|
||||
'</code>', 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))))
|
||||
})),
|
||||
codeLicense))
|
||||
}
|
||||
|
||||
@@ -110,23 +120,25 @@ showcaseAppInfo <- function() {
|
||||
readmemd <- file.path.ci(getwd(), "Readme.md")
|
||||
hasReadme <- file.exists(readmemd)
|
||||
if (hasDesc) {
|
||||
desc <- read.dcf(descfile)
|
||||
con <- textConnection(readUTF8(descfile))
|
||||
on.exit(close(con), add = TRUE)
|
||||
desc <- read.dcf(con)
|
||||
}
|
||||
with(tags,
|
||||
div(class="container-fluid shiny-code-container well",
|
||||
with(tags,
|
||||
div(class="container-fluid shiny-code-container well",
|
||||
id="showcase-well",
|
||||
div(class="row-fluid",
|
||||
if (hasDesc || hasReadme) {
|
||||
div(id="showcase-app-metadata", class="span4",
|
||||
if (hasDesc) appMetadata(desc) else "",
|
||||
div(id="showcase-app-metadata", class="span4",
|
||||
if (hasDesc) appMetadata(desc) else "",
|
||||
if (hasReadme) div(id="readme-md"))
|
||||
} else "",
|
||||
div(id="showcase-code-inline",
|
||||
div(id="showcase-code-inline",
|
||||
class=if (hasReadme || hasDesc) "span8" else "span10 offset1",
|
||||
showcaseCodeTabs(
|
||||
if (hasDesc && "License" %in% colnames(desc)) {
|
||||
small(class="showcase-code-license muted",
|
||||
"Code license: ",
|
||||
small(class="showcase-code-license muted",
|
||||
"Code license: ",
|
||||
licenseLink(desc[1,"License"]))
|
||||
} else "")))))
|
||||
}
|
||||
@@ -135,10 +147,10 @@ showcaseAppInfo <- function() {
|
||||
# Returns the body of the showcase document, given the HTML it should wrap.
|
||||
showcaseBody <- function(htmlBody) {
|
||||
with(tags, tagList(
|
||||
table(id="showcase-app-code",
|
||||
table(id="showcase-app-code",
|
||||
tr(td(id="showcase-app-container",
|
||||
class="showcase-app-container-expanded",
|
||||
HTML(htmlBody),
|
||||
class="showcase-app-container-expanded",
|
||||
HTML(htmlBody),
|
||||
td(id="showcase-sxs-code",
|
||||
class="showcase-sxs-code-collapsed")))),
|
||||
showcaseAppInfo()))
|
||||
|
||||
83
R/slider.R
@@ -3,19 +3,17 @@ hasDecimals <- function(value) {
|
||||
return (!identical(value, truncatedValue))
|
||||
}
|
||||
|
||||
#' Animation Options
|
||||
#'
|
||||
#' Creates an options object for customizing animations for \link{sliderInput}.
|
||||
#'
|
||||
#' @rdname sliderInput
|
||||
#'
|
||||
#' @param interval The interval, in milliseconds, between each animation step.
|
||||
#' @param loop \code{TRUE} to automatically restart the animation when it
|
||||
#' @param loop \code{TRUE} to automatically restart the animation when it
|
||||
#' reaches the end.
|
||||
#' @param playButton Specifies the appearance of the play button. Valid values
|
||||
#' are a one-element character vector (for a simple text label), an HTML tag
|
||||
#' or list of tags (using \code{\link{tag}} and friends), or raw HTML (using
|
||||
#' @param playButton Specifies the appearance of the play button. Valid values
|
||||
#' are a one-element character vector (for a simple text label), an HTML tag
|
||||
#' or list of tags (using \code{\link{tag}} and friends), or raw HTML (using
|
||||
#' \code{\link{HTML}}).
|
||||
#' @param pauseButton Similar to \code{playButton}, but for the pause button.
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
animationOptions <- function(interval=1000,
|
||||
loop=FALSE,
|
||||
@@ -30,35 +28,35 @@ animationOptions <- function(interval=1000,
|
||||
# Create a new slider control (list of slider input element and the script
|
||||
# tag used to configure it). This is a lower level control that should
|
||||
# be wrapped in an "input" construct (e.g. sliderInput in bootstrap.R)
|
||||
#
|
||||
#
|
||||
# this is a wrapper for: https://github.com/egorkhmelev/jslider
|
||||
# (www/shared/slider contains js, css, and img dependencies)
|
||||
# (www/shared/slider contains js, css, and img dependencies)
|
||||
slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
round=FALSE, format='#,##0.#####', locale='us',
|
||||
ticks=TRUE, animate=FALSE) {
|
||||
ticks=TRUE, animate=FALSE, width=NULL) {
|
||||
# validate inputId
|
||||
inputId <- as.character(inputId)
|
||||
if (!is.character(inputId))
|
||||
stop("inputId not specified")
|
||||
|
||||
|
||||
# validate numeric inputs
|
||||
if (!is.numeric(value) || !is.numeric(min) || !is.numeric(max))
|
||||
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,
|
||||
else if (min(value) < min)
|
||||
stop(paste("slider initial value", value,
|
||||
"is less than the specified minimum"))
|
||||
else if (max(value) > max)
|
||||
stop(paste("slider initial value", value,
|
||||
else if (max(value) > max)
|
||||
stop(paste("slider initial value", value,
|
||||
"is greater than the specified maximum"))
|
||||
else if (min > max)
|
||||
else if (min > max)
|
||||
stop(paste("slider maximum is greater than minimum"))
|
||||
else if (!is.null(step)) {
|
||||
if (!is.numeric(step))
|
||||
if (!is.numeric(step))
|
||||
stop("step is not a numeric value")
|
||||
if (step > (max - min))
|
||||
if (step > (max - min))
|
||||
stop("step is greater than range")
|
||||
}
|
||||
|
||||
|
||||
# step
|
||||
range <- max - min
|
||||
if (is.null(step)) {
|
||||
@@ -68,7 +66,7 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
else
|
||||
step = 1
|
||||
}
|
||||
|
||||
|
||||
# Default state is to not have ticks
|
||||
if (identical(ticks, TRUE)) {
|
||||
# Automatic ticks
|
||||
@@ -99,35 +97,36 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
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(
|
||||
singleton(tags$head(
|
||||
tags$link(rel="stylesheet",
|
||||
type="text/css",
|
||||
href="shared/slider/css/jquery.slider.min.css"),
|
||||
|
||||
tags$script(src="shared/slider/js/jquery.slider.min.js")
|
||||
)),
|
||||
tags$input(
|
||||
id=inputId, type="slider",
|
||||
name=inputId, value=paste(value, collapse=';'), class="jslider",
|
||||
'data-from'=min, 'data-to'=max, 'data-step'=step,
|
||||
'data-skin'='plastic', 'data-round'=round, 'data-locale'=locale,
|
||||
'data-format'=format, 'data-scale'=ticks,
|
||||
'data-smooth'=FALSE
|
||||
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='#',
|
||||
@@ -138,6 +137,6 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
tags$span(class='play', animate$playButton),
|
||||
tags$span(class='pause', animate$pauseButton)))
|
||||
}
|
||||
|
||||
return(sliderFragment)
|
||||
|
||||
return(tagList(sliderFragment))
|
||||
}
|
||||
|
||||
449
R/tags.R
@@ -1,449 +0,0 @@
|
||||
|
||||
|
||||
htmlEscape <- local({
|
||||
.htmlSpecials <- list(
|
||||
`&` = '&',
|
||||
`<` = '<',
|
||||
`>` = '>'
|
||||
)
|
||||
.htmlSpecialsPattern <- paste(names(.htmlSpecials), collapse='|')
|
||||
.htmlSpecialsAttrib <- c(
|
||||
.htmlSpecials,
|
||||
`'` = ''',
|
||||
`"` = '"',
|
||||
`\r` = ' ',
|
||||
`\n` = ' '
|
||||
)
|
||||
.htmlSpecialsPatternAttrib <- paste(names(.htmlSpecialsAttrib), collapse='|')
|
||||
|
||||
function(text, attribute=TRUE) {
|
||||
pattern <- if(attribute)
|
||||
.htmlSpecialsPatternAttrib
|
||||
else
|
||||
.htmlSpecialsPattern
|
||||
|
||||
# Short circuit in the common case that there's nothing to escape
|
||||
if (!grepl(pattern, text))
|
||||
return(text)
|
||||
|
||||
specials <- if(attribute)
|
||||
.htmlSpecialsAttrib
|
||||
else
|
||||
.htmlSpecials
|
||||
|
||||
for (chr in names(specials)) {
|
||||
text <- gsub(chr, specials[[chr]], text, fixed=TRUE)
|
||||
}
|
||||
|
||||
return(text)
|
||||
}
|
||||
})
|
||||
|
||||
isTag <- function(x) {
|
||||
inherits(x, "shiny.tag")
|
||||
}
|
||||
|
||||
#' @S3method print shiny.tag
|
||||
print.shiny.tag <- function(x, ...) {
|
||||
print(as.character(x), ...)
|
||||
invisible(x)
|
||||
}
|
||||
|
||||
#' @S3method format shiny.tag
|
||||
format.shiny.tag <- function(x, ...) {
|
||||
as.character(renderTags(x)$html)
|
||||
}
|
||||
|
||||
#' @S3method as.character shiny.tag
|
||||
as.character.shiny.tag <- function(x, ...) {
|
||||
renderTags(x)$html
|
||||
}
|
||||
|
||||
#' @S3method print shiny.tag.list
|
||||
print.shiny.tag.list <- print.shiny.tag
|
||||
|
||||
#' @S3method format shiny.tag.list
|
||||
format.shiny.tag.list <- format.shiny.tag
|
||||
|
||||
#' @S3method as.character shiny.tag.list
|
||||
as.character.shiny.tag.list <- as.character.shiny.tag
|
||||
|
||||
#' @S3method print html
|
||||
print.html <- function(x, ...) {
|
||||
cat(x, "\n")
|
||||
invisible(x)
|
||||
}
|
||||
|
||||
#' @S3method format html
|
||||
format.html <- function(x, ...) {
|
||||
as.character(x)
|
||||
}
|
||||
|
||||
normalizeText <- function(text) {
|
||||
if (!is.null(attr(text, "html")))
|
||||
text
|
||||
else
|
||||
htmlEscape(text, attribute=FALSE)
|
||||
|
||||
}
|
||||
|
||||
#' @export
|
||||
tagList <- function(...) {
|
||||
lst <- list(...)
|
||||
class(lst) <- c("shiny.tag.list", "list")
|
||||
return(lst)
|
||||
}
|
||||
|
||||
#' @export
|
||||
tagAppendChild <- function(tag, child) {
|
||||
tag$children[[length(tag$children)+1]] <- child
|
||||
tag
|
||||
}
|
||||
|
||||
#' @export
|
||||
tagAppendChildren <- function(tag, ..., list = NULL) {
|
||||
tag$children <- c(tag$children, c(list(...), list))
|
||||
tag
|
||||
}
|
||||
|
||||
#' @export
|
||||
tagSetChildren <- function(tag, ..., list = NULL) {
|
||||
tag$children <- c(list(...), list)
|
||||
tag
|
||||
}
|
||||
|
||||
#' @export
|
||||
tag <- function(`_tag_name`, varArgs) {
|
||||
# Get arg names; if not a named list, use vector of empty strings
|
||||
varArgsNames <- names(varArgs)
|
||||
if (is.null(varArgsNames))
|
||||
varArgsNames <- character(length=length(varArgs))
|
||||
|
||||
# Named arguments become attribs, dropping NULL values
|
||||
named_idx <- nzchar(varArgsNames)
|
||||
attribs <- dropNulls(varArgs[named_idx])
|
||||
|
||||
# Unnamed arguments are flattened and added as children.
|
||||
# Use unname() to remove the names attribute from the list, which would
|
||||
# consist of empty strings anyway.
|
||||
children <- flattenTags(unname(varArgs[!named_idx]))
|
||||
|
||||
# Return tag data structure
|
||||
structure(
|
||||
list(name = `_tag_name`,
|
||||
attribs = attribs,
|
||||
children = children),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
}
|
||||
|
||||
tagWrite <- function(tag, textWriter, indent=0, context = NULL, eol = "\n") {
|
||||
|
||||
# optionally process a list of tags
|
||||
if (!isTag(tag) && is.list(tag)) {
|
||||
sapply(tag, function(t) tagWrite(t, textWriter, indent, context))
|
||||
return (NULL)
|
||||
}
|
||||
|
||||
# first call optional filter -- exit function if it returns false
|
||||
if (!is.null(context) && !is.null(context$filter) && !context$filter(tag))
|
||||
return (NULL)
|
||||
|
||||
# compute indent text
|
||||
indentText <- paste(rep(" ", indent*2), collapse="")
|
||||
|
||||
# Check if it's just text (may either be plain-text or HTML)
|
||||
if (is.character(tag)) {
|
||||
textWriter(paste(indentText, normalizeText(tag), eol, sep=""))
|
||||
return (NULL)
|
||||
}
|
||||
|
||||
# write tag name
|
||||
textWriter(paste(indentText, "<", tag$name, sep=""))
|
||||
|
||||
# concatenate attributes
|
||||
attribs <- tag$attribs
|
||||
attribs <- lapply(split(attribs, names(attribs)), paste, collapse = " ")
|
||||
|
||||
# write attributes
|
||||
for (attrib in names(attribs)) {
|
||||
attribValue <- attribs[[attrib]]
|
||||
if (!is.na(attribValue)) {
|
||||
if (is.logical(attribValue))
|
||||
attribValue <- tolower(attribValue)
|
||||
text <- htmlEscape(attribValue, attribute=TRUE)
|
||||
textWriter(paste(" ", attrib,"=\"", text, "\"", sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter(paste(" ", attrib, sep=""))
|
||||
}
|
||||
}
|
||||
|
||||
# write any children
|
||||
if (length(tag$children) > 0) {
|
||||
textWriter(">")
|
||||
|
||||
# special case for a single child text node (skip newlines and indentation)
|
||||
if ((length(tag$children) == 1) && is.character(tag$children[[1]]) ) {
|
||||
tagWrite(tag$children[[1]], textWriter, 0, context, "")
|
||||
textWriter(paste("</", tag$name, ">", eol, sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter("\n")
|
||||
for (child in tag$children)
|
||||
tagWrite(child, textWriter, indent+1, context)
|
||||
textWriter(paste(indentText, "</", tag$name, ">", eol, sep=""))
|
||||
}
|
||||
}
|
||||
else {
|
||||
# only self-close void elements
|
||||
# (see: http://dev.w3.org/html5/spec/single-page.html#void-elements)
|
||||
if (tag$name %in% c("area", "base", "br", "col", "command", "embed", "hr",
|
||||
"img", "input", "keygen", "link", "meta", "param",
|
||||
"source", "track", "wbr")) {
|
||||
textWriter(paste("/>", eol, sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter(paste("></", tag$name, ">", eol, sep=""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderTags <- function(ui, singletons = character(0)) {
|
||||
# provide a filter so we can intercept head tag requests
|
||||
context <- new.env()
|
||||
context$head <- character()
|
||||
context$singletons <- singletons
|
||||
context$filter <- function(content) {
|
||||
if (inherits(content, 'shiny.singleton')) {
|
||||
sig <- digest(content, algo='sha1')
|
||||
if (sig %in% context$singletons)
|
||||
return(FALSE)
|
||||
context$singletons <- c(sig, context$singletons)
|
||||
}
|
||||
|
||||
if (isTag(content) && identical(content$name, "head")) {
|
||||
textConn <- textConnection(NULL, "w")
|
||||
textConnWriter <- function(text) cat(text, file = textConn)
|
||||
tagWrite(content$children, textConnWriter, 1, context)
|
||||
context$head <- append(context$head, textConnectionValue(textConn))
|
||||
close(textConn)
|
||||
return (FALSE)
|
||||
}
|
||||
else {
|
||||
return (TRUE)
|
||||
}
|
||||
}
|
||||
|
||||
# write ui HTML to a character vector
|
||||
textConn <- textConnection(NULL, "w")
|
||||
tagWrite(ui, function(text) cat(text, file = textConn), 0, context)
|
||||
uiHTML <- paste(textConnectionValue(textConn), collapse = "\n")
|
||||
close(textConn)
|
||||
|
||||
return(list(head = HTML(paste(context$head, collapse = "\n")),
|
||||
singletons = context$singletons,
|
||||
html = HTML(uiHTML)))
|
||||
}
|
||||
|
||||
# environment used to store all available tags
|
||||
#' @export
|
||||
tags <- list(
|
||||
a = function(...) tag("a", list(...)),
|
||||
abbr = function(...) tag("abbr", list(...)),
|
||||
address = function(...) tag("address", list(...)),
|
||||
area = function(...) tag("area", list(...)),
|
||||
article = function(...) tag("article", list(...)),
|
||||
aside = function(...) tag("aside", list(...)),
|
||||
audio = function(...) tag("audio", list(...)),
|
||||
b = function(...) tag("b", list(...)),
|
||||
base = function(...) tag("base", list(...)),
|
||||
bdi = function(...) tag("bdi", list(...)),
|
||||
bdo = function(...) tag("bdo", list(...)),
|
||||
blockquote = function(...) tag("blockquote", list(...)),
|
||||
body = function(...) tag("body", list(...)),
|
||||
br = function(...) tag("br", list(...)),
|
||||
button = function(...) tag("button", list(...)),
|
||||
canvas = function(...) tag("canvas", list(...)),
|
||||
caption = function(...) tag("caption", list(...)),
|
||||
cite = function(...) tag("cite", list(...)),
|
||||
code = function(...) tag("code", list(...)),
|
||||
col = function(...) tag("col", list(...)),
|
||||
colgroup = function(...) tag("colgroup", list(...)),
|
||||
command = function(...) tag("command", list(...)),
|
||||
data = function(...) tag("data", list(...)),
|
||||
datalist = function(...) tag("datalist", list(...)),
|
||||
dd = function(...) tag("dd", list(...)),
|
||||
del = function(...) tag("del", list(...)),
|
||||
details = function(...) tag("details", list(...)),
|
||||
dfn = function(...) tag("dfn", list(...)),
|
||||
div = function(...) tag("div", list(...)),
|
||||
dl = function(...) tag("dl", list(...)),
|
||||
dt = function(...) tag("dt", list(...)),
|
||||
em = function(...) tag("em", list(...)),
|
||||
embed = function(...) tag("embed", list(...)),
|
||||
eventsource = function(...) tag("eventsource", list(...)),
|
||||
fieldset = function(...) tag("fieldset", list(...)),
|
||||
figcaption = function(...) tag("figcaption", list(...)),
|
||||
figure = function(...) tag("figure", list(...)),
|
||||
footer = function(...) tag("footer", list(...)),
|
||||
form = function(...) tag("form", list(...)),
|
||||
h1 = function(...) tag("h1", list(...)),
|
||||
h2 = function(...) tag("h2", list(...)),
|
||||
h3 = function(...) tag("h3", list(...)),
|
||||
h4 = function(...) tag("h4", list(...)),
|
||||
h5 = function(...) tag("h5", list(...)),
|
||||
h6 = function(...) tag("h6", list(...)),
|
||||
head = function(...) tag("head", list(...)),
|
||||
header = function(...) tag("header", list(...)),
|
||||
hgroup = function(...) tag("hgroup", list(...)),
|
||||
hr = function(...) tag("hr", list(...)),
|
||||
html = function(...) tag("html", list(...)),
|
||||
i = function(...) tag("i", list(...)),
|
||||
iframe = function(...) tag("iframe", list(...)),
|
||||
img = function(...) tag("img", list(...)),
|
||||
input = function(...) tag("input", list(...)),
|
||||
ins = function(...) tag("ins", list(...)),
|
||||
kbd = function(...) tag("kbd", list(...)),
|
||||
keygen = function(...) tag("keygen", list(...)),
|
||||
label = function(...) tag("label", list(...)),
|
||||
legend = function(...) tag("legend", list(...)),
|
||||
li = function(...) tag("li", list(...)),
|
||||
link = function(...) tag("link", list(...)),
|
||||
mark = function(...) tag("mark", list(...)),
|
||||
map = function(...) tag("map", list(...)),
|
||||
menu = function(...) tag("menu", list(...)),
|
||||
meta = function(...) tag("meta", list(...)),
|
||||
meter = function(...) tag("meter", list(...)),
|
||||
nav = function(...) tag("nav", list(...)),
|
||||
noscript = function(...) tag("noscript", list(...)),
|
||||
object = function(...) tag("object", list(...)),
|
||||
ol = function(...) tag("ol", list(...)),
|
||||
optgroup = function(...) tag("optgroup", list(...)),
|
||||
option = function(...) tag("option", list(...)),
|
||||
output = function(...) tag("output", list(...)),
|
||||
p = function(...) tag("p", list(...)),
|
||||
param = function(...) tag("param", list(...)),
|
||||
pre = function(...) tag("pre", list(...)),
|
||||
progress = function(...) tag("progress", list(...)),
|
||||
q = function(...) tag("q", list(...)),
|
||||
ruby = function(...) tag("ruby", list(...)),
|
||||
rp = function(...) tag("rp", list(...)),
|
||||
rt = function(...) tag("rt", list(...)),
|
||||
s = function(...) tag("s", list(...)),
|
||||
samp = function(...) tag("samp", list(...)),
|
||||
script = function(...) tag("script", list(...)),
|
||||
section = function(...) tag("section", list(...)),
|
||||
select = function(...) tag("select", list(...)),
|
||||
small = function(...) tag("small", list(...)),
|
||||
source = function(...) tag("source", list(...)),
|
||||
span = function(...) tag("span", list(...)),
|
||||
strong = function(...) tag("strong", list(...)),
|
||||
style = function(...) tag("style", list(...)),
|
||||
sub = function(...) tag("sub", list(...)),
|
||||
summary = function(...) tag("summary", list(...)),
|
||||
sup = function(...) tag("sup", list(...)),
|
||||
table = function(...) tag("table", list(...)),
|
||||
tbody = function(...) tag("tbody", list(...)),
|
||||
td = function(...) tag("td", list(...)),
|
||||
textarea = function(...) tag("textarea", list(...)),
|
||||
tfoot = function(...) tag("tfoot", list(...)),
|
||||
th = function(...) tag("th", list(...)),
|
||||
thead = function(...) tag("thead", list(...)),
|
||||
time = function(...) tag("time", list(...)),
|
||||
title = function(...) tag("title", list(...)),
|
||||
tr = function(...) tag("tr", list(...)),
|
||||
track = function(...) tag("track", list(...)),
|
||||
u = function(...) tag("u", list(...)),
|
||||
ul = function(...) tag("ul", list(...)),
|
||||
var = function(...) tag("var", list(...)),
|
||||
video = function(...) tag("video", list(...)),
|
||||
wbr = function(...) tag("wbr", list(...))
|
||||
)
|
||||
|
||||
#' Mark Characters as HTML
|
||||
#'
|
||||
#' Marks the given text as HTML, which means the \link{tag} functions will know
|
||||
#' not to perform HTML escaping on it.
|
||||
#'
|
||||
#' @param text The text value to mark with HTML
|
||||
#' @param ... Any additional values to be converted to character and
|
||||
#' concatenated together
|
||||
#' @return The same value, but marked as HTML.
|
||||
#'
|
||||
#' @examples
|
||||
#' el <- div(HTML("I like <u>turtles</u>"))
|
||||
#' cat(as.character(el))
|
||||
#'
|
||||
#' @export
|
||||
HTML <- function(text, ...) {
|
||||
htmlText <- c(text, as.character(list(...)))
|
||||
htmlText <- paste(htmlText, collapse=" ")
|
||||
attr(htmlText, "html") <- TRUE
|
||||
class(htmlText) <- c("html", "character")
|
||||
htmlText
|
||||
}
|
||||
|
||||
#' Evaluate an expression using the \code{tags}
|
||||
#'
|
||||
#' This function makes it simpler to write HTML-generating code. Instead of
|
||||
#' needing to specify \code{tags} each time a tag function is used, as in
|
||||
#' \code{tags$div()} and \code{tags$p()}, code inside \code{withTags} is
|
||||
#' evaluated with \code{tags} searched first, so you can simply use
|
||||
#' \code{div()} and \code{p()}.
|
||||
#'
|
||||
#' If your code uses an object which happens to have the same name as an
|
||||
#' HTML tag function, such as \code{source()} or \code{summary()}, it will call
|
||||
#' the tag function. To call the intended (non-tags function), specify the
|
||||
#' namespace, as in \code{base::source()} or \code{base::summary()}.
|
||||
#'
|
||||
#' @param code A set of tags.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Using tags$ each time
|
||||
#' tags$div(class = "myclass",
|
||||
#' tags$h3("header"),
|
||||
#' tags$p("text")
|
||||
#' )
|
||||
#'
|
||||
#' # Equivalent to above, but using withTags
|
||||
#' withTags(
|
||||
#' div(class = "myclass",
|
||||
#' h3("header"),
|
||||
#' p("text")
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
withTags <- function(code) {
|
||||
eval(substitute(code), envir = as.list(tags), enclos = parent.frame())
|
||||
}
|
||||
|
||||
|
||||
# Given a list of tags, lists, and other items, return a flat list, where the
|
||||
# items from the inner, nested lists are pulled to the top level, recursively.
|
||||
flattenTags <- function(x) {
|
||||
if (isTag(x)) {
|
||||
# For tags, wrap them into a list (which will be unwrapped by caller)
|
||||
list(x)
|
||||
} else if (is.list(x)) {
|
||||
if (length(x) == 0) {
|
||||
# Empty lists are simply returned
|
||||
x
|
||||
} else {
|
||||
# For items that are lists (but not tags), recurse
|
||||
unlist(lapply(x, flattenTags), recursive = FALSE)
|
||||
}
|
||||
|
||||
} else if (is.character(x)){
|
||||
# This will preserve attributes if x is a character with attribute,
|
||||
# like what HTML() produces
|
||||
list(x)
|
||||
|
||||
} else {
|
||||
# For other items, coerce to character and wrap them into a list (which
|
||||
# will be unwrapped by caller). Note that this will strip attributes.
|
||||
list(as.character(x))
|
||||
}
|
||||
}
|
||||
2
R/tar.R
@@ -141,7 +141,7 @@ untar2 <- function(tarfile, files = NULL, list = FALSE, exdir = ".")
|
||||
warning(gettextf("failed to copy %s to %s", sQuote(name2), sQuote(name)), domain = NA)
|
||||
}
|
||||
} else {
|
||||
if(.Platform$OS.type == "windows") {
|
||||
if(isWindows()) {
|
||||
## this will not work for links to dirs
|
||||
from <- file.path(dirname(name), name2)
|
||||
if (!file.copy(from, name))
|
||||
|
||||
14
R/timer.R
@@ -23,17 +23,17 @@ TimerCallbacks <- setRefClass(
|
||||
schedule = function(millis, func) {
|
||||
id <- .nextId
|
||||
.nextId <<- .nextId + 1L
|
||||
|
||||
|
||||
t <- now()
|
||||
|
||||
|
||||
# TODO: Horribly inefficient, use a heap instead
|
||||
.times <<- rbind(.times, data.frame(time=t+millis,
|
||||
scheduled=t,
|
||||
id=id))
|
||||
.times <<- .times[order(.times$time),]
|
||||
|
||||
|
||||
.funcs$set(as.character(id), func)
|
||||
|
||||
|
||||
return(id)
|
||||
},
|
||||
timeToNextEvent = function() {
|
||||
@@ -46,18 +46,18 @@ TimerCallbacks <- setRefClass(
|
||||
elapsed <- .times$time < now()
|
||||
result <- .times[elapsed,]
|
||||
.times <<- .times[!elapsed,]
|
||||
|
||||
|
||||
# TODO: Examine scheduled column to check if any funny business
|
||||
# has occurred with the system clock (e.g. if scheduled
|
||||
# is later than now())
|
||||
|
||||
|
||||
return(result)
|
||||
},
|
||||
executeElapsed = function() {
|
||||
elapsed <- takeElapsed()
|
||||
if (length(elapsed) == 0)
|
||||
return(FALSE)
|
||||
|
||||
|
||||
for (id in elapsed$id) {
|
||||
thisFunc <- .funcs$remove(as.character(id))
|
||||
# TODO: Catch exception, and...?
|
||||
|
||||
171
R/update-input.R
@@ -118,7 +118,7 @@ updateSliderInput <- updateTextInput
|
||||
#' }
|
||||
#' @export
|
||||
updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL) {
|
||||
min = NULL, max = NULL) {
|
||||
|
||||
# If value is a date object, convert it to a string with yyyy-mm-dd format
|
||||
# Same for min and max
|
||||
@@ -163,8 +163,8 @@ updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
#' }
|
||||
#' @export
|
||||
updateDateRangeInput <- function(session, inputId, label = NULL,
|
||||
start = NULL, end = NULL, min = NULL, max = NULL) {
|
||||
|
||||
start = NULL, end = NULL, min = NULL,
|
||||
max = NULL) {
|
||||
# Make sure start and end are strings, not date objects. This is for
|
||||
# consistency across different locales.
|
||||
if (inherits(start, "Date")) start <- format(start, '%Y-%m-%d')
|
||||
@@ -186,11 +186,11 @@ updateDateRangeInput <- function(session, inputId, label = NULL,
|
||||
#'
|
||||
#' @param session The \code{session} object passed to function given to
|
||||
#' \code{shinyServer}.
|
||||
#' @param inputId The id of the \code{tabsetPanel}, \code{navlistPanel},
|
||||
#' @param inputId The id of the \code{tabsetPanel}, \code{navlistPanel},
|
||||
#' or \code{navbarPage} object.
|
||||
#' @param selected The name of the tab to make active.
|
||||
#'
|
||||
#' @seealso \code{\link{tabsetPanel}}, \code{\link{navlistPanel}},
|
||||
#' @seealso \code{\link{tabsetPanel}}, \code{\link{navlistPanel}},
|
||||
#' \code{\link{navbarPage}}
|
||||
#'
|
||||
#' @examples
|
||||
@@ -249,17 +249,35 @@ updateTabsetPanel <- function(session, inputId, selected = NULL) {
|
||||
updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL, step = NULL) {
|
||||
|
||||
message <- dropNulls(list(label=label, value=value, min=min, max=max, step=step))
|
||||
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(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
|
||||
options <- if (length(choices))
|
||||
format(tagList(
|
||||
generateOptions(inputId, choices, selected, inline, type = type)
|
||||
))
|
||||
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' Change the value of a checkbox group input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param choices A named vector or named list of options. For each item, the
|
||||
#' name will be used as the label, and the value will be used as the value.
|
||||
#' @param selected A vector or list of options which will be selected.
|
||||
#' @inheritParams checkboxGroupInput
|
||||
#'
|
||||
#' @seealso \code{\link{checkboxGroupInput}}
|
||||
#'
|
||||
@@ -285,38 +303,23 @@ updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
#' updateCheckboxGroupInput(session, "inCheckboxGroup2",
|
||||
#' label = paste("checkboxgroup label", x),
|
||||
#' choices = cb_options,
|
||||
#' selected = sprintf("option label %d 2", x)
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
choices = NULL, selected = NULL) {
|
||||
|
||||
choices <- choicesWithNames(choices)
|
||||
|
||||
options <- mapply(choices, names(choices),
|
||||
SIMPLIFY = FALSE, USE.NAMES = FALSE,
|
||||
FUN = function(value, name) {
|
||||
list(value = value,
|
||||
label = name,
|
||||
checked = name %in% selected)
|
||||
}
|
||||
)
|
||||
|
||||
message <- dropNulls(list(label = label, options = options))
|
||||
|
||||
session$sendInputMessage(inputId, message)
|
||||
choices = NULL, selected = NULL,
|
||||
inline = FALSE) {
|
||||
updateInputOptions(session, inputId, label, choices, selected, inline)
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a radio input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param choices A named vector or named list of options. For each item, the
|
||||
#' name will be used as the label, and the value will be used as the value.
|
||||
#' @param selected A vector or list of options which will be selected.
|
||||
#' @inheritParams radioButtons
|
||||
#'
|
||||
#' @seealso \code{\link{radioButtons}}
|
||||
#'
|
||||
@@ -340,21 +343,24 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
#' updateRadioButtons(session, "inRadio2",
|
||||
#' label = paste("Radio label", x),
|
||||
#' choices = r_options,
|
||||
#' selected = sprintf("option label %d 2", x)
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateRadioButtons <- updateCheckboxGroupInput
|
||||
updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, inline = FALSE) {
|
||||
# you must select at least one radio button
|
||||
if (is.null(selected) && !is.null(choices)) selected <- choices[[1]]
|
||||
updateInputOptions(session, inputId, label, choices, selected, inline, type = 'radio')
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a select input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param choices A named vector or named list of options. For each item, the
|
||||
#' name will be used as the label, and the value will be used as the value.
|
||||
#' @param selected A vector or list of options which will be selected.
|
||||
#' @inheritParams selectInput
|
||||
#'
|
||||
#' @seealso \code{\link{selectInput}}
|
||||
#'
|
||||
@@ -381,27 +387,94 @@ updateRadioButtons <- updateCheckboxGroupInput
|
||||
#' updateSelectInput(session, "inSelect2",
|
||||
#' label = paste("Select label", x),
|
||||
#' choices = s_options,
|
||||
#' selected = sprintf("option label %d 2", x)
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateSelectInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL) {
|
||||
|
||||
selected = NULL) {
|
||||
choices <- choicesWithNames(choices)
|
||||
|
||||
options <- mapply(choices, names(choices),
|
||||
SIMPLIFY = FALSE, USE.NAMES = FALSE,
|
||||
FUN = function(value, name) {
|
||||
list(value = value,
|
||||
label = name,
|
||||
selected = name %in% selected)
|
||||
}
|
||||
)
|
||||
|
||||
message <- dropNulls(list(label = label, options = options))
|
||||
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
options <- if (length(choices)) selectOptions(choices, selected)
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' @rdname updateSelectInput
|
||||
#' @inheritParams selectizeInput
|
||||
#' @param server whether to store \code{choices} on the server side, and load
|
||||
#' the select options dynamically on searching, instead of writing all
|
||||
#' \code{choices} into the page at once (i.e., only use the client-side
|
||||
#' version of \pkg{selectize.js})
|
||||
#' @export
|
||||
updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, options = list(),
|
||||
server = FALSE) {
|
||||
if (length(options)) {
|
||||
res <- checkAsIs(options)
|
||||
cfg <- tags$script(
|
||||
type = 'application/json',
|
||||
`data-for` = inputId,
|
||||
`data-eval` = if (length(res$eval)) HTML(toJSON(res$eval)),
|
||||
HTML(toJSON(res$options))
|
||||
)
|
||||
session$sendInputMessage(inputId, list(config = as.character(cfg)))
|
||||
}
|
||||
if (!server) {
|
||||
return(updateSelectInput(session, inputId, label, choices, selected))
|
||||
}
|
||||
# in the server mode, the choices are not available before we type, so we
|
||||
# cannot really pre-select any options, but here we insert the `selected`
|
||||
# options into selectize forcibly
|
||||
value <- unname(selected)
|
||||
selected <- choicesWithNames(selected)
|
||||
message <- dropNulls(list(
|
||||
label = label,
|
||||
value = value,
|
||||
selected = if (length(selected)) {
|
||||
columnToRowData(list(label = names(selected), value = selected))
|
||||
},
|
||||
url = session$registerDataObj(inputId, choices, selectizeJSON)
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
selectizeJSON <- function(data, req) {
|
||||
query <- parseQueryString(req$QUERY_STRING)
|
||||
# extract the query variables, conjunction (and/or), search string, maximum options
|
||||
var <- fromJSON(query$field)
|
||||
cjn <- if (query$conju == 'and') all else any
|
||||
# all keywords in lower-case, for case-insensitive matching
|
||||
key <- unique(strsplit(tolower(query$query), '\\s+')[[1]])
|
||||
if (identical(key, '')) key <- character(0)
|
||||
mop <- query$maxop
|
||||
|
||||
# convert a single vector to a data frame so it returns {label: , value: }
|
||||
# later in JSON; other objects return arbitrary JSON {x: , y: , foo: , ...}
|
||||
data <- if (is.atomic(data)) {
|
||||
data <- choicesWithNames(data)
|
||||
data.frame(label = names(data), value = data, stringsAsFactors = FALSE)
|
||||
} else as.data.frame(data, stringsAsFactors = FALSE)
|
||||
|
||||
# start searching for keywords in all specified columns
|
||||
idx <- logical(nrow(data))
|
||||
if (length(key)) for (v in var) {
|
||||
matches <- do.call(
|
||||
cbind,
|
||||
lapply(key, function(k) {
|
||||
grepl(k, tolower(as.character(data[[v]])), fixed = TRUE)
|
||||
})
|
||||
)
|
||||
# merge column matches using OR, and match multiple keywords in one column
|
||||
# using the conjunction setting (AND or OR)
|
||||
idx <- idx | apply(matches, 1, cjn)
|
||||
}
|
||||
# only return the first n rows (n = maximum options in configuration)
|
||||
idx <- head(which(idx), mop)
|
||||
data <- data[idx, ]
|
||||
|
||||
httpResponse(200, 'application/json', toJSON(columnToRowData(data)))
|
||||
}
|
||||
|
||||
626
R/utils.R
@@ -1,13 +1,17 @@
|
||||
#' @include globals.R
|
||||
#' @include map.R
|
||||
NULL
|
||||
|
||||
#' Make a random number generator repeatable
|
||||
#'
|
||||
#' Given a function that generates random data, returns a wrapped version of
|
||||
#'
|
||||
#' Given a function that generates random data, returns a wrapped version of
|
||||
#' that function that always uses the same seed when called. The seed to use can
|
||||
#' be passed in explicitly if desired; otherwise, a random number is used.
|
||||
#'
|
||||
#'
|
||||
#' @param rngfunc The function that is affected by the R session's seed.
|
||||
#' @param seed The seed to set every time the resulting function is called.
|
||||
#' @return A repeatable version of the function that was passed in.
|
||||
#'
|
||||
#'
|
||||
#' @note When called, the returned function attempts to preserve the R session's
|
||||
#' current seed by snapshotting and restoring
|
||||
#' \code{\link[base]{.Random.seed}}.
|
||||
@@ -19,11 +23,11 @@
|
||||
#' rnormA(3) # [1] 1.8285879 -0.7468041 -0.4639111
|
||||
#' rnormA(5) # [1] 1.8285879 -0.7468041 -0.4639111 -1.6510126 -1.4686924
|
||||
#' rnormB(5) # [1] -0.7946034 0.2568374 -0.6567597 1.2451387 -0.8375699
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
repeatable <- function(rngfunc, seed = runif(1, 0, .Machine$integer.max)) {
|
||||
force(seed)
|
||||
|
||||
|
||||
function(...) {
|
||||
# When we exit, restore the seed to its original state
|
||||
if (exists('.Random.seed', where=globalenv())) {
|
||||
@@ -33,15 +37,94 @@ repeatable <- function(rngfunc, seed = runif(1, 0, .Machine$integer.max)) {
|
||||
else {
|
||||
on.exit(rm('.Random.seed', pos=globalenv()))
|
||||
}
|
||||
|
||||
|
||||
set.seed(seed)
|
||||
|
||||
|
||||
rngfunc(...)
|
||||
}
|
||||
}
|
||||
|
||||
# Temporarily set x in env to value, evaluate expr, and
|
||||
# then restore x to its original state
|
||||
withTemporary <- function(env, x, value, expr, unset = FALSE) {
|
||||
|
||||
if (exists(x, envir = env, inherits = FALSE)) {
|
||||
oldValue <- get(x, envir = env, inherits = FALSE)
|
||||
on.exit(
|
||||
assign(x, oldValue, envir = env, inherits = FALSE),
|
||||
add = TRUE)
|
||||
} else {
|
||||
on.exit(
|
||||
rm(list = x, envir = env, inherits = FALSE),
|
||||
add = TRUE
|
||||
)
|
||||
}
|
||||
|
||||
if (!missing(value) && !isTRUE(unset))
|
||||
assign(x, value, envir = env, inherits = FALSE)
|
||||
else {
|
||||
if (exists(x, envir = env, inherits = FALSE))
|
||||
rm(list = x, envir = env, inherits = FALSE)
|
||||
}
|
||||
force(expr)
|
||||
}
|
||||
|
||||
.globals$ownSeed <- NULL
|
||||
# Evaluate an expression using Shiny's own private stream of
|
||||
# randomness (not affected by set.seed).
|
||||
withPrivateSeed <- function(expr) {
|
||||
withTemporary(.GlobalEnv, ".Random.seed",
|
||||
.globals$ownSeed, unset=is.null(.globals$ownSeed), {
|
||||
tryCatch({
|
||||
expr
|
||||
}, finally = {
|
||||
.globals$ownSeed <- getExists('.Random.seed', 'numeric', globalenv())
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# a homemade version of set.seed(NULL) for backward compatibility with R 2.15.x
|
||||
reinitializeSeed <- if (getRversion() >= '3.0.0') {
|
||||
function() set.seed(NULL)
|
||||
} else function() {
|
||||
if (exists('.Random.seed', globalenv()))
|
||||
rm(list = '.Random.seed', pos = globalenv())
|
||||
stats::runif(1) # generate any random numbers so R can reinitialize the seed
|
||||
}
|
||||
|
||||
# Version of runif that runs with private seed
|
||||
p_runif <- function(...) {
|
||||
withPrivateSeed(runif(...))
|
||||
}
|
||||
|
||||
# Version of sample that runs with private seed
|
||||
p_sample <- function(...) {
|
||||
withPrivateSeed(sample(...))
|
||||
}
|
||||
|
||||
# Return a random integral value in the range [min, max).
|
||||
# If only one argument is passed, then min=0 and max=argument.
|
||||
randomInt <- function(min, max) {
|
||||
if (missing(max)) {
|
||||
max <- min
|
||||
min <- 0
|
||||
}
|
||||
if (min < 0 || max <= min)
|
||||
stop("Invalid min/max values")
|
||||
|
||||
min + sample(max-min, 1)-1
|
||||
}
|
||||
|
||||
p_randomInt <- function(...) {
|
||||
withPrivateSeed(randomInt(...))
|
||||
}
|
||||
|
||||
`%OR%` <- function(x, y) {
|
||||
ifelse(is.null(x) || is.na(x), y, x)
|
||||
if (is.null(x) || isTRUE(is.na(x)))
|
||||
y
|
||||
else
|
||||
x
|
||||
}
|
||||
|
||||
`%AND%` <- function(x, y) {
|
||||
@@ -60,6 +143,109 @@ dropNulls <- function(x) {
|
||||
x[!vapply(x, is.null, FUN.VALUE=logical(1))]
|
||||
}
|
||||
|
||||
nullOrEmpty <- function(x) {
|
||||
is.null(x) || length(x) == 0
|
||||
}
|
||||
# Given a vector or list, drop all the NULL items in it
|
||||
dropNullsOrEmpty <- function(x) {
|
||||
x[!vapply(x, nullOrEmpty, FUN.VALUE=logical(1))]
|
||||
}
|
||||
|
||||
# Combine dir and (file)name into a file path. If a file already exists with a
|
||||
# name differing only by case, then use it instead.
|
||||
file.path.ci <- function(dir, name) {
|
||||
default <- file.path(dir, name)
|
||||
if (file.exists(default))
|
||||
return(default)
|
||||
if (!file.exists(dir))
|
||||
return(default)
|
||||
|
||||
matches <- list.files(dir, name, ignore.case=TRUE, full.names=TRUE,
|
||||
include.dirs=TRUE)
|
||||
if (length(matches) == 0)
|
||||
return(default)
|
||||
return(matches[[1]])
|
||||
}
|
||||
|
||||
# Attempt to join a path and relative path, and turn the result into a
|
||||
# (normalized) absolute path. The result will only be returned if it is an
|
||||
# existing file/directory and is a descendant of dir.
|
||||
#
|
||||
# Example:
|
||||
# resolve("/Users/jcheng", "shiny") # "/Users/jcheng/shiny"
|
||||
# resolve("/Users/jcheng", "./shiny") # "/Users/jcheng/shiny"
|
||||
# resolve("/Users/jcheng", "shiny/../shiny/") # "/Users/jcheng/shiny"
|
||||
# resolve("/Users/jcheng", ".") # NULL
|
||||
# resolve("/Users/jcheng", "..") # NULL
|
||||
# resolve("/Users/jcheng", "shiny/..") # NULL
|
||||
resolve <- function(dir, relpath) {
|
||||
abs.path <- file.path(dir, relpath)
|
||||
if (!file.exists(abs.path))
|
||||
return(NULL)
|
||||
abs.path <- normalizePath(abs.path, winslash='/', mustWork=TRUE)
|
||||
dir <- normalizePath(dir, winslash='/', mustWork=TRUE)
|
||||
# trim the possible trailing slash under Windows (#306)
|
||||
if (isWindows()) dir <- sub('/$', '', dir)
|
||||
if (nchar(abs.path) <= nchar(dir) + 1)
|
||||
return(NULL)
|
||||
if (substr(abs.path, 1, nchar(dir)) != dir ||
|
||||
substr(abs.path, nchar(dir)+1, nchar(dir)+1) != '/') {
|
||||
return(NULL)
|
||||
}
|
||||
return(abs.path)
|
||||
}
|
||||
|
||||
isWindows <- function() .Platform$OS.type == 'windows'
|
||||
|
||||
# This is a wrapper for download.file and has the same interface.
|
||||
# The only difference is that, if the protocol is https, it changes the
|
||||
# download settings, depending on platform.
|
||||
download <- function(url, ...) {
|
||||
# First, check protocol. If http or https, check platform:
|
||||
if (grepl('^https?://', url)) {
|
||||
|
||||
# If Windows, call setInternet2, then use download.file with defaults.
|
||||
if (isWindows()) {
|
||||
# If we directly use setInternet2, R CMD CHECK gives a Note on Mac/Linux
|
||||
mySI2 <- `::`(utils, 'setInternet2')
|
||||
# Store initial settings
|
||||
internet2_start <- mySI2(NA)
|
||||
on.exit(mySI2(internet2_start))
|
||||
|
||||
# Needed for https
|
||||
mySI2(TRUE)
|
||||
download.file(url, ...)
|
||||
|
||||
} else {
|
||||
# If non-Windows, check for curl/wget/lynx, then call download.file with
|
||||
# appropriate method.
|
||||
|
||||
if (nzchar(Sys.which("wget")[1])) {
|
||||
method <- "wget"
|
||||
} else if (nzchar(Sys.which("curl")[1])) {
|
||||
method <- "curl"
|
||||
|
||||
# curl needs to add a -L option to follow redirects.
|
||||
# Save the original options and restore when we exit.
|
||||
orig_extra_options <- getOption("download.file.extra")
|
||||
on.exit(options(download.file.extra = orig_extra_options))
|
||||
|
||||
options(download.file.extra = paste("-L", orig_extra_options))
|
||||
|
||||
} else if (nzchar(Sys.which("lynx")[1])) {
|
||||
method <- "lynx"
|
||||
} else {
|
||||
stop("no download method found")
|
||||
}
|
||||
|
||||
download.file(url, method = method, ...)
|
||||
}
|
||||
|
||||
} else {
|
||||
download.file(url, ...)
|
||||
}
|
||||
}
|
||||
|
||||
knownContentTypes <- Map$new()
|
||||
knownContentTypes$mset(
|
||||
html='text/html; charset=UTF-8',
|
||||
@@ -115,7 +301,7 @@ makeFunction <- function(args = pairlist(), body, env = parent.frame()) {
|
||||
eval(call("function", args, body), env)
|
||||
}
|
||||
|
||||
#' Convert an expression or quoted expression to a function
|
||||
#' Convert an expression to a function
|
||||
#'
|
||||
#' This is to be called from another function, because it will attempt to get
|
||||
#' an unquoted expression from two calls back.
|
||||
@@ -130,8 +316,8 @@ makeFunction <- function(args = pairlist(), body, env = parent.frame()) {
|
||||
#' @param env The desired environment for the function. Defaults to the
|
||||
#' calling environment two steps back.
|
||||
#' @param quoted Is the expression quoted?
|
||||
#' @param caller_offset If specified, the offset in the callstack of the
|
||||
#' functiont to be treated as the caller.
|
||||
#' @param caller_offset If specified, the offset in the callstack of the
|
||||
#' functiont to be treated as the caller.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Example of a new renderer, similar to renderText
|
||||
@@ -167,7 +353,7 @@ makeFunction <- function(args = pairlist(), body, env = parent.frame()) {
|
||||
#' # "text, text, text"
|
||||
#'
|
||||
#' @export
|
||||
exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE,
|
||||
exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE,
|
||||
caller_offset=1) {
|
||||
# Get the quoted expr from two calls back
|
||||
expr_sub <- eval(substitute(substitute(expr)), parent.frame(caller_offset))
|
||||
@@ -177,10 +363,10 @@ exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE,
|
||||
# If expr is a single token, then indexing with [[ will error; if it has multiple
|
||||
# tokens, then [[ works. In the former case it will be a name object; in the
|
||||
# latter, it will be a language object.
|
||||
if (!is.name(expr_sub) && expr_sub[[1]] == as.name('function')) {
|
||||
if (!is.null(expr_sub) && !is.name(expr_sub) && expr_sub[[1]] == as.name('function')) {
|
||||
# Get name of function that called this function
|
||||
called_fun <- sys.call(-1 * caller_offset)[[1]]
|
||||
|
||||
|
||||
shinyDeprecated(msg = paste("Passing functions to '", called_fun,
|
||||
"' is deprecated. Please use expressions instead. See ?", called_fun,
|
||||
" for more information.", sep=""))
|
||||
@@ -196,29 +382,31 @@ exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE,
|
||||
}
|
||||
}
|
||||
|
||||
#' Install an expression as a function
|
||||
#'
|
||||
#' Installs an expression in the given environment as a function, and registers
|
||||
#' debug hooks so that breakpoints may be set in the function.
|
||||
#'
|
||||
#'
|
||||
#' This function can replace \code{exprToFunction} as follows: we may use
|
||||
#' \code{func <- exprToFunction(expr)} if we do not want the debug hooks, or
|
||||
#' \code{installExprFunction(expr, "func")} if we do. Both approaches create a
|
||||
#' function named \code{func} in the current environment.
|
||||
#'
|
||||
#' @seealso Wraps \code{exprToFunction}; see that method's documentation for
|
||||
#' more documentation and examples.
|
||||
#'
|
||||
#'
|
||||
#' @seealso Wraps \code{\link{exprToFunction}}; see that method's documentation
|
||||
#' for more documentation and examples.
|
||||
#'
|
||||
#' @param expr A quoted or unquoted expression
|
||||
#' @param name The name the function should be given
|
||||
#' @param name The name the function should be given
|
||||
#' @param eval.env The desired environment for the function. Defaults to the
|
||||
#' calling environment two steps back.
|
||||
#' calling environment two steps back.
|
||||
#' @param quoted Is the expression quoted?
|
||||
#' @param assign.env The environment in which the function should be assigned.
|
||||
#' @param label A label for the object to be shown in the debugger. Defaults
|
||||
#' to the name of the calling function.
|
||||
#'
|
||||
#' @param label A label for the object to be shown in the debugger. Defaults to
|
||||
#' the name of the calling function.
|
||||
#'
|
||||
#' @export
|
||||
installExprFunction <- function(expr, name, eval.env = parent.frame(2),
|
||||
quoted = FALSE,
|
||||
installExprFunction <- function(expr, name, eval.env = parent.frame(2),
|
||||
quoted = FALSE,
|
||||
assign.env = parent.frame(1),
|
||||
label = as.character(sys.call(-1)[[1]])) {
|
||||
func <- exprToFunction(expr, eval.env, quoted, 2)
|
||||
@@ -301,7 +489,7 @@ shinyCallingHandlers <- function(expr) {
|
||||
shinyDeprecated <- function(new=NULL, msg=NULL,
|
||||
old=as.character(sys.call(sys.parent()))[1L]) {
|
||||
|
||||
if (getOption("shiny.deprecation.messages", default=TRUE) == FALSE)
|
||||
if (getOption("shiny.deprecation.messages") %OR% TRUE == FALSE)
|
||||
return(invisible())
|
||||
|
||||
if (is.null(msg)) {
|
||||
@@ -314,14 +502,14 @@ shinyDeprecated <- function(new=NULL, msg=NULL,
|
||||
message(msg)
|
||||
}
|
||||
|
||||
#' Register a function with the debugger (if one is active).
|
||||
#'
|
||||
#' Register a function with the debugger (if one is active).
|
||||
#'
|
||||
#' Call this function after exprToFunction to give any active debugger a hook
|
||||
#' to set and clear breakpoints in the function. A debugger may implement
|
||||
#' registerShinyDebugHook to receive callbacks when Shiny functions are
|
||||
#' instantiated at runtime.
|
||||
#' registerShinyDebugHook to receive callbacks when Shiny functions are
|
||||
#' instantiated at runtime.
|
||||
#'
|
||||
#' @param name Name of the field or object containing the function.
|
||||
#' @param name Name of the field or object containing the function.
|
||||
#' @param where The reference object or environment containing the function.
|
||||
#' @param label A label to display on the function in the debugger.
|
||||
#' @noRd
|
||||
@@ -370,27 +558,49 @@ Callbacks <- setRefClass(
|
||||
)
|
||||
|
||||
# convert a data frame to JSON as required by DataTables request
|
||||
dataTablesJSON <- function(data, query) {
|
||||
dataTablesJSON <- function(data, req) {
|
||||
query <- req$QUERY_STRING
|
||||
n <- nrow(data)
|
||||
with(parseQueryString(query), {
|
||||
useRegex <- function(j, envir = parent.frame()) {
|
||||
# FIXME: bRegex is not part of the query string yet (DataTables 1.9.4)
|
||||
return(TRUE)
|
||||
ex <- getExists(
|
||||
if (missing(j)) 'bRegex' else sprintf('bRegex_%s', j), 'character', envir
|
||||
)
|
||||
is.null(ex) || ex == 'true'
|
||||
}
|
||||
# global searching
|
||||
i <- seq_len(n)
|
||||
if (nzchar(sSearch)) {
|
||||
i0 <- apply(data, 2, function(x) grep(sSearch, as.character(x)))
|
||||
sSearch <- getExists('sSearch', 'character')
|
||||
if (length(sSearch) && nzchar(sSearch)) {
|
||||
bRegex <- useRegex()
|
||||
i0 <- apply(data, 2, function(x) grep(sSearch, as.character(x), fixed = !bRegex))
|
||||
i <- intersect(i, unique(unlist(i0)))
|
||||
}
|
||||
# search by columns
|
||||
if (length(i)) for (j in seq_len(as.integer(iColumns)) - 1) {
|
||||
if (is.null(k <- get_exists(sprintf('sSearch_%d', j), 'character'))) next
|
||||
if (nzchar(k)) i <- intersect(grep(k, as.character(data[, j + 1])), i)
|
||||
if (is.null(s <- getExists(sprintf('bSearchable_%d', j), 'character')) ||
|
||||
s == "0" || s == "false") next # the j-th column is not searchable
|
||||
if (is.null(k <- getExists(sprintf('sSearch_%d', j), 'character'))) next
|
||||
if (nzchar(k)) {
|
||||
dj <- data[, j + 1]
|
||||
r <- commaToRange(k)
|
||||
ij <- if (length(r) == 2 && is.numeric(dj)) {
|
||||
which(dj >= r[1] & dj <= r[2])
|
||||
} else {
|
||||
grep(k, as.character(dj), fixed = !useRegex(j))
|
||||
}
|
||||
i <- intersect(ij, i)
|
||||
}
|
||||
if (length(i) == 0) break
|
||||
}
|
||||
if (length(i) != n) data <- data[i, , drop = FALSE]
|
||||
# sorting
|
||||
oList <- list()
|
||||
for (j in seq_len(as.integer(iSortingCols)) - 1) {
|
||||
if (is.null(k <- get_exists(sprintf('iSortCol_%d', j), 'character'))) break
|
||||
desc = get_exists(sprintf('sSortDir_%d', j), 'character')
|
||||
if (is.null(k <- getExists(sprintf('iSortCol_%d', j), 'character'))) break
|
||||
desc <- getExists(sprintf('sSortDir_%d', j), 'character')
|
||||
if (is.character(desc)) {
|
||||
col <- data[, as.integer(k) + 1]
|
||||
oList[[length(oList) + 1]] <- (if (desc == 'asc') identity else `-`)(
|
||||
@@ -403,37 +613,75 @@ dataTablesJSON <- function(data, query) {
|
||||
data <- data[i, , drop = FALSE]
|
||||
}
|
||||
# paging
|
||||
i <- seq(as.integer(iDisplayStart) + 1L, length.out = as.integer(iDisplayLength))
|
||||
i <- i[i <= n]
|
||||
fdata <- data[i, , drop = FALSE] # filtered data
|
||||
if (iDisplayLength != '-1') {
|
||||
i <- seq(as.integer(iDisplayStart) + 1L, length.out = as.integer(iDisplayLength))
|
||||
i <- i[i <= nrow(data)]
|
||||
fdata <- data[i, , drop = FALSE] # filtered data
|
||||
} else fdata <- data
|
||||
fdata <- unname(as.matrix(fdata))
|
||||
if (nrow(fdata) == 0) fdata = list()
|
||||
# WAT: toJSON(list(x = matrix(nrow = 0, ncol = 1))) => {"x": } (#299)
|
||||
if (nrow(fdata) == 0) fdata <- list()
|
||||
# WAT: toJSON(list(x = matrix(1:2))) => {x: [ [1], [2] ]}, however,
|
||||
# toJSON(list(x = matrix(1))) => {x: [ 1 ]} (loss of dimension, #429)
|
||||
if (all(dim(fdata) == 1)) fdata <- list(list(fdata[1, 1]))
|
||||
|
||||
toJSON(list(
|
||||
res <- toJSON(list(
|
||||
sEcho = as.integer(sEcho),
|
||||
iTotalRecords = n,
|
||||
iTotalDisplayRecords = nrow(data),
|
||||
aaData = fdata
|
||||
))
|
||||
httpResponse(200, 'application/json', res)
|
||||
})
|
||||
}
|
||||
|
||||
get_exists = function(x, mode) {
|
||||
if (exists(x, envir = parent.frame(), mode = mode, inherits = FALSE))
|
||||
get(x, envir = parent.frame(), mode = mode, inherits = FALSE)
|
||||
getExists <- function(x, mode, envir = parent.frame()) {
|
||||
if (exists(x, envir = envir, mode = mode, inherits = FALSE))
|
||||
get(x, envir = envir, mode = mode, inherits = FALSE)
|
||||
}
|
||||
|
||||
# convert a string of the form "lower,upper" to c(lower, upper)
|
||||
commaToRange <- function(string) {
|
||||
if (!grepl(',', string)) return()
|
||||
r <- strsplit(string, ',')[[1]]
|
||||
if (length(r) > 2) return()
|
||||
if (length(r) == 1) r <- c(r, '') # lower,
|
||||
r <- as.numeric(r)
|
||||
if (is.na(r[1])) r[1] <- -Inf
|
||||
if (is.na(r[2])) r[2] <- Inf
|
||||
r
|
||||
}
|
||||
|
||||
# for options passed to DataTables/Selectize/..., the options of the class AsIs
|
||||
# will be evaluated as literal JavaScript code
|
||||
checkAsIs <- function(options) {
|
||||
evalOptions <- if (length(options)) {
|
||||
nms <- names(options)
|
||||
i <- unlist(lapply(options, function(x) {
|
||||
is.character(x) && inherits(x, 'AsIs')
|
||||
}))
|
||||
if (any(i)) {
|
||||
# must convert to character, otherwise toJSON() turns it to an array []
|
||||
options[i] <- lapply(options[i], paste, collapse = '\n')
|
||||
nms[i] # options of these names will be evaluated in JS
|
||||
}
|
||||
}
|
||||
list(options = options, eval = evalOptions)
|
||||
}
|
||||
|
||||
srcrefFromShinyCall <- function(expr) {
|
||||
srcrefs <- attr(expr, "srcref")
|
||||
num_exprs <- length(srcrefs)
|
||||
c(srcrefs[[1]][1], srcrefs[[1]][2],
|
||||
if (num_exprs < 1)
|
||||
return(NULL)
|
||||
c(srcrefs[[1]][1], srcrefs[[1]][2],
|
||||
srcrefs[[num_exprs]][3], srcrefs[[num_exprs]][4],
|
||||
srcrefs[[1]][5], srcrefs[[num_exprs]][6])
|
||||
}
|
||||
|
||||
# Indicates whether the given querystring should cause the associated request
|
||||
# to be handled in showcase mode. Returns the showcase mode if set, or NULL
|
||||
# if no showcase mode is set.
|
||||
# to be handled in showcase mode. Returns the showcase mode if set, or NULL
|
||||
# if no showcase mode is set.
|
||||
showcaseModeOfQuerystring <- function(querystring) {
|
||||
if (nchar(querystring) > 0) {
|
||||
qs <- parseQueryString(querystring)
|
||||
@@ -443,7 +691,7 @@ showcaseModeOfQuerystring <- function(querystring) {
|
||||
}
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
|
||||
showcaseModeOfReq <- function(req) {
|
||||
showcaseModeOfQuerystring(req$QUERY_STRING)
|
||||
}
|
||||
@@ -452,8 +700,8 @@ showcaseModeOfReq <- function(req) {
|
||||
# empty string if the source reference doesn't include file information.
|
||||
srcFileOfRef <- function(srcref) {
|
||||
fileEnv <- attr(srcref, "srcfile")
|
||||
# The 'srcfile' attribute should be a non-null environment containing the
|
||||
# variable 'filename', which gives the full path to the source file.
|
||||
# The 'srcfile' attribute should be a non-null environment containing the
|
||||
# variable 'filename', which gives the full path to the source file.
|
||||
if (!is.null(fileEnv) &&
|
||||
is.environment(fileEnv) &&
|
||||
exists("filename", where = fileEnv))
|
||||
@@ -461,3 +709,273 @@ srcFileOfRef <- function(srcref) {
|
||||
else
|
||||
""
|
||||
}
|
||||
|
||||
# Format a number without sci notation, and keep as many digits as possible (do
|
||||
# we really need to go beyond 15 digits?)
|
||||
formatNoSci <- function(x) {
|
||||
if (is.null(x)) return(NULL)
|
||||
format(x, scientific = FALSE, digits = 15)
|
||||
}
|
||||
|
||||
# Returns a function that calls the given func and caches the result for
|
||||
# subsequent calls, unless the given file's mtime changes.
|
||||
cachedFuncWithFile <- function(dir, file, func, case.sensitive = FALSE) {
|
||||
dir <- normalizePath(dir, mustWork=TRUE)
|
||||
mtime <- NA
|
||||
value <- NULL
|
||||
function(...) {
|
||||
fname <- if (case.sensitive)
|
||||
file.path(dir, file)
|
||||
else
|
||||
file.path.ci(dir, file)
|
||||
|
||||
now <- file.info(fname)$mtime
|
||||
if (!identical(mtime, now)) {
|
||||
value <<- func(fname, ...)
|
||||
mtime <<- now
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
# turn column-based data to row-based data (mainly for JSON), e.g. data.frame(x
|
||||
# = 1:10, y = 10:1) ==> list(list(x = 1, y = 10), list(x = 2, y = 9), ...)
|
||||
columnToRowData <- function(data) {
|
||||
do.call(
|
||||
mapply, c(
|
||||
list(FUN = function(...) list(...), SIMPLIFY = FALSE, USE.NAMES = FALSE),
|
||||
as.list(data)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#' Validate input values and other conditions
|
||||
#'
|
||||
#' For an output rendering function (e.g. \code{\link{renderPlot}()}), you may
|
||||
#' need to check that certain input values are available and valid before you
|
||||
#' can render the output. \code{validate} gives you a convenient mechanism for
|
||||
#' doing so.
|
||||
#'
|
||||
#' The \code{validate} function takes any number of (unnamed) arguments, each of
|
||||
#' which represents a condition to test. If any of the conditions represent
|
||||
#' failure, then a special type of error is signaled which stops execution. If
|
||||
#' this error is not handled by application-specific code, it is displayed to
|
||||
#' the user by Shiny.
|
||||
#'
|
||||
#' An easy way to provide arguments to \code{validate} is to use the \code{need}
|
||||
#' function, which takes an expression and a string; if the expression is
|
||||
#' considered a failure, then the string will be used as the error message. The
|
||||
#' \code{need} function considers its expression to be a failure if it is any of
|
||||
#' the following:
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \item{\code{FALSE}}
|
||||
#' \item{\code{NULL}}
|
||||
#' \item{\code{""}}
|
||||
#' \item{An empty atomic vector}
|
||||
#' \item{An atomic vector that contains only missing values}
|
||||
#' \item{A logical vector that contains all \code{FALSE} or missing values}
|
||||
#' \item{An object of class \code{"try-error"}}
|
||||
#' \item{A value that represents an unclicked \code{\link{actionButton}}}
|
||||
#' }
|
||||
#'
|
||||
#' If any of these values happen to be valid, you can explicitly turn them to
|
||||
#' logical values. For example, if you allow \code{NA} but not \code{NULL}, you
|
||||
#' can use the condition \code{!is.null(input$foo)}, because \code{!is.null(NA)
|
||||
#' == TRUE}.
|
||||
#'
|
||||
#' If you need validation logic that differs significantly from \code{need}, you
|
||||
#' can create other validation test functions. A passing test should return
|
||||
#' \code{NULL}. A failing test should return an error message as a
|
||||
#' single-element character vector, or if the failure should happen silently,
|
||||
#' \code{FALSE}.
|
||||
#'
|
||||
#' Because validation failure is signaled as an error, you can use
|
||||
#' \code{validate} in reactive expressions, and validation failures will
|
||||
#' automatically propagate to outputs that use the reactive expression. In
|
||||
#' other words, if reactive expression \code{a} needs \code{input$x}, and two
|
||||
#' outputs use \code{a} (and thus depend indirectly on \code{input$x}), it's
|
||||
#' not necessary for the outputs to validate \code{input$x} explicitly, as long
|
||||
#' as \code{a} does validate it.
|
||||
#'
|
||||
#' @param ... A list of tests. Each test should equal \code{NULL} for success,
|
||||
#' \code{FALSE} for silent failure, or a string for failure with an error
|
||||
#' message.
|
||||
#' @param errorClass A CSS class to apply. The actual CSS string will have
|
||||
#' \code{shiny-output-error-} prepended to this value.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' # in ui.R
|
||||
#' fluidPage(
|
||||
#' checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
|
||||
#' selectizeInput('in2', 'Select a state', choices = state.name),
|
||||
#' plotOutput('plot')
|
||||
#' )
|
||||
#'
|
||||
#' # in server.R
|
||||
#' function(input, output) {
|
||||
#' output$plot <- renderPlot({
|
||||
#' validate(
|
||||
#' need(input$in1, 'Check at least one letter!'),
|
||||
#' need(input$in2 == '', 'Please choose a state.')
|
||||
#' )
|
||||
#' plot(1:10, main = paste(c(input$in1, input$in2), collapse = ', '))
|
||||
#' })
|
||||
#' }
|
||||
validate <- function(..., errorClass = character(0)) {
|
||||
results <- sapply(list(...), function(x) {
|
||||
# Detect NULL or NA
|
||||
if (is.null(x))
|
||||
return(NA_character_)
|
||||
else if (identical(x, FALSE))
|
||||
return("")
|
||||
else if (is.character(x))
|
||||
return(paste(as.character(x), collapse = "\n"))
|
||||
else
|
||||
stop("Unexpected validation result: ", as.character(x))
|
||||
})
|
||||
|
||||
results <- na.omit(results)
|
||||
if (length(results) == 0)
|
||||
return(invisible())
|
||||
|
||||
# There may be empty strings remaining; these are message-less failures that
|
||||
# started as FALSE
|
||||
results <- results[nzchar(results)]
|
||||
|
||||
stopWithCondition(c("validation", errorClass), paste(results, collapse="\n"))
|
||||
}
|
||||
|
||||
#' @param expr An expression to test. The condition will pass if the expression
|
||||
#' meets the conditions spelled out in Details.
|
||||
#' @param message A message to convey to the user if the validation condition is
|
||||
#' not met. If no message is provided, one will be created using \code{label}.
|
||||
#' To fail with no message, use \code{FALSE} for the message.
|
||||
#' @param label A human-readable name for the field that may be missing. This
|
||||
#' parameter is not needed if \code{message} is provided, but must be provided
|
||||
#' otherwise.
|
||||
#' @export
|
||||
#' @rdname validate
|
||||
need <- function(expr, message = paste(label, "must be provided"), label) {
|
||||
|
||||
force(message) # Fail fast on message/label both being missing
|
||||
|
||||
if (!isTruthy(expr))
|
||||
return(message)
|
||||
else
|
||||
return(invisible(NULL))
|
||||
}
|
||||
|
||||
isTruthy <- function(x) {
|
||||
if (inherits(x, 'try-error'))
|
||||
return(FALSE)
|
||||
|
||||
if (!is.atomic(x))
|
||||
return(TRUE)
|
||||
|
||||
if (is.null(x))
|
||||
return(FALSE)
|
||||
if (length(x) == 0)
|
||||
return(FALSE)
|
||||
if (all(is.na(x)))
|
||||
return(FALSE)
|
||||
if (is.character(x) && !any(nzchar(na.omit(x))))
|
||||
return(FALSE)
|
||||
if (inherits(x, 'shinyActionButtonValue') && x == 0)
|
||||
return(FALSE)
|
||||
if (is.logical(x) && !any(na.omit(x)))
|
||||
return(FALSE)
|
||||
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
# add class(es) to the error condition, which will be used as names of CSS
|
||||
# classes, e.g. shiny-output-error shiny-output-error-validation
|
||||
stopWithCondition <- function(class, message) {
|
||||
cond <- structure(
|
||||
list(message = message),
|
||||
class = c(class, 'shiny.silent.error', 'error', 'condition')
|
||||
)
|
||||
stop(cond)
|
||||
}
|
||||
|
||||
#' Collect information about the Shiny Server environment
|
||||
#'
|
||||
#' This function returns the information about the current Shiny Server, such as
|
||||
#' its version, and whether it is the open source edition or professional
|
||||
#' edition. If the app is not served through the Shiny Server, this function
|
||||
#' just returns \code{list(shinyServer = FALSE)}.
|
||||
#' @export
|
||||
#' @return A list of the Shiny Server information.
|
||||
serverInfo <- function() {
|
||||
.globals$serverInfo
|
||||
}
|
||||
.globals$serverInfo <- list(shinyServer = FALSE)
|
||||
|
||||
setServerInfo <- function(...) {
|
||||
infoOld <- serverInfo()
|
||||
infoNew <- list(...)
|
||||
infoOld[names(infoNew)] <- infoNew
|
||||
.globals$serverInfo <- infoOld
|
||||
}
|
||||
|
||||
# see if the file can be read as UTF-8 on Windows, and converted from UTF-8 to
|
||||
# native encoding; if the conversion fails, it will produce NA's in the results
|
||||
checkEncoding <- function(file) {
|
||||
# skip *nix because its locale is normally UTF-8 based (e.g. en_US.UTF-8), and
|
||||
# *nix users have to make a conscious effort to save a file with an encoding
|
||||
# that is not UTF-8; if they choose to do so, we cannot do much about it
|
||||
# except sitting back and seeing them punished after they choose to escape a
|
||||
# world of consistency (falling back to getOption('encoding') will not help
|
||||
# because native.enc is also normally UTF-8 based on *nix)
|
||||
if (!isWindows()) return('UTF-8')
|
||||
# an empty file?
|
||||
size <- file.info(file)[, 'size']
|
||||
if (size == 0) return('UTF-8')
|
||||
|
||||
x <- readLines(file, encoding = 'UTF-8', warn = FALSE)
|
||||
# if conversion is successful and there are no embedded nul's, use UTF-8
|
||||
if (!any(is.na(iconv(x, 'UTF-8'))) &&
|
||||
!any(readBin(file, 'raw', size) == as.raw(0))) return('UTF-8')
|
||||
|
||||
# check if there is a BOM character: this is also skipped on *nix, because R
|
||||
# on *nix simply ignores this meaningless character if present, but it hurts
|
||||
# on Windows
|
||||
if (identical(charToRaw(readChar(file, 3L, TRUE)), charToRaw('\UFEFF'))) {
|
||||
warning('You should not include the Byte Order Mark (BOM) in ', file, '. ',
|
||||
'Please re-save it in UTF-8 without BOM. See ',
|
||||
'http://shiny.rstudio.com/articles/unicode.html for more info.')
|
||||
if (getRversion() < '3.0.0')
|
||||
stop('R does not support UTF-8-BOM before 3.0.0. Please upgrade R.')
|
||||
return('UTF-8-BOM')
|
||||
}
|
||||
|
||||
enc <- getOption('encoding')
|
||||
msg <- c(sprintf('The file "%s" is not encoded in UTF-8. ', file),
|
||||
'Please convert its encoding to UTF-8 ',
|
||||
'(e.g. use the menu `File -> Save with Encoding` in RStudio). ',
|
||||
'See http://shiny.rstudio.com/articles/unicode.html for more info.')
|
||||
if (enc == 'UTF-8') stop(msg)
|
||||
# if you publish the app to ShinyApps.io, you will be in trouble
|
||||
warning(c(msg, ' Falling back to the encoding "', enc, '".'))
|
||||
|
||||
enc
|
||||
}
|
||||
|
||||
# try to read a file using UTF-8 (fall back to getOption('encoding') in case of
|
||||
# failure, which defaults to native.enc, i.e. native encoding)
|
||||
readUTF8 <- function(file) {
|
||||
enc <- checkEncoding(file)
|
||||
# readLines() does not support UTF-8-BOM directly; has to go through file()
|
||||
if (enc == 'UTF-8-BOM') {
|
||||
file <- base::file(file, encoding = enc)
|
||||
on.exit(close(file), add = TRUE)
|
||||
}
|
||||
x <- readLines(file, encoding = enc, warn = FALSE)
|
||||
enc2native(x)
|
||||
}
|
||||
|
||||
# similarly, try to source() a file with UTF-8
|
||||
sourceUTF8 <- function(file, ...) {
|
||||
source(file, ..., keep.source = TRUE, encoding = checkEncoding(file))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Shiny
|
||||
|
||||
[](https://travis-ci.org/rstudio/shiny)
|
||||
[](https://travis-ci.org/rstudio/shiny)
|
||||
|
||||
Shiny is a new package from RStudio that makes it incredibly easy to build interactive web applications with R.
|
||||
|
||||
|
||||
30
inst/NOTICE
@@ -5,6 +5,8 @@ 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
|
||||
|
||||
@@ -34,7 +36,7 @@ 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 and bootstrap-datepicker License
|
||||
Bootstrap, bootstrap-datepicker, and selectize License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
@@ -240,6 +242,32 @@ Bootstrap and bootstrap-datepicker License
|
||||
limitations under the License.
|
||||
|
||||
|
||||
es5-shim License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2009-2014 Kristopher Michael Kowal and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
jslider License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: Hello Shiny!
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: GPL-3
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
This small Shiny application demonstrates Shiny's automatic UI updates. Move the
|
||||
*Number of observations* slider and notice how the `renderPlot` expression is
|
||||
automatically re-evaluated when its dependant, `input$obs`, changes, causing a
|
||||
new distribution to be generated and the plot to be rendered.
|
||||
|
||||
This small Shiny application demonstrates Shiny's automatic UI updates. Move
|
||||
the *Number of bins* slider and notice how the `renderPlot` expression is
|
||||
automatically re-evaluated when its dependant, `input$bins`, changes,
|
||||
causing a histogram with a new number of bins to be rendered.
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic required to generate and plot a random
|
||||
# distribution
|
||||
# Define server logic required to draw a histogram
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Expression that generates a plot of the distribution. The
|
||||
# expression is wrapped in a call to renderPlot to indicate
|
||||
# that:
|
||||
|
||||
# Expression that generates a histogram. The expression is
|
||||
# wrapped in a call to renderPlot to indicate that:
|
||||
#
|
||||
# 1) It is "reactive" and therefore should be automatically
|
||||
# 1) It is "reactive" and therefore should be automatically
|
||||
# re-executed when inputs change
|
||||
# 2) Its output type is a plot
|
||||
#
|
||||
# 2) Its output type is a plot
|
||||
|
||||
output$distPlot <- renderPlot({
|
||||
|
||||
# generate an rnorm distribution and plot it
|
||||
dist <- rnorm(input$obs)
|
||||
hist(dist)
|
||||
x <- faithful[, 2] # Old Faithful Geyser data
|
||||
bins <- seq(min(x), max(x), length.out = input$bins + 1)
|
||||
|
||||
# draw the histogram with the specified number of bins
|
||||
hist(x, breaks = bins, col = 'darkgray', border = 'white')
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for application that plots random distributions
|
||||
# Define UI for application that draws a histogram
|
||||
shinyUI(fluidPage(
|
||||
|
||||
|
||||
# Application title
|
||||
titlePanel("Hello Shiny!"),
|
||||
|
||||
# Sidebar with a slider input for number of observations
|
||||
|
||||
# Sidebar with a slider input for the number of bins
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
sliderInput("obs",
|
||||
"Number of observations:",
|
||||
min = 1,
|
||||
max = 1000,
|
||||
value = 500)
|
||||
sliderInput("bins",
|
||||
"Number of bins:",
|
||||
min = 1,
|
||||
max = 50,
|
||||
value = 30)
|
||||
),
|
||||
|
||||
|
||||
# Show a plot of the generated distribution
|
||||
mainPanel(
|
||||
plotOutput("distPlot")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: Shiny Text
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: GPL-3
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: Reactivity
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: GPL-3
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
@@ -5,7 +5,7 @@ library(datasets)
|
||||
# dataset
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# By declaring databaseInput as a reactive expression we ensure
|
||||
# By declaring datasetInput as a reactive expression we ensure
|
||||
# that:
|
||||
#
|
||||
# 1) It is only called when the inputs it depends on changes
|
||||
|
||||
@@ -24,7 +24,7 @@ shinyUI(fluidPage(
|
||||
# Show the caption, a summary of the dataset and an HTML
|
||||
# table with the requested number of observations
|
||||
mainPanel(
|
||||
h3(textOutput("caption")),
|
||||
h3(textOutput("caption", container = span)),
|
||||
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: Miles Per Gallon
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: GPL-3
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: Sliders
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: GPL-3
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: Tabsets
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: GPL-3
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: Widgets
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: GPL-3
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
This example demonstrates some additional widgets included in Shiny, such as `helpText` and `submitButton`. The latter is used to delay rendering output until the user explicitly requests it.
|
||||
|
||||
|
||||
7
inst/examples/08_html/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Custom HTML UI
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/08_html/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Normally we use the built-in functions, such as `textInput()`, to generate
|
||||
the HTML UI in the R script `ui.R`. Actually **shiny** also works with a
|
||||
custom HTML page `www/index.html`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#html-ui) for more details.
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: File Upload
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: GPL-3
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
4
inst/examples/09_upload/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
We can add a file upload input in the UI using the function `fileInput()`,
|
||||
e.g. `fileInput('foo')`. In `server.R`, we can access the uploaded files via
|
||||
`input$foo`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#uploads) for more details.
|
||||
@@ -14,12 +14,12 @@ shinyUI(fluidPage(
|
||||
c(Comma=',',
|
||||
Semicolon=';',
|
||||
Tab='\t'),
|
||||
'Comma'),
|
||||
','),
|
||||
radioButtons('quote', 'Quote',
|
||||
c(None='',
|
||||
'Double Quote'='"',
|
||||
'Single Quote'="'"),
|
||||
'Double Quote')
|
||||
'"')
|
||||
),
|
||||
mainPanel(
|
||||
tableOutput('contents')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: File Download
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: GPL-3
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
4
inst/examples/10_download/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
We can add a download button to the UI using `downloadButton()`, and write
|
||||
the content of the file in `downloadHandler()` in `server.R`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#downloads) for more
|
||||
details.
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: Timer
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: GPL-3
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
4
inst/examples/11_timer/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
The function `invalidateLater()` can be used to invalidate an observer or
|
||||
reactive expression in a given number of milliseconds. In this example, the
|
||||
output `currentTime` is updated every second, so it shows the current time
|
||||
on a second basis.
|
||||
163
inst/staticdocs/index.r
Normal file
@@ -0,0 +1,163 @@
|
||||
sd_section("UI Layout",
|
||||
"Functions for laying out the user interface for your application.",
|
||||
c(
|
||||
"absolutePanel",
|
||||
"bootstrapPage",
|
||||
"column",
|
||||
"conditionalPanel",
|
||||
"fixedPage",
|
||||
"fluidPage",
|
||||
"headerPanel",
|
||||
"helpText",
|
||||
"icon",
|
||||
"mainPanel",
|
||||
"navbarPage",
|
||||
"navlistPanel",
|
||||
"pageWithSidebar",
|
||||
"sidebarLayout",
|
||||
"sidebarPanel",
|
||||
"tabPanel",
|
||||
"tabsetPanel",
|
||||
"titlePanel",
|
||||
"inputPanel",
|
||||
"flowLayout",
|
||||
"splitLayout",
|
||||
"verticalLayout",
|
||||
"wellPanel",
|
||||
"withMathJax"
|
||||
)
|
||||
)
|
||||
sd_section("UI Inputs",
|
||||
"Functions for creating user interface elements that prompt the user for input values or interaction.",
|
||||
c(
|
||||
"actionButton",
|
||||
"checkboxGroupInput",
|
||||
"checkboxInput",
|
||||
"dateInput",
|
||||
"dateRangeInput",
|
||||
"fileInput",
|
||||
"numericInput",
|
||||
"radioButtons",
|
||||
"selectInput",
|
||||
"sliderInput",
|
||||
"submitButton",
|
||||
"textInput",
|
||||
"updateCheckboxGroupInput",
|
||||
"updateCheckboxInput",
|
||||
"updateDateInput",
|
||||
"updateDateRangeInput",
|
||||
"updateNumericInput",
|
||||
"updateRadioButtons",
|
||||
"updateSelectInput",
|
||||
"updateSliderInput",
|
||||
"updateTabsetPanel",
|
||||
"updateTextInput"
|
||||
)
|
||||
)
|
||||
sd_section("UI Outputs",
|
||||
"Functions for creating user interface elements that, in conjunction with rendering functions, display different kinds of output from your application.",
|
||||
c(
|
||||
"htmlOutput",
|
||||
"imageOutput",
|
||||
"plotOutput",
|
||||
"outputOptions",
|
||||
"tableOutput",
|
||||
"textOutput",
|
||||
"verbatimTextOutput",
|
||||
"downloadButton"
|
||||
)
|
||||
)
|
||||
sd_section("Interface builder functions",
|
||||
"A sub-library for writing HTML using R functions. These functions form the foundation on which the higher level user interface functions are built, and can also be used in your Shiny UI to provide custom HTML, CSS, and JavaScript.",
|
||||
c(
|
||||
"builder",
|
||||
"HTML",
|
||||
"include",
|
||||
"singleton",
|
||||
"tag",
|
||||
"validateCssUnit",
|
||||
"withTags"
|
||||
)
|
||||
)
|
||||
sd_section("Rendering functions",
|
||||
"Functions that you use in your application's server side code, assigning them to outputs that appear in your user interface.",
|
||||
c(
|
||||
"renderPlot",
|
||||
"renderText",
|
||||
"renderPrint",
|
||||
"renderDataTable",
|
||||
"renderImage",
|
||||
"renderTable",
|
||||
"renderUI",
|
||||
"downloadHandler",
|
||||
"reactivePlot",
|
||||
"reactivePrint",
|
||||
"reactiveTable",
|
||||
"reactiveText",
|
||||
"reactiveUI"
|
||||
)
|
||||
)
|
||||
sd_section("Reactive constructs",
|
||||
"A sub-library that provides reactive programming facilities for R.",
|
||||
c(
|
||||
"invalidateLater",
|
||||
"is.reactivevalues",
|
||||
"isolate",
|
||||
"makeReactiveBinding",
|
||||
"observe",
|
||||
"reactive",
|
||||
"reactiveFileReader",
|
||||
"reactivePoll",
|
||||
"reactiveTimer",
|
||||
"reactiveValues",
|
||||
"reactiveValuesToList",
|
||||
"domains",
|
||||
"showReactLog"
|
||||
)
|
||||
)
|
||||
sd_section("Boilerplate",
|
||||
"Functions that are required boilerplate in ui.R and server.R.",
|
||||
c(
|
||||
"shinyUI",
|
||||
"shinyServer"
|
||||
)
|
||||
)
|
||||
sd_section("Running",
|
||||
"Functions that are used to run or stop Shiny applications.",
|
||||
c(
|
||||
"runApp",
|
||||
"runExample",
|
||||
"runUrl",
|
||||
"stopApp"
|
||||
)
|
||||
)
|
||||
sd_section("Extending Shiny",
|
||||
"Functions that are intended to be called by third-party packages that extend Shiny.",
|
||||
c(
|
||||
"addResourcePath",
|
||||
"registerInputHandler",
|
||||
"removeInputHandler",
|
||||
"markRenderFunction"
|
||||
)
|
||||
)
|
||||
sd_section("Utility functions",
|
||||
"Miscellaneous utilities that may be useful to advanced users or when extending Shiny.",
|
||||
c(
|
||||
"validate",
|
||||
"session",
|
||||
"exprToFunction",
|
||||
"installExprFunction",
|
||||
"parseQueryString",
|
||||
"plotPNG",
|
||||
"repeatable",
|
||||
"shinyDeprecated",
|
||||
"serverInfo"
|
||||
)
|
||||
)
|
||||
sd_section("Embedding",
|
||||
"Functions that are intended for third-party packages that embed Shiny applications.",
|
||||
c(
|
||||
"shinyApp",
|
||||
"maskReactiveContext"
|
||||
)
|
||||
)
|
||||
@@ -1145,8 +1145,8 @@ describe("Input Bindings", function() {
|
||||
label: 'Select input:',
|
||||
value: 'option1',
|
||||
options: [
|
||||
{ value: 'option1', label: 'option1 label', selected: true },
|
||||
{ value: 'option2', label: 'option2 label', selected: false }
|
||||
{ value: 'option1', label: 'option1 label' },
|
||||
{ value: 'option2', label: 'option2 label' }
|
||||
]
|
||||
});
|
||||
});
|
||||
@@ -1156,8 +1156,8 @@ describe("Input Bindings", function() {
|
||||
label: 'Select input:',
|
||||
value: 'option4',
|
||||
options: [
|
||||
{ value: 'option3', label: 'option3 label', selected: false },
|
||||
{ value: 'option4', label: 'option4 label', selected: true }
|
||||
{ value: 'option3', label: 'option3 label' },
|
||||
{ value: 'option4', label: 'option4 label' }
|
||||
]
|
||||
};
|
||||
receive_message(id, state_complete);
|
||||
@@ -1168,50 +1168,45 @@ describe("Input Bindings", function() {
|
||||
receive_message(id, { });
|
||||
expect(get_state(id)).toEqual(state_complete);
|
||||
|
||||
// Don't provide value, but set selected:true on an option
|
||||
// Don't provide value, and the default should be the first option
|
||||
var state_novalue = {
|
||||
options: [
|
||||
{ value: 'option5', label: 'option5 label', selected: false },
|
||||
{ value: 'option6', label: 'option6 label', selected: true }
|
||||
{ value: 'option5', label: 'option5 label' },
|
||||
{ value: 'option6', label: 'option6 label' }
|
||||
]
|
||||
};
|
||||
var state_novalue_expected = {
|
||||
label: 'Select input:',
|
||||
value: 'option6',
|
||||
value: 'option5',
|
||||
options: state_novalue.options
|
||||
};
|
||||
receive_message(id, state_novalue);
|
||||
expect(get_value(id)).toBe('option6');
|
||||
expect(get_value(id)).toBe('option5');
|
||||
expect(get_state(id)).toEqual(state_novalue_expected);
|
||||
|
||||
|
||||
// Provide value, but no selected:true
|
||||
var state_noselected = {
|
||||
value: 'option7',
|
||||
options: [
|
||||
{ value: 'option7', label: 'option7 label'},
|
||||
{ value: 'option8', label: 'option8 label'}
|
||||
]
|
||||
// Only update value
|
||||
var state_value = {
|
||||
value: 'option6'
|
||||
};
|
||||
var state_noselected_expected = {
|
||||
var state_value_expected = {
|
||||
label: 'Select input:',
|
||||
value: 'option7',
|
||||
value: 'option6',
|
||||
options: [
|
||||
{ value: 'option7', label: 'option7 label', selected: true },
|
||||
{ value: 'option8', label: 'option8 label', selected: false }
|
||||
{ value: 'option5', label: 'option5 label' },
|
||||
{ value: 'option6', label: 'option6 label' }
|
||||
]
|
||||
};
|
||||
receive_message(id, state_noselected);
|
||||
expect(get_value(id)).toBe('option7');
|
||||
expect(get_state(id)).toEqual(state_noselected_expected);
|
||||
receive_message(id, state_value);
|
||||
expect(get_value(id)).toEqual('option6');
|
||||
expect(get_state(id)).toEqual(state_value_expected);
|
||||
|
||||
// Set label
|
||||
var state_newlabel_complete = {
|
||||
label: 'new label',
|
||||
value: 'option9',
|
||||
options: [
|
||||
{ value: 'option9', label: 'option9 label', selected: true },
|
||||
{ value: 'option10', label: 'option10 label', selected: false }
|
||||
{ value: 'option9', label: 'option9 label' },
|
||||
{ value: 'option10', label: 'option10 label' }
|
||||
]
|
||||
};
|
||||
receive_message(id, state_newlabel_complete);
|
||||
@@ -1272,8 +1267,8 @@ describe("Input Bindings", function() {
|
||||
label: 'Radio buttons:',
|
||||
value: 'option1',
|
||||
options: [
|
||||
{ value: 'option1', label: 'option1 label', checked: true },
|
||||
{ value: 'option2', label: 'option2 label', checked: false }
|
||||
{ value: 'option1', label: 'option1 label' },
|
||||
{ value: 'option2', label: 'option2 label' }
|
||||
]
|
||||
});
|
||||
});
|
||||
@@ -1283,8 +1278,8 @@ describe("Input Bindings", function() {
|
||||
label: 'Radio buttons:',
|
||||
value: 'option4',
|
||||
options: [
|
||||
{ value: 'option3', label: 'option3 label', checked: false },
|
||||
{ value: 'option4', label: 'option4 label', checked: true }
|
||||
{ value: 'option3', label: 'option3 label' },
|
||||
{ value: 'option4', label: 'option4 label' }
|
||||
]
|
||||
};
|
||||
receive_message(id, state_complete);
|
||||
@@ -1295,50 +1290,46 @@ describe("Input Bindings", function() {
|
||||
receive_message(id, { });
|
||||
expect(get_state(id)).toEqual(state_complete);
|
||||
|
||||
// Don't provide value, but set checked:true on an option
|
||||
// Don't provide value, and the value will be undefined
|
||||
// since no option is checked
|
||||
var state_novalue = {
|
||||
options: [
|
||||
{ value: 'option5', label: 'option5 label', checked: false },
|
||||
{ value: 'option6', label: 'option6 label', checked: true }
|
||||
{ value: 'option5', label: 'option5 label' },
|
||||
{ value: 'option6', label: 'option6 label' }
|
||||
]
|
||||
};
|
||||
var state_novalue_expected = {
|
||||
label: 'Radio buttons:',
|
||||
value: 'option6',
|
||||
value: undefined,
|
||||
options: state_novalue.options
|
||||
};
|
||||
receive_message(id, state_novalue);
|
||||
expect(get_value(id)).toBe('option6');
|
||||
expect(get_value(id)).toBe(undefined);
|
||||
expect(get_state(id)).toEqual(state_novalue_expected);
|
||||
|
||||
|
||||
// Provide value, but no checked:true
|
||||
var state_nochecked = {
|
||||
value: 'option7',
|
||||
options: [
|
||||
{ value: 'option7', label: 'option7 label'},
|
||||
{ value: 'option8', label: 'option8 label'}
|
||||
]
|
||||
// Only update value
|
||||
var state_value = {
|
||||
value: 'option6'
|
||||
};
|
||||
var state_nochecked_expected = {
|
||||
var state_value_expected = {
|
||||
label: 'Radio buttons:',
|
||||
value: 'option7',
|
||||
value: 'option6',
|
||||
options: [
|
||||
{ value: 'option7', label: 'option7 label', checked: true },
|
||||
{ value: 'option8', label: 'option8 label', checked: false }
|
||||
{ value: 'option5', label: 'option5 label' },
|
||||
{ value: 'option6', label: 'option6 label' }
|
||||
]
|
||||
};
|
||||
receive_message(id, state_nochecked);
|
||||
expect(get_value(id)).toBe('option7');
|
||||
expect(get_state(id)).toEqual(state_nochecked_expected);
|
||||
receive_message(id, state_value);
|
||||
expect(get_value(id)).toEqual('option6');
|
||||
expect(get_state(id)).toEqual(state_value_expected);
|
||||
|
||||
// Set label
|
||||
var state_newlabel_complete = {
|
||||
label: 'new label',
|
||||
value: 'option9',
|
||||
options: [
|
||||
{ value: 'option9', label: 'option9 label', checked: true },
|
||||
{ value: 'option10', label: 'option10 label', checked: false }
|
||||
{ value: 'option9', label: 'option9 label' },
|
||||
{ value: 'option10', label: 'option10 label' }
|
||||
]
|
||||
};
|
||||
receive_message(id, state_newlabel_complete);
|
||||
@@ -1419,8 +1410,8 @@ describe("Input Bindings", function() {
|
||||
label: 'Checkbox group:',
|
||||
value: ['option1'],
|
||||
options: [
|
||||
{ value: 'option1', label: 'option1 label', checked: true },
|
||||
{ value: 'option2', label: 'option2 label', checked: false }
|
||||
{ value: 'option1', label: 'option1 label' },
|
||||
{ value: 'option2', label: 'option2 label' }
|
||||
]
|
||||
});
|
||||
});
|
||||
@@ -1430,8 +1421,8 @@ describe("Input Bindings", function() {
|
||||
label: 'Checkbox group:',
|
||||
value: ['option4'],
|
||||
options: [
|
||||
{ value: 'option3', label: 'option3 label', checked: false },
|
||||
{ value: 'option4', label: 'option4 label', checked: true }
|
||||
{ value: 'option3', label: 'option3 label' },
|
||||
{ value: 'option4', label: 'option4 label' }
|
||||
]
|
||||
};
|
||||
receive_message(id, state_complete);
|
||||
@@ -1442,50 +1433,46 @@ describe("Input Bindings", function() {
|
||||
receive_message(id, { });
|
||||
expect(get_state(id)).toEqual(state_complete);
|
||||
|
||||
// Don't provide value, but set checked:true on an option
|
||||
// Don't provide value
|
||||
var state_novalue = {
|
||||
options: [
|
||||
{ value: 'option5', label: 'option5 label', checked: true },
|
||||
{ value: 'option6', label: 'option6 label', checked: true }
|
||||
{ value: 'option5', label: 'option5 label' },
|
||||
{ value: 'option6', label: 'option6 label' }
|
||||
]
|
||||
};
|
||||
var state_novalue_expected = {
|
||||
label: 'Checkbox group:',
|
||||
value: ['option5', 'option6'],
|
||||
value: [ ],
|
||||
options: state_novalue.options
|
||||
};
|
||||
receive_message(id, state_novalue);
|
||||
expect(get_value(id)).toEqual(['option5', 'option6']);
|
||||
expect(get_value(id)).toEqual([ ]);
|
||||
expect(get_state(id)).toEqual(state_novalue_expected);
|
||||
|
||||
|
||||
// Provide value, but no checked:true
|
||||
var state_nochecked = {
|
||||
value: 'option7',
|
||||
options: [
|
||||
{ value: 'option7', label: 'option7 label'},
|
||||
{ value: 'option8', label: 'option8 label'}
|
||||
]
|
||||
// Only update value
|
||||
var state_value = {
|
||||
value: 'option6'
|
||||
};
|
||||
var state_nochecked_expected = {
|
||||
var state_value_expected = {
|
||||
label: 'Checkbox group:',
|
||||
value: ['option7'],
|
||||
value: ['option6'],
|
||||
options: [
|
||||
{ value: 'option7', label: 'option7 label', checked: true },
|
||||
{ value: 'option8', label: 'option8 label', checked: false }
|
||||
{ value: 'option5', label: 'option5 label' },
|
||||
{ value: 'option6', label: 'option6 label' }
|
||||
]
|
||||
};
|
||||
receive_message(id, state_nochecked);
|
||||
expect(get_value(id)).toEqual(['option7']);
|
||||
expect(get_state(id)).toEqual(state_nochecked_expected);
|
||||
receive_message(id, state_value);
|
||||
expect(get_value(id)).toEqual(['option6']);
|
||||
expect(get_state(id)).toEqual(state_value_expected);
|
||||
|
||||
// Set label
|
||||
var state_newlabel_complete = {
|
||||
label: 'Checkbox group new label:',
|
||||
value: ['option4'],
|
||||
options: [
|
||||
{ value: 'option3', label: 'option3 label', checked: false },
|
||||
{ value: 'option4', label: 'option4 label', checked: true }
|
||||
{ value: 'option3', label: 'option3 label' },
|
||||
{ value: 'option4', label: 'option4 label' }
|
||||
]
|
||||
};
|
||||
receive_message(id, state_newlabel_complete);
|
||||
|
||||
@@ -7,8 +7,8 @@ test_that("CSS unit validation", {
|
||||
}
|
||||
|
||||
# Test strings and expected results
|
||||
strings <- c("100x", "10px", "10.4px", ".4px", "1px0", "px", "5", "%", "5%", "auto", "1auto", "")
|
||||
expected <- c(NA, "10px", "10.4px", ".4px", NA, NA, NA, NA, "5%", "auto", NA, NA)
|
||||
strings <- c("100x", "10px", "10.4px", ".4px", "1px0", "px", "5", "%", "5%", "auto", "1auto", "")
|
||||
expected <- c(NA, "10px", "10.4px", ".4px", NA, NA, "5px", NA, "5%", "auto", NA, NA)
|
||||
results <- vapply(strings, validateCssUnit_wrap, character(1), USE.NAMES = FALSE)
|
||||
expect_equal(results, expected)
|
||||
|
||||
@@ -22,39 +22,126 @@ test_that("Repeated names for selectInput and radioButtons choices", {
|
||||
# tag object, but they get the job done for now.
|
||||
|
||||
# Select input
|
||||
x <- selectInput('id','label', choices = c(a='x1', a='x2', b='x3'))
|
||||
choices <- x[[2]]$children
|
||||
|
||||
expect_equal(choices[[1]]$children[[1]], 'a')
|
||||
expect_equal(choices[[1]]$attribs$value, 'x1')
|
||||
expect_equal(choices[[1]]$attribs$selected, 'selected')
|
||||
|
||||
expect_equal(choices[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]]$attribs$value, 'x2')
|
||||
# This one actually should be NULL, but with the syntax of selectInput, it
|
||||
# must be 'selected'.
|
||||
expect_equal(choices[[2]]$attribs$selected, 'selected')
|
||||
|
||||
expect_equal(choices[[3]]$children[[1]], 'b')
|
||||
expect_equal(choices[[3]]$attribs$value, 'x3')
|
||||
expect_equal(choices[[3]]$attribs$selected, NULL)
|
||||
x <- selectInput('id','label', choices = c(a='x1', a='x2', b='x3'), selectize = FALSE)
|
||||
expect_equal(format(x), '<label class="control-label" for="id">label</label>
|
||||
<select id="id"><option value="x1" selected>a</option>\n<option value="x2">a</option>\n<option value="x3">b</option></select>')
|
||||
|
||||
|
||||
# Radio buttons
|
||||
x <- radioButtons('id','label', choices = c(a='x1', a='x2', b='x3'))
|
||||
choices <- x$children
|
||||
|
||||
expect_equal(choices[[2]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]]$children[[1]]$attribs$value, 'x1')
|
||||
expect_equal(choices[[2]]$children[[1]]$attribs$checked, 'checked')
|
||||
expect_equal(choices[[2]][[1]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]][[1]]$children[[1]]$attribs$value, 'x1')
|
||||
expect_equal(choices[[2]][[1]]$children[[1]]$attribs$checked, 'checked')
|
||||
|
||||
expect_equal(choices[[3]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[3]]$children[[1]]$attribs$value, 'x2')
|
||||
# This one actually should be NULL, but with the syntax of radioButtons, it
|
||||
# must be 'checked'.
|
||||
expect_equal(choices[[3]]$children[[1]]$attribs$checked, 'checked')
|
||||
expect_equal(choices[[2]][[2]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]][[2]]$children[[1]]$attribs$value, 'x2')
|
||||
expect_equal(choices[[2]][[2]]$children[[1]]$attribs$checked, NULL)
|
||||
|
||||
expect_equal(choices[[4]]$children[[2]]$children[[1]], 'b')
|
||||
expect_equal(choices[[4]]$children[[1]]$attribs$value, 'x3')
|
||||
expect_equal(choices[[4]]$children[[1]]$attribs$checked, NULL)
|
||||
expect_equal(choices[[2]][[3]]$children[[2]]$children[[1]], 'b')
|
||||
expect_equal(choices[[2]][[3]]$children[[1]]$attribs$value, 'x3')
|
||||
expect_equal(choices[[2]][[3]]$children[[1]]$attribs$checked, NULL)
|
||||
})
|
||||
|
||||
|
||||
test_that("Choices are correctly assigned names", {
|
||||
# Unnamed vector
|
||||
expect_identical(
|
||||
choicesWithNames(c("a","b","3")),
|
||||
list(a="a", b="b", "3"="3")
|
||||
)
|
||||
# Unnamed list
|
||||
expect_identical(
|
||||
choicesWithNames(list("a","b",3)),
|
||||
list(a="a", b="b", "3"=3)
|
||||
)
|
||||
# Vector, with some named, some not
|
||||
expect_identical(
|
||||
choicesWithNames(c(A="a", "b", C="3", "4")),
|
||||
list(A="a", "b"="b", C="3", "4"="4")
|
||||
)
|
||||
# List, with some named, some not
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", "b", C=3, 4)),
|
||||
list(A="a", "b"="b", C=3, "4"=4)
|
||||
)
|
||||
# List, named, with a sub-vector
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", B="b", C=c("d", "e"))),
|
||||
list(A="a", B="b", C=list(d="d", e="e"))
|
||||
)
|
||||
# List, named, with sublist
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", B="b", C=list("d", "e"))),
|
||||
list(A="a", B="b", C=list(d="d", e="e"))
|
||||
)
|
||||
# List, some named, with sublist
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", "b", C=list("d", E="e"))),
|
||||
list(A="a", b="b", C=list(d="d", E="e"))
|
||||
)
|
||||
# Deeper nesting
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", "b", C=list(D=list("e", "f"), G=c(H="h", "i")))),
|
||||
list(A="a", b="b", C=list(D=list(e="e", f="f"), G=list(H="h", i="i")))
|
||||
)
|
||||
# Error when sublist is unnamed
|
||||
expect_error(choicesWithNames(list(A="a", "b", list(1,2))))
|
||||
})
|
||||
|
||||
|
||||
test_that("selectOptions returns correct HTML", {
|
||||
# None selected
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("a", "b")), list()),
|
||||
HTML("<option value=\"a\">a</option>\n<option value=\"b\">b</option>")
|
||||
)
|
||||
# One selected
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("a", "b")), "a"),
|
||||
HTML("<option value=\"a\" selected>a</option>\n<option value=\"b\">b</option>")
|
||||
)
|
||||
# One selected, with named items
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list(A="a", B="b")), "a"),
|
||||
HTML("<option value=\"a\" selected>A</option>\n<option value=\"b\">B</option>")
|
||||
)
|
||||
# Two selected, with optgroup
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("a", B=list("c", D="d"))), c("a", "d")),
|
||||
HTML("<option value=\"a\" selected>a</option>\n<optgroup label=\"B\">\n<option value=\"c\">c</option>\n<option value=\"d\" selected>D</option>\n</optgroup>")
|
||||
)
|
||||
|
||||
# Escape HTML in strings
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("<A>"="a", B="b")), "a"),
|
||||
HTML("<option value=\"a\" selected><A></option>\n<option value=\"b\">B</option>")
|
||||
)
|
||||
})
|
||||
|
||||
test_that("selectInput selects items by default", {
|
||||
# None specified as selected (defaults to first)
|
||||
expect_true(grepl(
|
||||
'<option value="a" selected>',
|
||||
selectInput('x', 'x', list("a", "b"))
|
||||
))
|
||||
|
||||
# Nested list (optgroup)
|
||||
expect_true(grepl(
|
||||
'<option value="a" selected>',
|
||||
selectInput('x', 'x', list(A=list("a", "b"), "c"))
|
||||
))
|
||||
|
||||
# Nothing selected when choices=NULL
|
||||
expect_identical(
|
||||
'<select id="x"></select>',
|
||||
format(selectInput('x', NULL, NULL, selectize = FALSE))
|
||||
)
|
||||
|
||||
# None specified as selected. With multiple=TRUE, none selected by default.
|
||||
expect_true(grepl(
|
||||
'<option value="a">',
|
||||
selectInput('x', 'x', list("a", "b"), multiple = TRUE)
|
||||
))
|
||||
})
|
||||
|
||||
47
inst/tests/test-input-handler.R
Normal file
@@ -0,0 +1,47 @@
|
||||
context("Parse Shiny Input")
|
||||
|
||||
test_that("A new type can be registered successfully", {
|
||||
registerInputHandler("shiny.someType", function(){})
|
||||
})
|
||||
|
||||
test_that("A duplicated type throws", {
|
||||
expect_error({
|
||||
registerInputHandler("shiny.dupType", function(){})
|
||||
registerInputHandler("shiny.dupType", function(){})
|
||||
})
|
||||
})
|
||||
|
||||
test_that("Date converts to date", {
|
||||
x <- "2013/01/01"
|
||||
class(x) <- "shiny.date"
|
||||
handler <- inputHandlers$get('shiny.date')
|
||||
expect_identical(
|
||||
handler(x), as.Date(unclass(x))
|
||||
)
|
||||
})
|
||||
|
||||
test_that("List of dates converts to vector", {
|
||||
x <- list("2013/01/01", "2014/01/01")
|
||||
class(x) <- "shiny.date"
|
||||
handler <- inputHandlers$get('shiny.date')
|
||||
expect_identical(
|
||||
handler(x), as.Date(unlist(x))
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Matrix converts list of lists to matrix", {
|
||||
x <- list(a=1:3,b=4:6)
|
||||
class(x) <- "shiny.matrix"
|
||||
handler <- inputHandlers$get('shiny.matrix')
|
||||
expect_identical(
|
||||
handler(x), matrix(c(1:3,4:6), byrow=FALSE, ncol=2)
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Nulls are not converted to NAs in parsing", {
|
||||
msg <- charToRaw("{\"method\":\"init\",\"data\":{\"obs\":500,\"nullObs\":null}}")
|
||||
expect_identical(
|
||||
decodeMessage(msg),
|
||||
list(method="init", data=list(obs=500, nullObs=NULL))
|
||||
)
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
context("Parse Shiny Input")
|
||||
|
||||
test_that("Default function is a pass-through", {
|
||||
x <- "2013/01/01"
|
||||
expect_identical(
|
||||
parseShinyInput(x), x
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Date converts to date", {
|
||||
x <- "2013/01/01"
|
||||
class(x) <- "shinyDate"
|
||||
expect_identical(
|
||||
parseShinyInput(x), as.Date(unclass(x))
|
||||
)
|
||||
})
|
||||
|
||||
test_that("List of dates converts to vector", {
|
||||
x <- list("2013/01/01", "2014/01/01")
|
||||
class(x) <- "shinyDate"
|
||||
expect_identical(
|
||||
parseShinyInput(x), as.Date(unlist(x))
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Matrix converts list of lists to matrix", {
|
||||
x <- list(a=1:3,b=4:6)
|
||||
class(x) <- "shinyMatrix"
|
||||
expect_identical(
|
||||
parseShinyInput(x), matrix(c(1:3,4:6), byrow=FALSE, ncol=2)
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Nulls are converted to NAs in parsing", {
|
||||
msg <- charToRaw("{\"method\":\"init\",\"data\":{\"obs\":500,\"nullObs\":null}}")
|
||||
expect_identical(
|
||||
decodeMessage(msg),
|
||||
list(method="init", data=list(obs=500, nullObs=NA))
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -694,4 +694,130 @@ test_that("classes of reactive object", {
|
||||
expect_false(is.reactive(v))
|
||||
expect_true(is.reactive(r))
|
||||
expect_false(is.reactive(o))
|
||||
|
||||
o$destroy()
|
||||
})
|
||||
|
||||
test_that("{} and NULL also work in reactive()", {
|
||||
reactive({})
|
||||
reactive(NULL)
|
||||
})
|
||||
|
||||
test_that("shiny.suppressMissingContextError option works", {
|
||||
options(shiny.suppressMissingContextError=TRUE)
|
||||
on.exit(options(shiny.suppressMissingContextError=FALSE), add = TRUE)
|
||||
|
||||
expect_true(reactive(TRUE)())
|
||||
})
|
||||
|
||||
test_that("reactive domains are inherited", {
|
||||
|
||||
domainA <- createMockDomain()
|
||||
domainB <- createMockDomain()
|
||||
|
||||
local({
|
||||
domainY <- NULL
|
||||
domainZ <- NULL
|
||||
x <- observe({
|
||||
|
||||
y <- observe({
|
||||
# Should be domainA (inherited from observer x)
|
||||
domainY <<- getDefaultReactiveDomain()
|
||||
})
|
||||
|
||||
z <- observe({
|
||||
# Should be domainB (explicitly passed in)
|
||||
domainZ <<- getDefaultReactiveDomain()
|
||||
}, domain = domainB)
|
||||
|
||||
}, domain = domainA)
|
||||
|
||||
flushReact()
|
||||
flushReact()
|
||||
|
||||
expect_identical(domainY, domainA)
|
||||
expect_identical(domainZ, domainB)
|
||||
})
|
||||
|
||||
local({
|
||||
domainY <- 1
|
||||
x <- NULL
|
||||
y <- NULL
|
||||
z <- NULL
|
||||
r3 <- NULL
|
||||
domainR3 <- NULL
|
||||
|
||||
r1 <- reactive({
|
||||
y <<- observe({
|
||||
# Should be NULL (r1 has no domain)
|
||||
domainY <<- getDefaultReactiveDomain()
|
||||
})
|
||||
})
|
||||
r2 <- reactive({
|
||||
z <<- observe({
|
||||
# Should be domainB (r2 has explicit domainB)
|
||||
domainZ <<- getDefaultReactiveDomain()
|
||||
})
|
||||
}, domain = domainB)
|
||||
|
||||
observe({
|
||||
r3 <<- reactive({
|
||||
# This should be domainA. Doesn't matter where r3 is invoked, it only
|
||||
# matters where it was created.
|
||||
domainR3 <<- getDefaultReactiveDomain()
|
||||
})
|
||||
r1()
|
||||
r2()
|
||||
}, domain = domainA)
|
||||
|
||||
flushReact()
|
||||
flushReact()
|
||||
isolate(r3())
|
||||
|
||||
expect_identical(execCount(y), 1L)
|
||||
expect_identical(execCount(z), 1L)
|
||||
expect_identical(domainY, NULL)
|
||||
expect_identical(domainZ, domainB)
|
||||
expect_identical(domainR3, domainA)
|
||||
})
|
||||
})
|
||||
|
||||
test_that("observers autodestroy (or not)", {
|
||||
|
||||
domainA <- createMockDomain()
|
||||
local({
|
||||
a <- observe(NULL, domain = domainA)
|
||||
|
||||
b <- observe(NULL, domain = domainA, autoDestroy = FALSE)
|
||||
|
||||
c <- observe(NULL, domain = domainA)
|
||||
c$setAutoDestroy(FALSE)
|
||||
|
||||
d <- observe(NULL, domain = domainA, autoDestroy = FALSE)
|
||||
d$setAutoDestroy(TRUE)
|
||||
|
||||
e <- observe(NULL)
|
||||
|
||||
domainA$end()
|
||||
|
||||
flushReact()
|
||||
|
||||
expect_identical(execCount(a), 0L)
|
||||
expect_identical(execCount(b), 1L)
|
||||
expect_identical(execCount(c), 1L)
|
||||
expect_identical(execCount(d), 0L)
|
||||
expect_identical(execCount(e), 1L)
|
||||
})
|
||||
})
|
||||
|
||||
test_that("maskReactiveContext blocks use of reactives", {
|
||||
vals <- reactiveValues(x = 123)
|
||||
|
||||
# Block reactive contexts (created by isolate)
|
||||
expect_error(isolate(maskReactiveContext(vals$x)))
|
||||
expect_error(isolate(isolate(maskReactiveContext(vals$x))))
|
||||
|
||||
# Reactive contexts within maskReactiveContext shouldn't be blocked
|
||||
expect_identical(maskReactiveContext(isolate(vals$x)), 123)
|
||||
expect_identical(isolate(maskReactiveContext(isolate(vals$x))), 123)
|
||||
})
|
||||
|
||||
35
inst/tests/test-staticdocs.R
Normal file
@@ -0,0 +1,35 @@
|
||||
context("staticdocs")
|
||||
|
||||
test_that("All man pages have an entry in staticdocs/index.r", {
|
||||
if (!all(file.exists(c('../../inst/staticdocs', '../../man')))) {
|
||||
# This test works only when run against a package directory
|
||||
return()
|
||||
}
|
||||
# Known not to be indexed
|
||||
known_unindexed <- c("shiny-package", "knitr_methods", "knitr_methods_htmltools")
|
||||
|
||||
indexed_topics <- local({
|
||||
result <- character(0)
|
||||
sd_section <- function(dummy1, dummy2, section_topics) {
|
||||
result <<- c(result, section_topics)
|
||||
}
|
||||
source("../../inst/staticdocs/index.r", local = TRUE)
|
||||
result
|
||||
})
|
||||
|
||||
all_topics <- sub("\\.Rd", "", list.files("../../man", pattern = "*.Rd"))
|
||||
|
||||
# This test ensures that every documented topic is included in
|
||||
# staticdocs/index.r, unless explicitly waived by specifying it
|
||||
# in the known_unindexed variable above.
|
||||
missing <- setdiff(sort(all_topics), sort(c(known_unindexed, indexed_topics)))
|
||||
unknown <- setdiff(sort(c(known_unindexed, indexed_topics)), sort(all_topics))
|
||||
expect_equal(length(missing), 0,
|
||||
info = paste("Functions missing from index:\n",
|
||||
paste(" ", missing, sep = "", collapse = "\n"),
|
||||
sep = ""))
|
||||
expect_equal(length(unknown), 0,
|
||||
info = paste("Unrecognized functions in index.r:\n",
|
||||
paste(" ", unknown, sep = "", collapse = "\n"),
|
||||
sep = ""))
|
||||
})
|
||||
@@ -1,319 +0,0 @@
|
||||
context("tags")
|
||||
|
||||
test_that("Basic tag writing works", {
|
||||
expect_equal(as.character(tagList("hi")), HTML("hi"))
|
||||
expect_equal(
|
||||
as.character(tagList("one", "two", tagList("three"))),
|
||||
HTML("one\ntwo\nthree"))
|
||||
expect_equal(
|
||||
as.character(tags$b("one")),
|
||||
HTML("<b>one</b>"))
|
||||
expect_equal(
|
||||
as.character(tags$b("one", "two")),
|
||||
HTML("<b>\n one\n two\n</b>"))
|
||||
expect_equal(
|
||||
as.character(tagList(list("one"))),
|
||||
HTML("one"))
|
||||
expect_equal(
|
||||
as.character(tagList(list(tagList("one")))),
|
||||
HTML("one"))
|
||||
expect_equal(
|
||||
as.character(tagList(tags$br(), "one")),
|
||||
HTML("<br/>\none"))
|
||||
})
|
||||
|
||||
|
||||
test_that("withTags works", {
|
||||
output_tags <- tags$div(class = "myclass",
|
||||
tags$h3("header"),
|
||||
tags$p("text here")
|
||||
)
|
||||
output_withhtml <- withTags(
|
||||
div(class = "myclass",
|
||||
h3("header"),
|
||||
p("text here")
|
||||
)
|
||||
)
|
||||
expect_identical(output_tags, output_withhtml)
|
||||
|
||||
|
||||
# Check that current environment is searched
|
||||
x <- 100
|
||||
expect_identical(tags$p(x), withTags(p(x)))
|
||||
|
||||
# Just to make sure, run it in a function, which has its own environment
|
||||
foo <- function() {
|
||||
y <- 100
|
||||
withTags(p(y))
|
||||
}
|
||||
expect_identical(tags$p(100), foo())
|
||||
})
|
||||
|
||||
|
||||
test_that("HTML escaping in tags", {
|
||||
# Regular text is escaped
|
||||
expect_equivalent(format(div("<a&b>")), "<div><a&b></div>")
|
||||
|
||||
# Text in HTML() isn't escaped
|
||||
expect_equivalent(format(div(HTML("<a&b>"))), "<div><a&b></div>")
|
||||
|
||||
# Text in a property is escaped
|
||||
expect_equivalent(format(div(class = "<a&b>", "text")),
|
||||
'<div class="<a&b>">text</div>')
|
||||
|
||||
# HTML() has no effect in a property like 'class'
|
||||
expect_equivalent(format(div(class = HTML("<a&b>"), "text")),
|
||||
'<div class="<a&b>">text</div>')
|
||||
})
|
||||
|
||||
|
||||
test_that("Adding child tags", {
|
||||
tag_list <- list(tags$p("tag1"), tags$b("tag2"), tags$i("tag3"))
|
||||
|
||||
# Creating nested tags by calling the tag$div function and passing a list
|
||||
t1 <- tags$div(class="foo", tag_list)
|
||||
expect_equal(length(t1$children), 3)
|
||||
expect_equal(t1$children[[1]]$name, "p")
|
||||
expect_equal(t1$children[[1]]$children[[1]], "tag1")
|
||||
expect_equal(t1$children[[2]]$name, "b")
|
||||
expect_equal(t1$children[[2]]$children[[1]], "tag2")
|
||||
expect_equal(t1$children[[3]]$name, "i")
|
||||
expect_equal(t1$children[[3]]$children[[1]], "tag3")
|
||||
|
||||
|
||||
# div tag used as starting point for tests below
|
||||
div_tag <- tags$div(class="foo")
|
||||
|
||||
# Appending each child
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChild(t2, tag_list[[2]])
|
||||
t2 <- tagAppendChild(t2, tag_list[[3]])
|
||||
expect_identical(t1, t2)
|
||||
|
||||
|
||||
# tagSetChildren, using list argument
|
||||
t2 <- tagSetChildren(div_tag, list = tag_list)
|
||||
expect_identical(t1, t2)
|
||||
|
||||
# tagSetChildren, using ... arguments
|
||||
t2 <- tagSetChildren(div_tag, tag_list[[1]], tag_list[[2]], tag_list[[3]])
|
||||
expect_identical(t1, t2)
|
||||
|
||||
# tagSetChildren, using ... and list arguments
|
||||
t2 <- tagSetChildren(div_tag, tag_list[[1]], list = tag_list[2:3])
|
||||
expect_identical(t1, t2)
|
||||
|
||||
# tagSetChildren overwrites existing children
|
||||
t2 <- tagAppendChild(div_tag, p("should replace this tag"))
|
||||
t2 <- tagSetChildren(div_tag, list = tag_list)
|
||||
expect_identical(t1, t2)
|
||||
|
||||
|
||||
# tagAppendChildren, using list argument
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChildren(t2, list = tag_list[2:3])
|
||||
expect_identical(t1, t2)
|
||||
|
||||
# tagAppendChildren, using ... arguments
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChildren(t2, tag_list[[2]], tag_list[[3]])
|
||||
expect_identical(t1, t2)
|
||||
|
||||
# tagAppendChildren, using ... and list arguments
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChildren(t2, tag_list[[2]], list = list(tag_list[[3]]))
|
||||
expect_identical(t1, t2)
|
||||
|
||||
# tagAppendChildren can start with no children
|
||||
t2 <- tagAppendChildren(div_tag, list = tag_list)
|
||||
expect_identical(t1, t2)
|
||||
|
||||
|
||||
# tagSetChildren preserves attributes
|
||||
x <- tagSetChildren(div(), HTML("text"))
|
||||
expect_identical(attr(x$children[[1]], "html"), TRUE)
|
||||
|
||||
# tagAppendChildren preserves attributes
|
||||
x <- tagAppendChildren(div(), HTML("text"))
|
||||
expect_identical(attr(x$children[[1]], "html"), TRUE)
|
||||
})
|
||||
|
||||
|
||||
test_that("Creating simple tags", {
|
||||
# Empty tag
|
||||
expect_identical(
|
||||
div(),
|
||||
structure(
|
||||
list(name = "div", attribs = list(), children = list()),
|
||||
.Names = c("name", "attribs", "children"),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
)
|
||||
|
||||
# Tag with text
|
||||
expect_identical(
|
||||
div("text"),
|
||||
structure(
|
||||
list(name = "div", attribs = list(), children = list("text")),
|
||||
.Names = c("name", "attribs", "children"),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
)
|
||||
|
||||
# NULL attributes are dropped
|
||||
expect_identical(
|
||||
div(a = NULL, b = "value"),
|
||||
div(b = "value")
|
||||
)
|
||||
|
||||
# Numbers are coerced to strings
|
||||
expect_identical(
|
||||
div(1234),
|
||||
structure(
|
||||
list(name = "div", attribs = list(), children = list("1234")),
|
||||
.Names = c("name", "attribs", "children"),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
test_that("Creating nested tags", {
|
||||
# Simple version
|
||||
# Note that the $children list should not have a names attribute
|
||||
expect_identical(
|
||||
div(class="foo", list("a", "b")),
|
||||
structure(
|
||||
list(name = "div",
|
||||
attribs = structure(list(class = "foo"), .Names = "class"),
|
||||
children = list("a", "b")),
|
||||
.Names = c("name", "attribs", "children"),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
)
|
||||
|
||||
# More complex version
|
||||
t1 <- withTags(
|
||||
div(class = "foo",
|
||||
p("child tag"),
|
||||
list(
|
||||
p("in-list child tag 1"),
|
||||
"in-list character string",
|
||||
p(),
|
||||
p("in-list child tag 2")
|
||||
),
|
||||
"character string",
|
||||
1234
|
||||
)
|
||||
)
|
||||
|
||||
# t1 should be identical to this data structure.
|
||||
# The nested list should be flattened, and non-tag, non-strings should be
|
||||
# converted to strings
|
||||
t1_full <- structure(
|
||||
list(
|
||||
name = "div",
|
||||
attribs = list(class = "foo"),
|
||||
children = list(
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list("child tag")),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list("in-list child tag 1")),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
"in-list character string",
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list()),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list("in-list child tag 2")),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
"character string",
|
||||
"1234"
|
||||
)
|
||||
),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
|
||||
expect_identical(t1, t1_full)
|
||||
})
|
||||
|
||||
test_that("Attributes are preserved", {
|
||||
# HTML() adds an attribute to the data structure (note that this is
|
||||
# different from the 'attribs' field in the list)
|
||||
x <- HTML("<tag>&&</tag>")
|
||||
expect_identical(attr(x, "html"), TRUE)
|
||||
expect_equivalent(format(x), "<tag>&&</tag>")
|
||||
|
||||
# Make sure attributes are preserved when wrapped in other tags
|
||||
x <- div(HTML("<tag>&&</tag>"))
|
||||
expect_equivalent(x$children[[1]], HTML("<tag>&&</tag>"))
|
||||
expect_identical(attr(x$children[[1]], "html"), TRUE)
|
||||
expect_equivalent(format(x), "<div><tag>&&</tag></div>")
|
||||
|
||||
# Deeper nesting
|
||||
x <- div(p(HTML("<tag>&&</tag>")))
|
||||
expect_equivalent(x$children[[1]]$children[[1]], HTML("<tag>&&</tag>"))
|
||||
expect_identical(attr(x$children[[1]]$children[[1]], "html"), TRUE)
|
||||
expect_equivalent(format(x), "<div>\n <p><tag>&&</tag></p>\n</div>")
|
||||
})
|
||||
|
||||
|
||||
test_that("Flattening a list of tags", {
|
||||
# Flatten a nested list
|
||||
nested <- list(
|
||||
"a1",
|
||||
list(
|
||||
"b1",
|
||||
list("c1", "c2"),
|
||||
list(),
|
||||
"b2",
|
||||
list("d1", "d2")
|
||||
),
|
||||
"a2"
|
||||
)
|
||||
flat <- list("a1", "b1", "c1", "c2", "b2", "d1", "d2", "a2")
|
||||
expect_identical(flattenTags(nested), flat)
|
||||
|
||||
# no-op for flat lists
|
||||
expect_identical(flattenTags(list(a="1", "b")), list(a="1", "b"))
|
||||
|
||||
# numbers are coerced to character
|
||||
expect_identical(flattenTags(list(a=1, "b")), list(a="1", "b"))
|
||||
|
||||
# empty list results in empty list
|
||||
expect_identical(flattenTags(list()), list())
|
||||
|
||||
# preserve attributes
|
||||
nested <- list("txt1", list(structure("txt2", prop="prop2")))
|
||||
flat <- list("txt1",
|
||||
structure("txt2", prop="prop2"))
|
||||
expect_identical(flattenTags(nested), flat)
|
||||
})
|
||||
|
||||
test_that("Head and singleton behavior", {
|
||||
result <- renderTags(tagList(
|
||||
tags$head(singleton("hello"))
|
||||
))
|
||||
|
||||
expect_identical(result$html, HTML(""))
|
||||
expect_identical(result$head, HTML(" hello"))
|
||||
expect_identical(result$singletons, "60eed8231e688bcba7c275c58dd2e3b4dacb61f0")
|
||||
|
||||
# Ensure that "hello" actually behaves like a singleton
|
||||
result2 <- renderTags(tagList(
|
||||
tags$head(singleton("hello"))
|
||||
), singletons = result$singletons)
|
||||
|
||||
expect_identical(result$singletons, result2$singletons)
|
||||
expect_identical(result2$head, HTML(""))
|
||||
expect_identical(result2$html, HTML(""))
|
||||
})
|
||||
90
inst/tests/test-utils.R
Normal file
@@ -0,0 +1,90 @@
|
||||
context("utils")
|
||||
|
||||
test_that("Private randomness works at startup", {
|
||||
|
||||
if (exists(".Random.seed", envir = .GlobalEnv))
|
||||
rm(".Random.seed", envir = .GlobalEnv)
|
||||
.globals$ownSeed <- NULL
|
||||
# Just make sure this doesn't blow up
|
||||
createUniqueId(4)
|
||||
})
|
||||
|
||||
test_that("Setting process-wide seed doesn't affect private randomness", {
|
||||
set.seed(0)
|
||||
id1 <- createUniqueId(4)
|
||||
set.seed(0)
|
||||
id2 <- createUniqueId(4)
|
||||
|
||||
expect_false(identical(id1, id2))
|
||||
})
|
||||
|
||||
test_that("Resetting private seed doesn't result in dupes", {
|
||||
.globals$ownSeed <- NULL
|
||||
id3 <- createUniqueId(4)
|
||||
# Make sure we let enough time pass that reinitializing the seed is
|
||||
# going to result in a different value. This is especially required
|
||||
# on Windows.
|
||||
Sys.sleep(1)
|
||||
set.seed(0)
|
||||
.globals$ownSeed <- NULL
|
||||
id4 <- createUniqueId(4)
|
||||
|
||||
expect_false(identical(id3, id4))
|
||||
})
|
||||
|
||||
test_that("Clearing process-wide seed doesn't affect private randomness", {
|
||||
set.seed(NULL)
|
||||
id5 <- createUniqueId(4)
|
||||
set.seed(NULL)
|
||||
id6 <- createUniqueId(4)
|
||||
|
||||
expect_false(identical(id5, id6))
|
||||
})
|
||||
|
||||
test_that("Setting the private seed explicitly results in identical values", {
|
||||
set.seed(0)
|
||||
.globals$ownSeed <- .Random.seed
|
||||
id7 <- createUniqueId(4)
|
||||
set.seed(0)
|
||||
.globals$ownSeed <- .Random.seed
|
||||
id8 <- createUniqueId(4)
|
||||
|
||||
expect_identical(id7, id8)
|
||||
})
|
||||
|
||||
test_that("need() works as expected", {
|
||||
|
||||
# These are all falsy
|
||||
|
||||
expect_false(need(FALSE, FALSE))
|
||||
expect_false(need(NULL, FALSE))
|
||||
expect_false(need("", FALSE))
|
||||
|
||||
expect_false(need(character(0), FALSE))
|
||||
expect_false(need(logical(0), FALSE))
|
||||
expect_false(need(numeric(0), FALSE))
|
||||
expect_false(need(integer(0), FALSE))
|
||||
expect_false(need(complex(0), FALSE))
|
||||
expect_false(need(matrix(), FALSE))
|
||||
|
||||
expect_false(need(NA, FALSE))
|
||||
expect_false(need(NA_integer_, FALSE))
|
||||
expect_false(need(NA_real_, FALSE))
|
||||
expect_false(need(NA_complex_, FALSE))
|
||||
expect_false(need(NA_character_, FALSE))
|
||||
|
||||
expect_false(need(c(NA, NA, FALSE), FALSE))
|
||||
expect_false(need(c(FALSE), FALSE))
|
||||
|
||||
expect_false(need(try(stop("boom"), silent = TRUE), FALSE))
|
||||
|
||||
# These are all truthy
|
||||
|
||||
expect_null(need(0, FALSE))
|
||||
expect_null(need(1:10, FALSE))
|
||||
expect_null(need(LETTERS, FALSE))
|
||||
expect_null(need("NA", FALSE))
|
||||
expect_null(need(TRUE, FALSE))
|
||||
expect_null(need(c(NA, NA, TRUE), FALSE))
|
||||
expect_null(need(c(FALSE, FALSE, TRUE), FALSE))
|
||||
})
|
||||
6
inst/www/shared/jquery-ui/jquery-ui-min.js
vendored
1631
inst/www/shared/jquery-ui/jquery-ui.js
vendored
8346
inst/www/shared/jquery.js
vendored
245
inst/www/shared/jqueryui/1.10.4/AUTHORS.txt
Normal file
@@ -0,0 +1,245 @@
|
||||
Authors ordered by first contribution
|
||||
A list of current team members is available at http://jqueryui.com/about
|
||||
|
||||
Paul Bakaus <paul.bakaus@googlemail.com>
|
||||
Richard Worth <rdworth@gmail.com>
|
||||
Yehuda Katz <wycats@gmail.com>
|
||||
Sean Catchpole <sean@sunsean.com>
|
||||
John Resig <jeresig@gmail.com>
|
||||
Tane Piper <piper.tane@gmail.com>
|
||||
Dmitri Gaskin <dmitrig01@gmail.com>
|
||||
Klaus Hartl <klaus.hartl@googlemail.com>
|
||||
Stefan Petre <stefan.petre@gmail.com>
|
||||
Gilles van den Hoven <gilles@webunity.nl>
|
||||
Micheil Bryan Smith <micheil@brandedcode.com>
|
||||
Jörn Zaefferer <joern.zaefferer@gmail.com>
|
||||
Marc Grabanski <m@marcgrabanski.com>
|
||||
Keith Wood <kbwood.au@gmail.com>
|
||||
Brandon Aaron <brandon.aaron@gmail.com>
|
||||
Scott González <scott.gonzalez@gmail.com>
|
||||
Eduardo Lundgren <eduardolundgren@gmail.com>
|
||||
Aaron Eisenberger <aaronchi@gmail.com>
|
||||
Joan Piedra <theneojp@gmail.com>
|
||||
Bruno Basto <b.basto@gmail.com>
|
||||
Remy Sharp <remy@leftlogic.com>
|
||||
Bohdan Ganicky <bohdan.ganicky@gmail.com>
|
||||
David Bolter <david.bolter@gmail.com>
|
||||
Chi Cheng <cloudream@gmail.com>
|
||||
Ca-Phun Ung <pazu2k@gmail.com>
|
||||
Ariel Flesler <aflesler@gmail.com>
|
||||
Maggie Costello Wachs <fg.maggie@gmail.com>
|
||||
Scott Jehl <scott@scottjehl.com>
|
||||
Todd Parker <fg.todd@gmail.com>
|
||||
Andrew Powell <powella@gmail.com>
|
||||
Brant Burnett <btburnett3@gmail.com>
|
||||
Douglas Neiner <doug@pixelgraphics.us>
|
||||
Paul Irish <paul.irish@gmail.com>
|
||||
Ralph Whitbeck <ralph.whitbeck@gmail.com>
|
||||
Thibault Duplessis <thibault.duplessis@gmail.com>
|
||||
Dominique Vincent <dominique.vincent@toitl.com>
|
||||
Jack Hsu <jack.hsu@gmail.com>
|
||||
Adam Sontag <ajpiano@ajpiano.com>
|
||||
Carl Fürstenberg <carl@excito.com>
|
||||
Kevin Dalman <development@allpro.net>
|
||||
Alberto Fernández Capel <afcapel@gmail.com>
|
||||
Jacek Jędrzejewski (http://jacek.jedrzejewski.name)
|
||||
Ting Kuei <ting@kuei.com>
|
||||
Samuel Cormier-Iijima <sam@chide.it>
|
||||
Jon Palmer <jonspalmer@gmail.com>
|
||||
Ben Hollis <bhollis@amazon.com>
|
||||
Justin MacCarthy <Justin@Rubystars.biz>
|
||||
Eyal Kobrigo <kobrigo@hotmail.com>
|
||||
Tiago Freire <tiago.freire@gmail.com>
|
||||
Diego Tres <diegotres@gmail.com>
|
||||
Holger Rüprich <holger@rueprich.de>
|
||||
Ziling Zhao <zizhao@cisco.com>
|
||||
Mike Alsup <malsup@gmail.com>
|
||||
Robson Braga Araujo <robsonbraga@gmail.com>
|
||||
Pierre-Henri Ausseil <ph.ausseil@gmail.com>
|
||||
Christopher McCulloh <cmcculloh@gmail.com>
|
||||
Andrew Newcomb <ext.github@preceptsoftware.co.uk>
|
||||
Lim Chee Aun <cheeaun@gmail.com>
|
||||
Jorge Barreiro <yortx.barry@gmail.com>
|
||||
Daniel Steigerwald <daniel@steigerwald.cz>
|
||||
John Firebaugh <john_firebaugh@bigfix.com>
|
||||
John Enters <github@darkdark.net>
|
||||
Andrey Kapitcyn <ru.m157y@gmail.com>
|
||||
Dmitry Petrov <dpetroff@gmail.com>
|
||||
Eric Hynds <eric@hynds.net>
|
||||
Chairat Sunthornwiphat <pipo@sixhead.com>
|
||||
Josh Varner <josh.varner@gmail.com>
|
||||
Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
Jay Merrifield <fracmak@gmail.com>
|
||||
J. Ryan Stinnett <jryans@gmail.com>
|
||||
Peter Heiberg <peter@heiberg.se>
|
||||
Alex Dovenmuehle <adovenmuehle@gmail.com>
|
||||
Jamie Gegerson <git@jamiegegerson.com>
|
||||
Raymond Schwartz <skeetergraphics@gmail.com>
|
||||
Phillip Barnes <philbar@gmail.com>
|
||||
Kyle Wilkinson <kai@wikyd.org>
|
||||
Khaled AlHourani <me@khaledalhourani.com>
|
||||
Marian Rudzynski <mr@impaled.org>
|
||||
Jean-Francois Remy <jfremy@virtuoz.com>
|
||||
Doug Blood <dougblood@gmail.com>
|
||||
Filippo Cavallarin <filippo.cavallarin@codseq.it>
|
||||
Heiko Henning <h.henning@educa.ch>
|
||||
Aliaksandr Rahalevich <saksmlz@gmail.com>
|
||||
Mario Visic <mario@mariovisic.com>
|
||||
Xavi Ramirez <xavi.rmz@gmail.com>
|
||||
Max Schnur <max.schnur@gmail.com>
|
||||
Saji Nediyanchath <saji89@gmail.com>
|
||||
Corey Frang <gnarf@gnarf.net>
|
||||
Aaron Peterson <aaronp123@yahoo.com>
|
||||
Ivan Peters <ivan@ivanpeters.com>
|
||||
Mohamed Cherif Bouchelaghem <cherifbouchelaghem@yahoo.fr>
|
||||
Marcos Sousa <falecomigo@marcossousa.com>
|
||||
Michael DellaNoce <mdellanoce@mailtrust.com>
|
||||
George Marshall <echosx@gmail.com>
|
||||
Tobias Brunner <tobias@strongswan.org>
|
||||
Martin Solli <msolli@gmail.com>
|
||||
David Petersen <public@petersendidit.com>
|
||||
Dan Heberden <danheberden@gmail.com>
|
||||
William Kevin Manire <williamkmanire@gmail.com>
|
||||
Gilmore Davidson <gilmoreorless@gmail.com>
|
||||
Michael Wu <michaelmwu@gmail.com>
|
||||
Adam Parod <mystic414@gmail.com>
|
||||
Guillaume Gautreau <guillaume+github@ghusse.com>
|
||||
Marcel Toele <EleotleCram@gmail.com>
|
||||
Dan Streetman <ddstreet@ieee.org>
|
||||
Matt Hoskins <furlined@cat-basket.org>
|
||||
Giovanni Giacobbi <giovanni@giacobbi.net>
|
||||
Kyle Florence <kyle.florence@gmail.com>
|
||||
Pavol Hluchý <lopo@losys.sk>
|
||||
Hans Hillen <hans.hillen@gmail.com>
|
||||
Mark Johnson <virgofx@live.com>
|
||||
Trey Hunner <treyhunner@gmail.com>
|
||||
Shane Whittet <whittet@gmail.com>
|
||||
Edward A Faulkner <ef@alum.mit.edu>
|
||||
Adam Baratz <adam@adambaratz.com>
|
||||
Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
|
||||
Eike Send <eike.send@gmail.com>
|
||||
Kris Borchers <kris.borchers@gmail.com>
|
||||
Eddie Monge <eddie@eddiemonge.com>
|
||||
Israel Tsadok <itsadok@gmail.com>
|
||||
Carson McDonald <carson@ioncannon.net>
|
||||
Jason Davies <jason@jasondavies.com>
|
||||
Garrison Locke <gplocke@gmail.com>
|
||||
David Murdoch <musicisair@yahoo.com>
|
||||
Benjamin Scott Boyle <benjamins.boyle@gmail.com>
|
||||
Jesse Baird <jebaird@gmail.com>
|
||||
Jonathan Vingiano <jvingiano@gmail.com>
|
||||
Dylan Just <dev@ephox.com>
|
||||
Hiroshi Tomita <tomykaira@gmail.com>
|
||||
Glenn Goodrich <glenn.goodrich@gmail.com>
|
||||
Tarafder Ashek-E-Elahi <mail.ashek@gmail.com>
|
||||
Ryan Neufeld <ryan@neufeldmail.com>
|
||||
Marc Neuwirth <marc.neuwirth@gmail.com>
|
||||
Philip Graham <philip.robert.graham@gmail.com>
|
||||
Benjamin Sterling <benjamin.sterling@kenzomedia.com>
|
||||
Wesley Walser <waw325@gmail.com>
|
||||
Kouhei Sutou <kou@clear-code.com>
|
||||
Karl Kirch <karlkrch@gmail.com>
|
||||
Chris Kelly <ckdake@ckdake.com>
|
||||
Jay Oster <jay@loyalize.com>
|
||||
Alexander Polomoshnov <alex.polomoshnov@gmail.com>
|
||||
David Leal <dgleal@gmail.com>
|
||||
Igor Milla <igor.fsp.milla@gmail.com>
|
||||
Dave Methvin <dave.methvin@gmail.com>
|
||||
Florian Gutmann <f.gutmann@chronimo.com>
|
||||
Marwan Al Jubeh <marwan.aljubeh@gmail.com>
|
||||
Milan Broum <midlis@googlemail.com>
|
||||
Sebastian Sauer <info@dynpages.de>
|
||||
Gaëtan Muller <m.gaetan89@gmail.com>
|
||||
Michel Weimerskirch <michel@weimerskirch.net>
|
||||
William Griffiths <william@ycymro.com>
|
||||
Stojce Slavkovski <stojce@gmail.com>
|
||||
David Soms <david.soms@gmail.com>
|
||||
David De Sloovere <david.desloovere@hotmail.com>
|
||||
Michael P. Jung <michael.jung@terreon.de>
|
||||
Shannon Pekary <spekary@gmail.com>
|
||||
Matthew Edward Hutton <meh@corefiling.co.uk>
|
||||
James Khoury <james@jameskhoury.com>
|
||||
Rob Loach <robloach@gmail.com>
|
||||
Alberto Monteiro <betimbrasil@gmail.com>
|
||||
Alex Rhea <alex.rhea@gmail.com>
|
||||
Krzysztof Rosiński <rozwell69@gmail.com>
|
||||
Ryan Olton <oltonr@gmail.com>
|
||||
Genie <386@mail.com>
|
||||
Rick Waldron <waldron.rick@gmail.com>
|
||||
Ian Simpson <spoonlikesham@gmail.com>
|
||||
Lev Kitsis <spam4lev@gmail.com>
|
||||
TJ VanToll <tj.vantoll@gmail.com>
|
||||
Justin Domnitz <jdomnitz@gmail.com>
|
||||
Douglas Cerna <douglascerna@yahoo.com>
|
||||
Bert ter Heide <bertjh@hotmail.com>
|
||||
Jasvir Nagra <jasvir@gmail.com>
|
||||
Petr Hromadko <yuriy@tokyoscale.com>
|
||||
Harri Kilpiö <harri.kilpio@gmail.com>
|
||||
Lado Lomidze <lado.lomidze@gmail.com>
|
||||
Amir E. Aharoni <amir.aharoni@mail.huji.ac.il>
|
||||
Simon Sattes <simon.sattes@gmail.com>
|
||||
Jo Liss <joliss42@gmail.com>
|
||||
Guntupalli Karunakar <karunakarg@yahoo.com>
|
||||
Shahyar Ghobadpour <shahyar@gmail.com>
|
||||
Lukasz Lipinski <uzza17@gmail.com>
|
||||
Timo Tijhof <krinklemail@gmail.com>
|
||||
Jason Moon <jmoon@socialcast.com>
|
||||
Martin Frost <martinf55@hotmail.com>
|
||||
Eneko Illarramendi <eneko@illarra.com>
|
||||
EungJun Yi <semtlenori@gmail.com>
|
||||
Courtland Allen <courtlandallen@gmail.com>
|
||||
Viktar Varvanovich <non4eg@gmail.com>
|
||||
Danny Trunk <dtrunk90@gmail.com>
|
||||
Pavel Stetina <pavel.stetina@nangu.tv>
|
||||
Michael Stay <metaweta@gmail.com>
|
||||
Steven Roussey <sroussey@gmail.com>
|
||||
Michael Hollis <hollis21@gmail.com>
|
||||
Lee Rowlands <lee.rowlands@previousnext.com.au>
|
||||
Timmy Willison <timmywillisn@gmail.com>
|
||||
Karl Swedberg <kswedberg@gmail.com>
|
||||
Baoju Yuan <the_guy_1987@hotmail.com>
|
||||
Maciej Mroziński <mrozik87@gmail.com>
|
||||
Luis Dalmolin <luis.nh@gmail.com>
|
||||
Mark Aaron Shirley <maspwr@gmail.com>
|
||||
Martin Hoch <martin@fidion.de>
|
||||
Jiayi Yang <tr870829@gmail.com>
|
||||
Philipp Benjamin Köppchen <xgxtpbk@gws.ms>
|
||||
Sindre Sorhus <sindresorhus@gmail.com>
|
||||
Bernhard Sirlinger <bernhard.sirlinger@tele2.de>
|
||||
Jared A. Scheel <jared@jaredscheel.com>
|
||||
Rafael Xavier de Souza <rxaviers@gmail.com>
|
||||
John Chen <zhang.z.chen@intel.com>
|
||||
Dale Kocian <dale.kocian@gmail.com>
|
||||
Mike Sherov <mike.sherov@gmail.com>
|
||||
Andrew Couch <andy@couchand.com>
|
||||
Marc-Andre Lafortune <github@marc-andre.ca>
|
||||
Nate Eagle <nate.eagle@teamaol.com>
|
||||
David Souther <davidsouther@gmail.com>
|
||||
Mathias Stenbom <mathias@stenbom.com>
|
||||
Sergey Kartashov <ebishkek@yandex.ru>
|
||||
Avinash R <nashpapa@gmail.com>
|
||||
Ethan Romba <ethanromba@gmail.com>
|
||||
Cory Gackenheimer <cory.gack@gmail.com>
|
||||
Juan Pablo Kaniefsky <jpkaniefsky@gmail.com>
|
||||
Roman Salnikov <bardt.dz@gmail.com>
|
||||
Anika Henke <anika@selfthinker.org>
|
||||
Samuel Bovée <samycookie2000@yahoo.fr>
|
||||
Fabrício Matté <ult_combo@hotmail.com>
|
||||
Viktor Kojouharov <vkojouharov@gmail.com>
|
||||
Pawel Maruszczyk <lord_t@o2.pl>
|
||||
Pavel Selitskas <p.selitskas@gmail.com>
|
||||
Bjørn Johansen <bjorn.johansen@metronet.no>
|
||||
Matthieu Penant <thieum22@hotmail.com>
|
||||
Dominic Barnes <dominic@dbarnes.info>
|
||||
David Sullivan <david.sullivan@gmail.com>
|
||||
Thomas Jaggi <thomas.jaggi@gmail.com>
|
||||
Vahid Sohrabloo <vahid4134@gmail.com>
|
||||
Travis Carden <travis.carden@gmail.com>
|
||||
Bruno M. Custódio <bruno@brunomcustodio.com>
|
||||
Nathanael Silverman <nathanael.silverman@gmail.com>
|
||||
Christian Wenz <christian@wenz.org>
|
||||
Steve Urmston <steve@urm.st>
|
||||
Zaven Muradyan <megalivoithos@gmail.com>
|
||||
Woody Gilk <shadowhand@deviantart.com>
|
||||
Zbigniew Motyka <zbigniew.motyka@gmail.com>
|
||||
Suhail Alkowaileet <xsoh.k7@gmail.com>
|
||||
26
inst/www/shared/jqueryui/1.10.4/MIT-LICENSE.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
Copyright 2013 jQuery Foundation and other contributors,
|
||||
http://jqueryui.com/
|
||||
|
||||
This software consists of voluntary contributions made by many
|
||||
individuals (AUTHORS.txt, http://jqueryui.com/about) For exact
|
||||
contribution history, see the revision history and logs, available
|
||||
at http://jquery-ui.googlecode.com/svn/
|
||||
|
||||
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.
|
||||
BIN
inst/www/shared/jqueryui/1.10.4/images/animated-overlay.gif
Executable file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-bg_flat_0_aaaaaa_40x100.png
Executable file
|
After Width: | Height: | Size: 212 B |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-bg_flat_75_ffffff_40x100.png
Executable file
|
After Width: | Height: | Size: 208 B |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-bg_glass_55_fbf9ee_1x400.png
Executable file
|
After Width: | Height: | Size: 335 B |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-bg_glass_65_ffffff_1x400.png
Executable file
|
After Width: | Height: | Size: 207 B |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-bg_glass_75_dadada_1x400.png
Executable file
|
After Width: | Height: | Size: 262 B |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-bg_glass_75_e6e6e6_1x400.png
Executable file
|
After Width: | Height: | Size: 262 B |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-bg_glass_95_fef1ec_1x400.png
Executable file
|
After Width: | Height: | Size: 332 B |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-bg_highlight-soft_75_cccccc_1x100.png
Executable file
|
After Width: | Height: | Size: 280 B |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-icons_222222_256x240.png
Executable file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-icons_2e83ff_256x240.png
Executable file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-icons_454545_256x240.png
Executable file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-icons_888888_256x240.png
Executable file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
inst/www/shared/jqueryui/1.10.4/images/ui-icons_cd0a0a_256x240.png
Executable file
|
After Width: | Height: | Size: 4.4 KiB |
950
inst/www/shared/jqueryui/1.10.4/jquery-ui.css
vendored
Executable file
@@ -0,0 +1,950 @@
|
||||
/*! jQuery UI - v1.10.4 - 2014-05-05
|
||||
* http://jqueryui.com
|
||||
* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/
|
||||
* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
/* Layout helpers
|
||||
----------------------------------*/
|
||||
.ui-helper-hidden {
|
||||
display: none;
|
||||
}
|
||||
.ui-helper-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
.ui-helper-reset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
font-size: 100%;
|
||||
list-style: none;
|
||||
}
|
||||
.ui-helper-clearfix:before,
|
||||
.ui-helper-clearfix:after {
|
||||
content: "";
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.ui-helper-clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
.ui-helper-clearfix {
|
||||
min-height: 0; /* support: IE7 */
|
||||
}
|
||||
.ui-helper-zfix {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
filter:Alpha(Opacity=0);
|
||||
}
|
||||
|
||||
.ui-front {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-disabled {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
|
||||
/* states and images */
|
||||
.ui-icon {
|
||||
display: block;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable {
|
||||
position: relative;
|
||||
}
|
||||
.ui-resizable-handle {
|
||||
position: absolute;
|
||||
font-size: 0.1px;
|
||||
display: block;
|
||||
}
|
||||
.ui-resizable-disabled .ui-resizable-handle,
|
||||
.ui-resizable-autohide .ui-resizable-handle {
|
||||
display: none;
|
||||
}
|
||||
.ui-resizable-n {
|
||||
cursor: n-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-resizable-s {
|
||||
cursor: s-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-resizable-e {
|
||||
cursor: e-resize;
|
||||
width: 7px;
|
||||
right: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable-w {
|
||||
cursor: w-resize;
|
||||
width: 7px;
|
||||
left: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable-se {
|
||||
cursor: se-resize;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
right: 1px;
|
||||
bottom: 1px;
|
||||
}
|
||||
.ui-resizable-sw {
|
||||
cursor: sw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
bottom: -5px;
|
||||
}
|
||||
.ui-resizable-nw {
|
||||
cursor: nw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
.ui-resizable-ne {
|
||||
cursor: ne-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
.ui-selectable-helper {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
border: 1px dotted black;
|
||||
}
|
||||
.ui-accordion .ui-accordion-header {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
margin-top: 2px;
|
||||
padding: .5em .5em .5em .7em;
|
||||
min-height: 0; /* support: IE7 */
|
||||
}
|
||||
.ui-accordion .ui-accordion-icons {
|
||||
padding-left: 2.2em;
|
||||
}
|
||||
.ui-accordion .ui-accordion-noicons {
|
||||
padding-left: .7em;
|
||||
}
|
||||
.ui-accordion .ui-accordion-icons .ui-accordion-icons {
|
||||
padding-left: 2.2em;
|
||||
}
|
||||
.ui-accordion .ui-accordion-header .ui-accordion-header-icon {
|
||||
position: absolute;
|
||||
left: .5em;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
}
|
||||
.ui-accordion .ui-accordion-content {
|
||||
padding: 1em 2.2em;
|
||||
border-top: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.ui-autocomplete {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
cursor: default;
|
||||
}
|
||||
.ui-button {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
line-height: normal;
|
||||
margin-right: .1em;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
overflow: visible; /* removes extra width in IE */
|
||||
}
|
||||
.ui-button,
|
||||
.ui-button:link,
|
||||
.ui-button:visited,
|
||||
.ui-button:hover,
|
||||
.ui-button:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
/* to make room for the icon, a width needs to be set here */
|
||||
.ui-button-icon-only {
|
||||
width: 2.2em;
|
||||
}
|
||||
/* button elements seem to need a little more width */
|
||||
button.ui-button-icon-only {
|
||||
width: 2.4em;
|
||||
}
|
||||
.ui-button-icons-only {
|
||||
width: 3.4em;
|
||||
}
|
||||
button.ui-button-icons-only {
|
||||
width: 3.7em;
|
||||
}
|
||||
|
||||
/* button text element */
|
||||
.ui-button .ui-button-text {
|
||||
display: block;
|
||||
line-height: normal;
|
||||
}
|
||||
.ui-button-text-only .ui-button-text {
|
||||
padding: .4em 1em;
|
||||
}
|
||||
.ui-button-icon-only .ui-button-text,
|
||||
.ui-button-icons-only .ui-button-text {
|
||||
padding: .4em;
|
||||
text-indent: -9999999px;
|
||||
}
|
||||
.ui-button-text-icon-primary .ui-button-text,
|
||||
.ui-button-text-icons .ui-button-text {
|
||||
padding: .4em 1em .4em 2.1em;
|
||||
}
|
||||
.ui-button-text-icon-secondary .ui-button-text,
|
||||
.ui-button-text-icons .ui-button-text {
|
||||
padding: .4em 2.1em .4em 1em;
|
||||
}
|
||||
.ui-button-text-icons .ui-button-text {
|
||||
padding-left: 2.1em;
|
||||
padding-right: 2.1em;
|
||||
}
|
||||
/* no icon support for input elements, provide padding by default */
|
||||
input.ui-button {
|
||||
padding: .4em 1em;
|
||||
}
|
||||
|
||||
/* button icon element(s) */
|
||||
.ui-button-icon-only .ui-icon,
|
||||
.ui-button-text-icon-primary .ui-icon,
|
||||
.ui-button-text-icon-secondary .ui-icon,
|
||||
.ui-button-text-icons .ui-icon,
|
||||
.ui-button-icons-only .ui-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
}
|
||||
.ui-button-icon-only .ui-icon {
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
}
|
||||
.ui-button-text-icon-primary .ui-button-icon-primary,
|
||||
.ui-button-text-icons .ui-button-icon-primary,
|
||||
.ui-button-icons-only .ui-button-icon-primary {
|
||||
left: .5em;
|
||||
}
|
||||
.ui-button-text-icon-secondary .ui-button-icon-secondary,
|
||||
.ui-button-text-icons .ui-button-icon-secondary,
|
||||
.ui-button-icons-only .ui-button-icon-secondary {
|
||||
right: .5em;
|
||||
}
|
||||
|
||||
/* button sets */
|
||||
.ui-buttonset {
|
||||
margin-right: 7px;
|
||||
}
|
||||
.ui-buttonset .ui-button {
|
||||
margin-left: 0;
|
||||
margin-right: -.3em;
|
||||
}
|
||||
|
||||
/* workarounds */
|
||||
/* reset extra padding in Firefox, see h5bp.com/l */
|
||||
input.ui-button::-moz-focus-inner,
|
||||
button.ui-button::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.ui-dialog {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: .2em;
|
||||
outline: 0;
|
||||
}
|
||||
.ui-dialog .ui-dialog-titlebar {
|
||||
padding: .4em 1em;
|
||||
position: relative;
|
||||
}
|
||||
.ui-dialog .ui-dialog-title {
|
||||
float: left;
|
||||
margin: .1em 0;
|
||||
white-space: nowrap;
|
||||
width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.ui-dialog .ui-dialog-titlebar-close {
|
||||
position: absolute;
|
||||
right: .3em;
|
||||
top: 50%;
|
||||
width: 20px;
|
||||
margin: -10px 0 0 0;
|
||||
padding: 1px;
|
||||
height: 20px;
|
||||
}
|
||||
.ui-dialog .ui-dialog-content {
|
||||
position: relative;
|
||||
border: 0;
|
||||
padding: .5em 1em;
|
||||
background: none;
|
||||
overflow: auto;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane {
|
||||
text-align: left;
|
||||
border-width: 1px 0 0 0;
|
||||
background-image: none;
|
||||
margin-top: .5em;
|
||||
padding: .3em 1em .5em .4em;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
|
||||
float: right;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane button {
|
||||
margin: .5em .4em .5em 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui-dialog .ui-resizable-se {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
right: -5px;
|
||||
bottom: -5px;
|
||||
background-position: 16px 16px;
|
||||
}
|
||||
.ui-draggable .ui-dialog-titlebar {
|
||||
cursor: move;
|
||||
}
|
||||
.ui-menu {
|
||||
list-style: none;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
display: block;
|
||||
outline: none;
|
||||
}
|
||||
.ui-menu .ui-menu {
|
||||
margin-top: -3px;
|
||||
position: absolute;
|
||||
}
|
||||
.ui-menu .ui-menu-item {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
/* support: IE10, see #8844 */
|
||||
list-style-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);
|
||||
}
|
||||
.ui-menu .ui-menu-divider {
|
||||
margin: 5px -2px 5px -2px;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
border-width: 1px 0 0 0;
|
||||
}
|
||||
.ui-menu .ui-menu-item a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 2px .4em;
|
||||
line-height: 1.5;
|
||||
min-height: 0; /* support: IE7 */
|
||||
font-weight: normal;
|
||||
}
|
||||
.ui-menu .ui-menu-item a.ui-state-focus,
|
||||
.ui-menu .ui-menu-item a.ui-state-active {
|
||||
font-weight: normal;
|
||||
margin: -1px;
|
||||
}
|
||||
|
||||
.ui-menu .ui-state-disabled {
|
||||
font-weight: normal;
|
||||
margin: .4em 0 .2em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.ui-menu .ui-state-disabled a {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* icon support */
|
||||
.ui-menu-icons {
|
||||
position: relative;
|
||||
}
|
||||
.ui-menu-icons .ui-menu-item a {
|
||||
position: relative;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
/* left-aligned */
|
||||
.ui-menu .ui-icon {
|
||||
position: absolute;
|
||||
top: .2em;
|
||||
left: .2em;
|
||||
}
|
||||
|
||||
/* right-aligned */
|
||||
.ui-menu .ui-menu-icon {
|
||||
position: static;
|
||||
float: right;
|
||||
}
|
||||
.ui-progressbar {
|
||||
height: 2em;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ui-progressbar .ui-progressbar-value {
|
||||
margin: -1px;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-progressbar .ui-progressbar-overlay {
|
||||
background: url("images/animated-overlay.gif");
|
||||
height: 100%;
|
||||
filter: alpha(opacity=25);
|
||||
opacity: 0.25;
|
||||
}
|
||||
.ui-progressbar-indeterminate .ui-progressbar-value {
|
||||
background-image: none;
|
||||
}
|
||||
.ui-spinner {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ui-spinner-input {
|
||||
border: none;
|
||||
background: none;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
margin: .2em 0;
|
||||
vertical-align: middle;
|
||||
margin-left: .4em;
|
||||
margin-right: 22px;
|
||||
}
|
||||
.ui-spinner-button {
|
||||
width: 16px;
|
||||
height: 50%;
|
||||
font-size: .5em;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
right: 0;
|
||||
}
|
||||
/* more specificity required here to override default borders */
|
||||
.ui-spinner a.ui-spinner-button {
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
border-right: none;
|
||||
}
|
||||
/* vertically center icon */
|
||||
.ui-spinner .ui-icon {
|
||||
position: absolute;
|
||||
margin-top: -8px;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
}
|
||||
.ui-spinner-up {
|
||||
top: 0;
|
||||
}
|
||||
.ui-spinner-down {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/* TR overrides */
|
||||
.ui-spinner .ui-icon-triangle-1-s {
|
||||
/* need to fix icons sprite */
|
||||
background-position: -65px -16px;
|
||||
}
|
||||
.ui-tabs {
|
||||
position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
|
||||
padding: .2em;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav {
|
||||
margin: 0;
|
||||
padding: .2em .2em 0;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
position: relative;
|
||||
top: 0;
|
||||
margin: 1px .2em 0 0;
|
||||
border-bottom-width: 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav .ui-tabs-anchor {
|
||||
float: left;
|
||||
padding: .5em 1em;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-active {
|
||||
margin-bottom: -1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,
|
||||
.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {
|
||||
cursor: text;
|
||||
}
|
||||
.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui-tabs .ui-tabs-panel {
|
||||
display: block;
|
||||
border-width: 0;
|
||||
padding: 1em 1.4em;
|
||||
background: none;
|
||||
}
|
||||
.ui-tooltip {
|
||||
padding: 8px;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
max-width: 300px;
|
||||
-webkit-box-shadow: 0 0 5px #aaa;
|
||||
box-shadow: 0 0 5px #aaa;
|
||||
}
|
||||
body .ui-tooltip {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
/* Component containers
|
||||
----------------------------------*/
|
||||
.ui-widget {
|
||||
font-family: Verdana,Arial,sans-serif;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.ui-widget .ui-widget {
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget input,
|
||||
.ui-widget select,
|
||||
.ui-widget textarea,
|
||||
.ui-widget button {
|
||||
font-family: Verdana,Arial,sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget-content {
|
||||
border: 1px solid #aaaaaa;
|
||||
background: #ffffff url("images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x;
|
||||
color: #222222;
|
||||
}
|
||||
.ui-widget-content a {
|
||||
color: #222222;
|
||||
}
|
||||
.ui-widget-header {
|
||||
border: 1px solid #aaaaaa;
|
||||
background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;
|
||||
color: #222222;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-widget-header a {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
/* Interaction states
|
||||
----------------------------------*/
|
||||
.ui-state-default,
|
||||
.ui-widget-content .ui-state-default,
|
||||
.ui-widget-header .ui-state-default {
|
||||
border: 1px solid #d3d3d3;
|
||||
background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #555555;
|
||||
}
|
||||
.ui-state-default a,
|
||||
.ui-state-default a:link,
|
||||
.ui-state-default a:visited {
|
||||
color: #555555;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-state-hover,
|
||||
.ui-widget-content .ui-state-hover,
|
||||
.ui-widget-header .ui-state-hover,
|
||||
.ui-state-focus,
|
||||
.ui-widget-content .ui-state-focus,
|
||||
.ui-widget-header .ui-state-focus {
|
||||
border: 1px solid #999999;
|
||||
background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #212121;
|
||||
}
|
||||
.ui-state-hover a,
|
||||
.ui-state-hover a:hover,
|
||||
.ui-state-hover a:link,
|
||||
.ui-state-hover a:visited,
|
||||
.ui-state-focus a,
|
||||
.ui-state-focus a:hover,
|
||||
.ui-state-focus a:link,
|
||||
.ui-state-focus a:visited {
|
||||
color: #212121;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-state-active,
|
||||
.ui-widget-content .ui-state-active,
|
||||
.ui-widget-header .ui-state-active {
|
||||
border: 1px solid #aaaaaa;
|
||||
background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #212121;
|
||||
}
|
||||
.ui-state-active a,
|
||||
.ui-state-active a:link,
|
||||
.ui-state-active a:visited {
|
||||
color: #212121;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-highlight,
|
||||
.ui-widget-content .ui-state-highlight,
|
||||
.ui-widget-header .ui-state-highlight {
|
||||
border: 1px solid #fcefa1;
|
||||
background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;
|
||||
color: #363636;
|
||||
}
|
||||
.ui-state-highlight a,
|
||||
.ui-widget-content .ui-state-highlight a,
|
||||
.ui-widget-header .ui-state-highlight a {
|
||||
color: #363636;
|
||||
}
|
||||
.ui-state-error,
|
||||
.ui-widget-content .ui-state-error,
|
||||
.ui-widget-header .ui-state-error {
|
||||
border: 1px solid #cd0a0a;
|
||||
background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;
|
||||
color: #cd0a0a;
|
||||
}
|
||||
.ui-state-error a,
|
||||
.ui-widget-content .ui-state-error a,
|
||||
.ui-widget-header .ui-state-error a {
|
||||
color: #cd0a0a;
|
||||
}
|
||||
.ui-state-error-text,
|
||||
.ui-widget-content .ui-state-error-text,
|
||||
.ui-widget-header .ui-state-error-text {
|
||||
color: #cd0a0a;
|
||||
}
|
||||
.ui-priority-primary,
|
||||
.ui-widget-content .ui-priority-primary,
|
||||
.ui-widget-header .ui-priority-primary {
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-priority-secondary,
|
||||
.ui-widget-content .ui-priority-secondary,
|
||||
.ui-widget-header .ui-priority-secondary {
|
||||
opacity: .7;
|
||||
filter:Alpha(Opacity=70);
|
||||
font-weight: normal;
|
||||
}
|
||||
.ui-state-disabled,
|
||||
.ui-widget-content .ui-state-disabled,
|
||||
.ui-widget-header .ui-state-disabled {
|
||||
opacity: .35;
|
||||
filter:Alpha(Opacity=35);
|
||||
background-image: none;
|
||||
}
|
||||
.ui-state-disabled .ui-icon {
|
||||
filter:Alpha(Opacity=35); /* For IE8 - See #6059 */
|
||||
}
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
|
||||
/* states and images */
|
||||
.ui-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.ui-icon,
|
||||
.ui-widget-content .ui-icon {
|
||||
background-image: url("images/ui-icons_222222_256x240.png");
|
||||
}
|
||||
.ui-widget-header .ui-icon {
|
||||
background-image: url("images/ui-icons_222222_256x240.png");
|
||||
}
|
||||
.ui-state-default .ui-icon {
|
||||
background-image: url("images/ui-icons_888888_256x240.png");
|
||||
}
|
||||
.ui-state-hover .ui-icon,
|
||||
.ui-state-focus .ui-icon {
|
||||
background-image: url("images/ui-icons_454545_256x240.png");
|
||||
}
|
||||
.ui-state-active .ui-icon {
|
||||
background-image: url("images/ui-icons_454545_256x240.png");
|
||||
}
|
||||
.ui-state-highlight .ui-icon {
|
||||
background-image: url("images/ui-icons_2e83ff_256x240.png");
|
||||
}
|
||||
.ui-state-error .ui-icon,
|
||||
.ui-state-error-text .ui-icon {
|
||||
background-image: url("images/ui-icons_cd0a0a_256x240.png");
|
||||
}
|
||||
|
||||
/* positioning */
|
||||
.ui-icon-blank { background-position: 16px 16px; }
|
||||
.ui-icon-carat-1-n { background-position: 0 0; }
|
||||
.ui-icon-carat-1-ne { background-position: -16px 0; }
|
||||
.ui-icon-carat-1-e { background-position: -32px 0; }
|
||||
.ui-icon-carat-1-se { background-position: -48px 0; }
|
||||
.ui-icon-carat-1-s { background-position: -64px 0; }
|
||||
.ui-icon-carat-1-sw { background-position: -80px 0; }
|
||||
.ui-icon-carat-1-w { background-position: -96px 0; }
|
||||
.ui-icon-carat-1-nw { background-position: -112px 0; }
|
||||
.ui-icon-carat-2-n-s { background-position: -128px 0; }
|
||||
.ui-icon-carat-2-e-w { background-position: -144px 0; }
|
||||
.ui-icon-triangle-1-n { background-position: 0 -16px; }
|
||||
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
|
||||
.ui-icon-triangle-1-e { background-position: -32px -16px; }
|
||||
.ui-icon-triangle-1-se { background-position: -48px -16px; }
|
||||
.ui-icon-triangle-1-s { background-position: -64px -16px; }
|
||||
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
|
||||
.ui-icon-triangle-1-w { background-position: -96px -16px; }
|
||||
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
|
||||
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
|
||||
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
|
||||
.ui-icon-arrow-1-n { background-position: 0 -32px; }
|
||||
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
|
||||
.ui-icon-arrow-1-e { background-position: -32px -32px; }
|
||||
.ui-icon-arrow-1-se { background-position: -48px -32px; }
|
||||
.ui-icon-arrow-1-s { background-position: -64px -32px; }
|
||||
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
|
||||
.ui-icon-arrow-1-w { background-position: -96px -32px; }
|
||||
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
|
||||
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
|
||||
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
|
||||
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
|
||||
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
|
||||
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
|
||||
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
|
||||
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
|
||||
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
|
||||
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
|
||||
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
|
||||
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
|
||||
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
|
||||
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
|
||||
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
|
||||
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
|
||||
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
|
||||
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
|
||||
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
|
||||
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
|
||||
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
|
||||
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
|
||||
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
|
||||
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
|
||||
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
|
||||
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
|
||||
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
|
||||
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
|
||||
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
|
||||
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
|
||||
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
|
||||
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
|
||||
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
|
||||
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
|
||||
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
|
||||
.ui-icon-arrow-4 { background-position: 0 -80px; }
|
||||
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
|
||||
.ui-icon-extlink { background-position: -32px -80px; }
|
||||
.ui-icon-newwin { background-position: -48px -80px; }
|
||||
.ui-icon-refresh { background-position: -64px -80px; }
|
||||
.ui-icon-shuffle { background-position: -80px -80px; }
|
||||
.ui-icon-transfer-e-w { background-position: -96px -80px; }
|
||||
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
|
||||
.ui-icon-folder-collapsed { background-position: 0 -96px; }
|
||||
.ui-icon-folder-open { background-position: -16px -96px; }
|
||||
.ui-icon-document { background-position: -32px -96px; }
|
||||
.ui-icon-document-b { background-position: -48px -96px; }
|
||||
.ui-icon-note { background-position: -64px -96px; }
|
||||
.ui-icon-mail-closed { background-position: -80px -96px; }
|
||||
.ui-icon-mail-open { background-position: -96px -96px; }
|
||||
.ui-icon-suitcase { background-position: -112px -96px; }
|
||||
.ui-icon-comment { background-position: -128px -96px; }
|
||||
.ui-icon-person { background-position: -144px -96px; }
|
||||
.ui-icon-print { background-position: -160px -96px; }
|
||||
.ui-icon-trash { background-position: -176px -96px; }
|
||||
.ui-icon-locked { background-position: -192px -96px; }
|
||||
.ui-icon-unlocked { background-position: -208px -96px; }
|
||||
.ui-icon-bookmark { background-position: -224px -96px; }
|
||||
.ui-icon-tag { background-position: -240px -96px; }
|
||||
.ui-icon-home { background-position: 0 -112px; }
|
||||
.ui-icon-flag { background-position: -16px -112px; }
|
||||
.ui-icon-calendar { background-position: -32px -112px; }
|
||||
.ui-icon-cart { background-position: -48px -112px; }
|
||||
.ui-icon-pencil { background-position: -64px -112px; }
|
||||
.ui-icon-clock { background-position: -80px -112px; }
|
||||
.ui-icon-disk { background-position: -96px -112px; }
|
||||
.ui-icon-calculator { background-position: -112px -112px; }
|
||||
.ui-icon-zoomin { background-position: -128px -112px; }
|
||||
.ui-icon-zoomout { background-position: -144px -112px; }
|
||||
.ui-icon-search { background-position: -160px -112px; }
|
||||
.ui-icon-wrench { background-position: -176px -112px; }
|
||||
.ui-icon-gear { background-position: -192px -112px; }
|
||||
.ui-icon-heart { background-position: -208px -112px; }
|
||||
.ui-icon-star { background-position: -224px -112px; }
|
||||
.ui-icon-link { background-position: -240px -112px; }
|
||||
.ui-icon-cancel { background-position: 0 -128px; }
|
||||
.ui-icon-plus { background-position: -16px -128px; }
|
||||
.ui-icon-plusthick { background-position: -32px -128px; }
|
||||
.ui-icon-minus { background-position: -48px -128px; }
|
||||
.ui-icon-minusthick { background-position: -64px -128px; }
|
||||
.ui-icon-close { background-position: -80px -128px; }
|
||||
.ui-icon-closethick { background-position: -96px -128px; }
|
||||
.ui-icon-key { background-position: -112px -128px; }
|
||||
.ui-icon-lightbulb { background-position: -128px -128px; }
|
||||
.ui-icon-scissors { background-position: -144px -128px; }
|
||||
.ui-icon-clipboard { background-position: -160px -128px; }
|
||||
.ui-icon-copy { background-position: -176px -128px; }
|
||||
.ui-icon-contact { background-position: -192px -128px; }
|
||||
.ui-icon-image { background-position: -208px -128px; }
|
||||
.ui-icon-video { background-position: -224px -128px; }
|
||||
.ui-icon-script { background-position: -240px -128px; }
|
||||
.ui-icon-alert { background-position: 0 -144px; }
|
||||
.ui-icon-info { background-position: -16px -144px; }
|
||||
.ui-icon-notice { background-position: -32px -144px; }
|
||||
.ui-icon-help { background-position: -48px -144px; }
|
||||
.ui-icon-check { background-position: -64px -144px; }
|
||||
.ui-icon-bullet { background-position: -80px -144px; }
|
||||
.ui-icon-radio-on { background-position: -96px -144px; }
|
||||
.ui-icon-radio-off { background-position: -112px -144px; }
|
||||
.ui-icon-pin-w { background-position: -128px -144px; }
|
||||
.ui-icon-pin-s { background-position: -144px -144px; }
|
||||
.ui-icon-play { background-position: 0 -160px; }
|
||||
.ui-icon-pause { background-position: -16px -160px; }
|
||||
.ui-icon-seek-next { background-position: -32px -160px; }
|
||||
.ui-icon-seek-prev { background-position: -48px -160px; }
|
||||
.ui-icon-seek-end { background-position: -64px -160px; }
|
||||
.ui-icon-seek-start { background-position: -80px -160px; }
|
||||
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
|
||||
.ui-icon-seek-first { background-position: -80px -160px; }
|
||||
.ui-icon-stop { background-position: -96px -160px; }
|
||||
.ui-icon-eject { background-position: -112px -160px; }
|
||||
.ui-icon-volume-off { background-position: -128px -160px; }
|
||||
.ui-icon-volume-on { background-position: -144px -160px; }
|
||||
.ui-icon-power { background-position: 0 -176px; }
|
||||
.ui-icon-signal-diag { background-position: -16px -176px; }
|
||||
.ui-icon-signal { background-position: -32px -176px; }
|
||||
.ui-icon-battery-0 { background-position: -48px -176px; }
|
||||
.ui-icon-battery-1 { background-position: -64px -176px; }
|
||||
.ui-icon-battery-2 { background-position: -80px -176px; }
|
||||
.ui-icon-battery-3 { background-position: -96px -176px; }
|
||||
.ui-icon-circle-plus { background-position: 0 -192px; }
|
||||
.ui-icon-circle-minus { background-position: -16px -192px; }
|
||||
.ui-icon-circle-close { background-position: -32px -192px; }
|
||||
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
|
||||
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
|
||||
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
|
||||
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
|
||||
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
|
||||
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
|
||||
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
|
||||
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
|
||||
.ui-icon-circle-zoomin { background-position: -176px -192px; }
|
||||
.ui-icon-circle-zoomout { background-position: -192px -192px; }
|
||||
.ui-icon-circle-check { background-position: -208px -192px; }
|
||||
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
|
||||
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
|
||||
.ui-icon-circlesmall-close { background-position: -32px -208px; }
|
||||
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
|
||||
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
|
||||
.ui-icon-squaresmall-close { background-position: -80px -208px; }
|
||||
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
|
||||
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
|
||||
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
|
||||
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
|
||||
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
|
||||
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
|
||||
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Corner radius */
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-left,
|
||||
.ui-corner-tl {
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-right,
|
||||
.ui-corner-tr {
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-left,
|
||||
.ui-corner-bl {
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-right,
|
||||
.ui-corner-br {
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30);
|
||||
}
|
||||
.ui-widget-shadow {
|
||||
margin: -8px 0 0 -8px;
|
||||
padding: 8px;
|
||||
background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30);
|
||||
border-radius: 8px;
|
||||
}
|
||||
12322
inst/www/shared/jqueryui/1.10.4/jquery-ui.js
vendored
Executable file
7
inst/www/shared/jqueryui/1.10.4/jquery-ui.min.css
vendored
Executable file
6
inst/www/shared/jqueryui/1.10.4/jquery-ui.min.js
vendored
Executable file
22
inst/www/shared/json2-min.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
("object"!=typeof JSON||JSON.stringify("\uf977").length>3)&&(JSON={}),function(){"use strict"
|
||||
function f(t){return 10>t?"0"+t:t}function quote(t){return escapable.lastIndex=0,escapable.test(t)?'"'+t.replace(escapable,function(t){var e=meta[t]
|
||||
return"string"==typeof e?e:"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+t+'"'}function str(t,e){var n,r,o,f,u,p=gap,a=e[t]
|
||||
switch(a&&"object"==typeof a&&"function"==typeof a.toJSON&&(a=a.toJSON(t)),"function"==typeof rep&&(a=rep.call(e,t,a)),typeof a){case"string":return quote(a)
|
||||
case"number":return isFinite(a)?a+"":"null"
|
||||
case"boolean":case"null":return a+""
|
||||
case"object":if(!a)return"null"
|
||||
if(gap+=indent,u=[],"[object Array]"===Object.prototype.toString.apply(a)){for(f=a.length,n=0;f>n;n+=1)u[n]=str(n,a)||"null"
|
||||
return o=0===u.length?"[]":gap?"[\n"+gap+u.join(",\n"+gap)+"\n"+p+"]":"["+u.join(",")+"]",gap=p,o}if(rep&&"object"==typeof rep)for(f=rep.length,n=0;f>n;n+=1)"string"==typeof rep[n]&&(r=rep[n],o=str(r,a),o&&u.push(quote(r)+(gap?": ":":")+o))
|
||||
else for(r in a)Object.prototype.hasOwnProperty.call(a,r)&&(o=str(r,a),o&&u.push(quote(r)+(gap?": ":":")+o))
|
||||
return o=0===u.length?"{}":gap?"{\n"+gap+u.join(",\n"+gap)+"\n"+p+"}":"{"+u.join(",")+"}",gap=p,o}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()})
|
||||
var cx,escapable,gap,indent,meta,rep
|
||||
"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(t,e,n){var r
|
||||
if(gap="",indent="","number"==typeof n)for(r=0;n>r;r+=1)indent+=" "
|
||||
else"string"==typeof n&&(indent=n)
|
||||
if(rep=e,e&&"function"!=typeof e&&("object"!=typeof e||"number"!=typeof e.length))throw Error("JSON.stringify")
|
||||
return str("",{"":t})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(t,e){var n,r,o=t[e]
|
||||
if(o&&"object"==typeof o)for(n in o)Object.prototype.hasOwnProperty.call(o,n)&&(r=walk(o,n),void 0!==r?o[n]=r:delete o[n])
|
||||
return reviver.call(t,e,o)}var j
|
||||
if(text+="",cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(t){return"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j
|
||||
throw new SyntaxError("JSON.parse")})}()
|
||||
|
||||
486
inst/www/shared/selectize/css/selectize.bootstrap2.css
Normal file
@@ -0,0 +1,486 @@
|
||||
/**
|
||||
* selectize.bootstrap2.css (v0.9.1) - Bootstrap 2 Theme
|
||||
* Copyright (c) 2013 Brian Reavis & contributors
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Brian Reavis <brian@thirdroute.com>
|
||||
*/
|
||||
.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder {
|
||||
visibility: visible !important;
|
||||
background: #f2f2f2 !important;
|
||||
background: rgba(0, 0, 0, 0.06) !important;
|
||||
border: 0 none !important;
|
||||
-webkit-box-shadow: inset 0 0 12px 4px #ffffff;
|
||||
box-shadow: inset 0 0 12px 4px #ffffff;
|
||||
}
|
||||
.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after {
|
||||
content: '!';
|
||||
visibility: hidden;
|
||||
}
|
||||
.selectize-control.plugin-drag_drop .ui-sortable-helper {
|
||||
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.selectize-dropdown-header {
|
||||
position: relative;
|
||||
padding: 3px 10px;
|
||||
border-bottom: 1px solid #d0d0d0;
|
||||
background: #f8f8f8;
|
||||
-webkit-border-radius: 4px 4px 0 0;
|
||||
-moz-border-radius: 4px 4px 0 0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
.selectize-dropdown-header-close {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
color: #333333;
|
||||
opacity: 0.4;
|
||||
margin-top: -12px;
|
||||
line-height: 20px;
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.selectize-dropdown-header-close:hover {
|
||||
color: #000000;
|
||||
}
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup {
|
||||
border-right: 1px solid #f2f2f2;
|
||||
border-top: 0 none;
|
||||
float: left;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child {
|
||||
border-right: 0 none;
|
||||
}
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup:before {
|
||||
display: none;
|
||||
}
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup-header {
|
||||
border-top: 0 none;
|
||||
}
|
||||
.selectize-control.plugin-remove_button [data-value] {
|
||||
position: relative;
|
||||
padding-right: 24px !important;
|
||||
}
|
||||
.selectize-control.plugin-remove_button [data-value] .remove {
|
||||
z-index: 1;
|
||||
/* fixes ie bug (see #392) */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 17px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
padding: 1px 0 0 0;
|
||||
border-left: 1px solid #cccccc;
|
||||
-webkit-border-radius: 0 2px 2px 0;
|
||||
-moz-border-radius: 0 2px 2px 0;
|
||||
border-radius: 0 2px 2px 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.selectize-control.plugin-remove_button [data-value] .remove:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.selectize-control.plugin-remove_button [data-value].active .remove {
|
||||
border-left-color: #0077b3;
|
||||
}
|
||||
.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover {
|
||||
background: none;
|
||||
}
|
||||
.selectize-control.plugin-remove_button .disabled [data-value] .remove {
|
||||
border-left-color: #e0e0e0;
|
||||
}
|
||||
.selectize-control {
|
||||
position: relative;
|
||||
}
|
||||
.selectize-dropdown,
|
||||
.selectize-input,
|
||||
.selectize-input input {
|
||||
color: #333333;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
-webkit-font-smoothing: inherit;
|
||||
}
|
||||
.selectize-input,
|
||||
.selectize-control.single .selectize-input.input-active {
|
||||
background: #ffffff;
|
||||
cursor: text;
|
||||
display: inline-block;
|
||||
}
|
||||
.selectize-input {
|
||||
border: 1px solid #d0d0d0;
|
||||
padding: 7px 10px;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.selectize-control.multi .selectize-input.has-items {
|
||||
padding: 5px 10px 2px;
|
||||
}
|
||||
.selectize-input.full {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.selectize-input.disabled,
|
||||
.selectize-input.disabled * {
|
||||
cursor: default !important;
|
||||
}
|
||||
.selectize-input.focus {
|
||||
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.selectize-input.dropdown-active {
|
||||
-webkit-border-radius: 4px 4px 0 0;
|
||||
-moz-border-radius: 4px 4px 0 0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
.selectize-input > * {
|
||||
vertical-align: baseline;
|
||||
display: -moz-inline-stack;
|
||||
display: inline-block;
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
}
|
||||
.selectize-control.multi .selectize-input > div {
|
||||
cursor: pointer;
|
||||
margin: 0 3px 3px 0;
|
||||
padding: 1px 3px;
|
||||
background: #e6e6e6;
|
||||
color: #333333;
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
.selectize-control.multi .selectize-input > div.active {
|
||||
background: #0088cc;
|
||||
color: #ffffff;
|
||||
border: 1px solid #0077b3;
|
||||
}
|
||||
.selectize-control.multi .selectize-input.disabled > div,
|
||||
.selectize-control.multi .selectize-input.disabled > div.active {
|
||||
color: #474747;
|
||||
background: #fafafa;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
.selectize-input > input {
|
||||
padding: 0 !important;
|
||||
min-height: 0 !important;
|
||||
max-height: none !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
text-indent: 0 !important;
|
||||
border: 0 none !important;
|
||||
background: none !important;
|
||||
line-height: inherit !important;
|
||||
-webkit-user-select: auto !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.selectize-input > input::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
.selectize-input > input:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
.selectize-input::after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
clear: left;
|
||||
}
|
||||
.selectize-input.dropdown-active::before {
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
background: #e5e5e5;
|
||||
height: 1px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.selectize-dropdown {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
border: 1px solid #d0d0d0;
|
||||
background: #ffffff;
|
||||
margin: -1px 0 0 0;
|
||||
border-top: 0 none;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
-webkit-border-radius: 0 0 4px 4px;
|
||||
-moz-border-radius: 0 0 4px 4px;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
.selectize-dropdown [data-selectable] {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
.selectize-dropdown [data-selectable] .highlight {
|
||||
background: rgba(255, 237, 40, 0.4);
|
||||
-webkit-border-radius: 1px;
|
||||
-moz-border-radius: 1px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
.selectize-dropdown [data-selectable],
|
||||
.selectize-dropdown .optgroup-header {
|
||||
padding: 3px 10px;
|
||||
}
|
||||
.selectize-dropdown .optgroup:first-child .optgroup-header {
|
||||
border-top: 0 none;
|
||||
}
|
||||
.selectize-dropdown .optgroup-header {
|
||||
color: #999999;
|
||||
background: #ffffff;
|
||||
cursor: default;
|
||||
}
|
||||
.selectize-dropdown .active {
|
||||
background-color: #0088cc;
|
||||
color: #ffffff;
|
||||
}
|
||||
.selectize-dropdown .active.create {
|
||||
color: #ffffff;
|
||||
}
|
||||
.selectize-dropdown .create {
|
||||
color: rgba(51, 51, 51, 0.5);
|
||||
}
|
||||
.selectize-dropdown-content {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 200px;
|
||||
}
|
||||
.selectize-control.single .selectize-input,
|
||||
.selectize-control.single .selectize-input input {
|
||||
cursor: pointer;
|
||||
}
|
||||
.selectize-control.single .selectize-input.input-active,
|
||||
.selectize-control.single .selectize-input.input-active input {
|
||||
cursor: text;
|
||||
}
|
||||
.selectize-control.single .selectize-input:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 15px;
|
||||
margin-top: -3px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 5px 5px 0 5px;
|
||||
border-color: #000000 transparent transparent transparent;
|
||||
}
|
||||
.selectize-control.single .selectize-input.dropdown-active:after {
|
||||
margin-top: -4px;
|
||||
border-width: 0 5px 5px 5px;
|
||||
border-color: transparent transparent #000000 transparent;
|
||||
}
|
||||
.selectize-control.rtl.single .selectize-input:after {
|
||||
left: 15px;
|
||||
right: auto;
|
||||
}
|
||||
.selectize-control.rtl .selectize-input > input {
|
||||
margin: 0 4px 0 -2px !important;
|
||||
}
|
||||
.selectize-control .selectize-input.disabled {
|
||||
opacity: 0.5;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.selectize-dropdown {
|
||||
margin: 2px 0 0 0;
|
||||
z-index: 1000;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.selectize-dropdown .optgroup-header {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.selectize-dropdown .optgroup:first-child:before {
|
||||
display: none;
|
||||
}
|
||||
.selectize-dropdown .optgroup:before {
|
||||
content: ' ';
|
||||
display: block;
|
||||
*width: 100%;
|
||||
height: 1px;
|
||||
margin: 9px 1px;
|
||||
*margin: -5px 0 5px;
|
||||
overflow: hidden;
|
||||
background-color: #e5e5e5;
|
||||
border-bottom: 1px solid #ffffff;
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
}
|
||||
.selectize-dropdown [data-selectable].active {
|
||||
background-color: #0081c2;
|
||||
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
|
||||
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
|
||||
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
|
||||
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
|
||||
}
|
||||
.selectize-dropdown-content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
.selectize-dropdown-header {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
.selectize-input {
|
||||
-webkit-transition: border linear .2s, box-shadow linear .2s;
|
||||
-moz-transition: border linear .2s, box-shadow linear .2s;
|
||||
-o-transition: border linear .2s, box-shadow linear .2s;
|
||||
transition: border linear .2s, box-shadow linear .2s;
|
||||
}
|
||||
.selectize-input.dropdown-active {
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.selectize-input.dropdown-active::before {
|
||||
display: none;
|
||||
}
|
||||
.selectize-input.input-active,
|
||||
.selectize-input.input-active:hover,
|
||||
.selectize-control.multi .selectize-input.focus {
|
||||
background: #ffffff !important;
|
||||
border-color: rgba(82, 168, 236, 0.8) !important;
|
||||
outline: 0 !important;
|
||||
outline: thin dotted \9 !important;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6) !important;
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6) !important;
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6) !important;
|
||||
}
|
||||
.selectize-control.single .selectize-input {
|
||||
color: #333333;
|
||||
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
|
||||
background-color: #f5f5f5;
|
||||
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
|
||||
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
|
||||
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
|
||||
background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
|
||||
border-color: #e6e6e6 #e6e6e6 #bfbfbf;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
*background-color: #e6e6e6;
|
||||
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
|
||||
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
|
||||
}
|
||||
.selectize-control.single .selectize-input:hover,
|
||||
.selectize-control.single .selectize-input:focus,
|
||||
.selectize-control.single .selectize-input:active,
|
||||
.selectize-control.single .selectize-input.active,
|
||||
.selectize-control.single .selectize-input.disabled,
|
||||
.selectize-control.single .selectize-input[disabled] {
|
||||
color: #333333;
|
||||
background-color: #e6e6e6;
|
||||
*background-color: #d9d9d9;
|
||||
}
|
||||
.selectize-control.single .selectize-input:active,
|
||||
.selectize-control.single .selectize-input.active {
|
||||
background-color: #cccccc \9;
|
||||
}
|
||||
.selectize-control.single .selectize-input:hover {
|
||||
color: #333333;
|
||||
text-decoration: none;
|
||||
background-position: 0 -15px;
|
||||
-webkit-transition: background-position 0.1s linear;
|
||||
-moz-transition: background-position 0.1s linear;
|
||||
-o-transition: background-position 0.1s linear;
|
||||
transition: background-position 0.1s linear;
|
||||
}
|
||||
.selectize-control.single .selectize-input.disabled {
|
||||
background: #e6e6e6 !important;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.selectize-control.multi .selectize-input {
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.selectize-control.multi .selectize-input.has-items {
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
}
|
||||
.selectize-control.multi .selectize-input > div {
|
||||
color: #333333;
|
||||
text-shadow: none;
|
||||
background-color: #f5f5f5;
|
||||
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
|
||||
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
|
||||
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
|
||||
background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
|
||||
border-color: #e6e6e6 #e6e6e6 #bfbfbf;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
*background-color: #e6e6e6;
|
||||
border: 1px solid #cccccc;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
|
||||
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
|
||||
}
|
||||
.selectize-control.multi .selectize-input > div.active {
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
|
||||
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.05);
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.05);
|
||||
color: #ffffff;
|
||||
text-shadow: none;
|
||||
background-color: #0081c2;
|
||||
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
|
||||
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
|
||||
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
|
||||
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
|
||||
border-color: #0077b3 #0077b3 #004466;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
*background-color: #0088cc;
|
||||
border: 1px solid #0088cc;
|
||||
}
|
||||
17
inst/www/shared/selectize/js/es5-shim.min.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
(function(o){"function"==typeof define?define(o):"function"==typeof YUI?YUI.add("es5",o):o()})(function(){function o(){}function v(a){a=+a;a!==a?a=0:0!==a&&(a!==1/0&&a!==-(1/0))&&(a=(0<a||-1)*Math.floor(Math.abs(a)));return a}function s(a){var b=typeof a;return null===a||"undefined"===b||"boolean"===b||"number"===b||"string"===b}Function.prototype.bind||(Function.prototype.bind=function(a){var b=this;if("function"!=typeof b)throw new TypeError("Function.prototype.bind called on incompatible "+b);
|
||||
var d=q.call(arguments,1),c=function(){if(this instanceof c){var e=b.apply(this,d.concat(q.call(arguments)));return Object(e)===e?e:this}return b.apply(a,d.concat(q.call(arguments)))};b.prototype&&(o.prototype=b.prototype,c.prototype=new o,o.prototype=null);return c});var k=Function.prototype.call,p=Object.prototype,q=Array.prototype.slice,h=k.bind(p.toString),t=k.bind(p.hasOwnProperty);t(p,"__defineGetter__")&&(k.bind(p.__defineGetter__),k.bind(p.__defineSetter__),k.bind(p.__lookupGetter__),k.bind(p.__lookupSetter__));
|
||||
if(2!=[1,2].splice(0).length){var y=Array.prototype.splice;Array.prototype.splice=function(a,b){return arguments.length?y.apply(this,[a===void 0?0:a,b===void 0?this.length-a:b].concat(q.call(arguments,2))):[]}}if(1!=[].unshift(0)){var z=Array.prototype.unshift;Array.prototype.unshift=function(){z.apply(this,arguments);return this.length}}Array.isArray||(Array.isArray=function(a){return h(a)=="[object Array]"});var k=Object("a"),l="a"!=k[0]||!(0 in k);Array.prototype.forEach||(Array.prototype.forEach=
|
||||
function(a,b){var d=n(this),c=l&&h(this)=="[object String]"?this.split(""):d,e=-1,f=c.length>>>0;if(h(a)!="[object Function]")throw new TypeError;for(;++e<f;)e in c&&a.call(b,c[e],e,d)});Array.prototype.map||(Array.prototype.map=function(a,b){var d=n(this),c=l&&h(this)=="[object String]"?this.split(""):d,e=c.length>>>0,f=Array(e);if(h(a)!="[object Function]")throw new TypeError(a+" is not a function");for(var g=0;g<e;g++)g in c&&(f[g]=a.call(b,c[g],g,d));return f});Array.prototype.filter||(Array.prototype.filter=
|
||||
function(a,b){var d=n(this),c=l&&h(this)=="[object String]"?this.split(""):d,e=c.length>>>0,f=[],g;if(h(a)!="[object Function]")throw new TypeError(a+" is not a function");for(var i=0;i<e;i++)if(i in c){g=c[i];a.call(b,g,i,d)&&f.push(g)}return f});Array.prototype.every||(Array.prototype.every=function(a,b){var d=n(this),c=l&&h(this)=="[object String]"?this.split(""):d,e=c.length>>>0;if(h(a)!="[object Function]")throw new TypeError(a+" is not a function");for(var f=0;f<e;f++)if(f in c&&!a.call(b,c[f],
|
||||
f,d))return false;return true});Array.prototype.some||(Array.prototype.some=function(a,b){var d=n(this),c=l&&h(this)=="[object String]"?this.split(""):d,e=c.length>>>0;if(h(a)!="[object Function]")throw new TypeError(a+" is not a function");for(var f=0;f<e;f++)if(f in c&&a.call(b,c[f],f,d))return true;return false});Array.prototype.reduce||(Array.prototype.reduce=function(a){var b=n(this),d=l&&h(this)=="[object String]"?this.split(""):b,c=d.length>>>0;if(h(a)!="[object Function]")throw new TypeError(a+
|
||||
" is not a function");if(!c&&arguments.length==1)throw new TypeError("reduce of empty array with no initial value");var e=0,f;if(arguments.length>=2)f=arguments[1];else{do{if(e in d){f=d[e++];break}if(++e>=c)throw new TypeError("reduce of empty array with no initial value");}while(1)}for(;e<c;e++)e in d&&(f=a.call(void 0,f,d[e],e,b));return f});Array.prototype.reduceRight||(Array.prototype.reduceRight=function(a){var b=n(this),d=l&&h(this)=="[object String]"?this.split(""):b,c=d.length>>>0;if(h(a)!=
|
||||
"[object Function]")throw new TypeError(a+" is not a function");if(!c&&arguments.length==1)throw new TypeError("reduceRight of empty array with no initial value");var e,c=c-1;if(arguments.length>=2)e=arguments[1];else{do{if(c in d){e=d[c--];break}if(--c<0)throw new TypeError("reduceRight of empty array with no initial value");}while(1)}do c in this&&(e=a.call(void 0,e,d[c],c,b));while(c--);return e});if(!Array.prototype.indexOf||-1!=[0,1].indexOf(1,2))Array.prototype.indexOf=function(a){var b=l&&
|
||||
h(this)=="[object String]"?this.split(""):n(this),d=b.length>>>0;if(!d)return-1;var c=0;arguments.length>1&&(c=v(arguments[1]));for(c=c>=0?c:Math.max(0,d+c);c<d;c++)if(c in b&&b[c]===a)return c;return-1};if(!Array.prototype.lastIndexOf||-1!=[0,1].lastIndexOf(0,-3))Array.prototype.lastIndexOf=function(a){var b=l&&h(this)=="[object String]"?this.split(""):n(this),d=b.length>>>0;if(!d)return-1;var c=d-1;arguments.length>1&&(c=Math.min(c,v(arguments[1])));for(c=c>=0?c:d-Math.abs(c);c>=0;c--)if(c in b&&
|
||||
a===b[c])return c;return-1};if(!Object.keys){var w=!0,x="toString toLocaleString valueOf hasOwnProperty isPrototypeOf propertyIsEnumerable constructor".split(" "),A=x.length,r;for(r in{toString:null})w=!1;Object.keys=function(a){if(typeof a!="object"&&typeof a!="function"||a===null)throw new TypeError("Object.keys called on a non-object");var b=[],d;for(d in a)t(a,d)&&b.push(d);if(w)for(d=0;d<A;d++){var c=x[d];t(a,c)&&b.push(c)}return b}}if(!Date.prototype.toISOString||-1===(new Date(-621987552E5)).toISOString().indexOf("-000001"))Date.prototype.toISOString=
|
||||
function(){var a,b,d,c;if(!isFinite(this))throw new RangeError("Date.prototype.toISOString called on non-finite value.");c=this.getUTCFullYear();a=this.getUTCMonth();c=c+Math.floor(a/12);a=[(a%12+12)%12+1,this.getUTCDate(),this.getUTCHours(),this.getUTCMinutes(),this.getUTCSeconds()];c=(c<0?"-":c>9999?"+":"")+("00000"+Math.abs(c)).slice(0<=c&&c<=9999?-4:-6);for(b=a.length;b--;){d=a[b];d<10&&(a[b]="0"+d)}return c+"-"+a.slice(0,2).join("-")+"T"+a.slice(2).join(":")+"."+("000"+this.getUTCMilliseconds()).slice(-3)+
|
||||
"Z"};r=!1;try{r=Date.prototype.toJSON&&null===(new Date(NaN)).toJSON()&&-1!==(new Date(-621987552E5)).toJSON().indexOf("-000001")&&Date.prototype.toJSON.call({toISOString:function(){return true}})}catch(H){}r||(Date.prototype.toJSON=function(){var a=Object(this),b;a:if(s(a))b=a;else{b=a.valueOf;if(typeof b==="function"){b=b.call(a);if(s(b))break a}b=a.toString;if(typeof b==="function"){b=b.call(a);if(s(b))break a}throw new TypeError;}if(typeof b==="number"&&!isFinite(b))return null;b=a.toISOString;
|
||||
if(typeof b!="function")throw new TypeError("toISOString property is not callable");return b.call(a)});var g=Date,m=function(a,b,d,c,e,f,h){var i=arguments.length;if(this instanceof g){i=i==1&&String(a)===a?new g(m.parse(a)):i>=7?new g(a,b,d,c,e,f,h):i>=6?new g(a,b,d,c,e,f):i>=5?new g(a,b,d,c,e):i>=4?new g(a,b,d,c):i>=3?new g(a,b,d):i>=2?new g(a,b):i>=1?new g(a):new g;i.constructor=m;return i}return g.apply(this,arguments)},u=function(a,b){var d=b>1?1:0;return B[b]+Math.floor((a-1969+d)/4)-Math.floor((a-
|
||||
1901+d)/100)+Math.floor((a-1601+d)/400)+365*(a-1970)},C=RegExp("^(\\d{4}|[+-]\\d{6})(?:-(\\d{2})(?:-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d{3}))?)?(Z|(?:([-+])(\\d{2}):(\\d{2})))?)?)?)?$"),B=[0,31,59,90,120,151,181,212,243,273,304,334,365],j;for(j in g)m[j]=g[j];m.now=g.now;m.UTC=g.UTC;m.prototype=g.prototype;m.prototype.constructor=m;m.parse=function(a){var b=C.exec(a);if(b){var d=Number(b[1]),c=Number(b[2]||1)-1,e=Number(b[3]||1)-1,f=Number(b[4]||0),h=Number(b[5]||0),i=Number(b[6]||
|
||||
0),j=Number(b[7]||0),m=!b[4]||b[8]?0:Number(new g(1970,0)),k=b[9]==="-"?1:-1,l=Number(b[10]||0),b=Number(b[11]||0);if(f<(h>0||i>0||j>0?24:25)&&h<60&&i<60&&j<1E3&&c>-1&&c<12&&l<24&&b<60&&e>-1&&e<u(d,c+1)-u(d,c)){d=((u(d,c)+e)*24+f+l*k)*60;d=((d+h+b*k)*60+i)*1E3+j+m;if(-864E13<=d&&d<=864E13)return d}return NaN}return g.parse.apply(this,arguments)};Date=m;Date.now||(Date.now=function(){return(new Date).getTime()});if("0".split(void 0,0).length){var D=String.prototype.split;String.prototype.split=function(a,
|
||||
b){return a===void 0&&b===0?[]:D.apply(this,arguments)}}if("".substr&&"b"!=="0b".substr(-1)){var E=String.prototype.substr;String.prototype.substr=function(a,b){return E.call(this,a<0?(a=this.length+a)<0?0:a:a,b)}}j="\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\ufeff";if(!String.prototype.trim||j.trim()){j="["+j+"]";var F=RegExp("^"+j+j+"*"),G=RegExp(j+j+"*$");String.prototype.trim=function(){if(this===void 0||this===
|
||||
null)throw new TypeError("can't convert "+this+" to object");return String(this).replace(F,"").replace(G,"")}}var n=function(a){if(a==null)throw new TypeError("can't convert "+a+" to object");return Object(a)}});
|
||||