Compare commits

..

556 Commits

Author SHA1 Message Date
Winston Chang
6c711b76b0 Bump version to 0.10.1 2014-07-25 14:20:43 -05:00
Winston Chang
9c914f10c4 Merge pull request #553 from wch/hidden-selectize
Add custom version of selectize.js to work around Firefox bug
2014-07-25 13:37:08 -05:00
Winston Chang
eda56d118a Merge pull request #549 from yihui/bugfix/utf8-bom
warn against the byte order mark if exists; fixes #545
2014-07-24 16:09:56 -05:00
Winston Chang
02c7351c6d Add custom version of selectize.js to work around Firefox bug
This is a workaround for issue #550. This version is based on selectize version 0.9.1.
2014-07-24 15:29:23 -05:00
Yihui Xie
ab618235f1 one more check before we use UTF-8: see if there are embedded nul's 2014-07-24 14:48:23 -05:00
Yihui Xie
ffead9ed70 add explanations of skipping *nix when checking encoding, and point to the shiny dev center article 2014-07-24 11:57:46 -05:00
Winston Chang
36aefadced Merge pull request #550 from rstudio/bugfix/offscreen-shinyapps
Deal gracefully with elements that have no computed style available
2014-07-23 11:55:24 -05:00
Jonathan McPherson
75ccfe38ce update comment with more specific browser notes 2014-07-23 09:43:42 -07:00
Jonathan McPherson
e3cb3fe2e4 deal gracefully with elements that have no computed style available 2014-07-22 14:41:34 -07:00
Yihui Xie
983e7e9b75 warn against the byte order mark if exists; fixes #545 2014-07-22 16:22:50 -05:00
Yihui Xie
3db47c076c a news item for the optgroup feature 2014-07-18 14:40:58 -05:00
Yihui Xie
eeff285b33 add links to examples in shiny dev center 2014-07-18 14:40:31 -05:00
Yihui Xie
029595f8ea an edge case selectInput(choices = NULL)
firstChoice() may fail with an error "subscript out of bounds"
2014-07-17 15:59:33 -05:00
Yihui Xie
ea2ec27724 bump version 2014-07-17 14:52:26 -05:00
Yihui Xie
f6bf4a416f Merge pull request #537 from yihui/bugfix/native-encoding
Use native encoding internally
2014-07-17 14:49:53 -05:00
Yihui Xie
af978a68e3 tweak the warning message
and simply stop() in case the user has set option(encoding = 'UTF-8')
2014-07-17 14:39:21 -05:00
Yihui Xie
89dc1323e1 add a note section about the clickId/hoverId arguments in plotOutput(), since they seem to have been confusing grid graphics users
and re-wrap the roxygen comments
2014-07-16 17:27:00 -05:00
Yihui Xie
a4b5f63deb a single quote ' was omitted here, making the Rd for plotOutput() incomplete since this line was ignored 2014-07-16 17:27:00 -05:00
Winston Chang
feaa6ccff4 Merge pull request #542 from rstudio/feature/select-optgroup
Feature/select optgroup (closes #520)
2014-07-16 17:22:29 -05:00
Winston Chang
7159293337 Use consistent indentation for function definitions 2014-07-16 17:04:22 -05:00
Winston Chang
4a5b31e3a7 Simplify needOptgroup function 2014-07-16 16:57:17 -05:00
Winston Chang
6f1dc89fb3 Enable union merge for NEWS 2014-07-16 16:48:56 -05:00
Yihui Xie
29dd405fe5 the options and ... arguments are no longer needed 2014-07-16 15:50:31 -05:00
Yihui Xie
0f0b0cd3d8 explicitly define updateSelectInput() instead of using the trick to change the options argument of updateInputOptions() 2014-07-16 15:50:08 -05:00
Winston Chang
262528e36a Add tests for deeper nesting with choicesWithNames 2014-07-16 12:07:13 -05:00
Winston Chang
597e86dd57 Make listify work on nested lists of depth > 1 2014-07-16 12:02:55 -05:00
Winston Chang
b604dba948 Add tests for default selected items 2014-07-16 00:52:29 -05:00
Winston Chang
1837a64bd2 Add tests for selectOptions 2014-07-16 00:51:57 -05:00
Winston Chang
9b413de4d8 selectOptions: handle mixed options and optgroups 2014-07-16 00:51:57 -05:00
Winston Chang
3d77cbd677 Slight clarification of comment 2014-07-16 00:51:57 -05:00
Winston Chang
62176c3218 Add tests for choicesWithNames 2014-07-16 00:51:57 -05:00
Winston Chang
d7a01c32cc Always convert choices to a (named) list
Converting it to a list, and ensuring that it's a named list reduces
many checks elsewhere.
2014-07-16 00:51:57 -05:00
Yihui Xie
cc493fd545 fall back to native encoding on Windows if UTF-8 does not work 2014-07-15 16:05:23 -05:00
Yihui Xie
6b8679454d factor out .Platform$OS.type == 'windows' as isWindows() 2014-07-15 16:04:02 -05:00
Yihui Xie
1b68d61e54 R CMD check will warn against the possibly missing variables inline and type 2014-07-11 17:36:37 -05:00
Yihui Xie
418de862e6 rename newOptions to config 2014-07-11 14:17:08 -05:00
Yihui Xie
413653858e inherit doc for updateFooInput() from fooInput() 2014-07-11 14:03:47 -05:00
Yihui Xie
f0886a7556 when we need <optgroup> for the child elements of choices, we just generate it no matter what the child element is
e.g. we use <optgroup> for A even if A only has one element: list(A = 'a', B = c('b1', 'b2'))
2014-07-11 14:03:47 -05:00
Yihui Xie
0e2666948f when choice is not a list, we need to return its first element 2014-07-11 14:03:47 -05:00
Yihui Xie
d2fc851816 make updateSelectInput(), updateCheckboxGroupInput(), and updateRadioButtons() work
now session$sendInputMessage() does not send options as the choices data, but as a pre-generated raw HTML string; in shiny.js, we just receive this string, and .append() it to the input after the input is emptied
2014-07-11 14:03:47 -05:00
Yihui Xie
e1fb29c8c5 factor out the code to generate options from checkboxGrouptInput() and radioButtons(), and use the new generator generateOptions() for them 2014-07-11 14:03:47 -05:00
Yihui Xie
fe3158fdd6 validateSelected() does not validate selected for the optgroup case; this function is only for shiny <= 0.9 2014-07-11 14:03:47 -05:00
Yihui Xie
721b26f80b choicesWithNames() should also consider the optgroup case now: all optgroup nodes must be named 2014-07-11 14:03:47 -05:00
Yihui Xie
d3ecfb22ee closes #326: generate <optgroup> when choices is a named list of choices
note the nested level of optgroup can be greater than one here, however, the HTML4 spec only allows one level, i.e. optgroup must have select as its direct parent http://stackoverflow.com/q/1037732/559676
2014-07-11 14:03:47 -05:00
Yihui Xie
27a98020c9 recursively select the first choice, to make sure selected is a scalar, even when choices is a list of lists (optgroup) 2014-07-11 14:03:47 -05:00
Yihui Xie
ab56b72f39 json2.js modified and uglified from https://github.com/yihui/JSON-js via http://lisperator.net/uglifyjs/
I'm using the date of json2 as its version number, since it does not really have a version number

This should close rstudio/shiny-server#79
2014-07-10 17:56:23 -05:00
Yihui Xie
8063f66958 let's read ui.R, server.R, README.md, and DESCRIPTION also with UTF-8
the reason for this is that htmltools::htmlEscape() uses gsub(..., x, fixed =
TRUE), which does not work on Windows if x is encoded in UTF-8; fixed = TRUE
only works with the native encoding
2014-07-10 15:59:42 -05:00
Yihui Xie
bf270b9adb convert the message to native encoding from UTF-8 before decoding it
on Windows, switch(input$foo, foo1 = val1) does not work even if input$foo ==
foo1 but Encoding(input$foo) is UTF-8 while foo1 is unknown (native encoding);
to prevent such problems, let's always use native encoding inside the shiny
process, and only do the UTF-8 conversion at I/O time

special thanks to @desar for the thorough tests
2014-07-10 15:59:42 -05:00
Joe Cheng
972db08740 Merge pull request #539 from yihui/doc/conditionalPanel
add a Note section to the documentation of conditionalPanel(), since it ...
2014-07-10 00:41:36 -07:00
Yihui Xie
6326c7cbaa add a Note section to the documentation of conditionalPanel(), since it always confuses R users
e.g. https://groups.google.com/d/msg/shiny-discuss/AFItYcRXzyw/ywRy3EEtjw4J
2014-07-10 00:11:11 -05:00
Yihui Xie
4152ace514 Merge pull request #538 from rstudio/bugfix/showcase-nullref
fix #500: check for null source ref highlight points
2014-07-09 11:55:02 -05:00
Jonathan McPherson
038221408c fix #500: check for null source ref highlight points 2014-07-09 09:26:39 -07:00
Joe Cheng
9f76def7ce Merge pull request #533 from rstudio/bugfix/slow-options
Calling getOption() with default is slow
2014-07-06 16:38:25 -07:00
Joe Cheng
1b83770c5c Merge pull request #528 from yihui/bugfix/showcase-encoding
use UTF-8 for showcase mode, and assume DESCRIPTION is also UTF-8
2014-07-06 12:13:05 -07:00
Joe Cheng
3458d924ca Calling getOption() with default is slow
This has a measurable effect in apps with lots of reactives.

Reported by Aran Lunzer
2014-07-06 12:10:51 -07:00
Yihui Xie
0749b9500c use UTF-8 for showcase mode, and assume DESCRIPTION is also UTF-8
this time we should fix #136 now; I did not find other files that have to be
read with UTF-8
2014-06-26 15:41:12 -05:00
Jonathan
b1dfc18a8c Merge pull request #527 from rstudio/render-rmd-warning
Emit R Markdown warnings for render* functions as well as shinyApps
2014-06-25 11:18:41 -07:00
Jonathan McPherson
7b25c282c0 append rather than replace knit_meta with Shiny warning 2014-06-25 10:20:07 -07:00
Yihui Xie
a128ceaf2d bump version 2014-06-25 10:41:34 -05:00
Jonathan McPherson
f266cab580 emit R Markdown warnings for render* functions as well as shinyApps 2014-06-24 16:43:58 -07:00
Yihui Xie
23bf9aaf17 Merge pull request #526 from rstudio/bugfix/plot-captures-output
Revert "tweak after #492: capture.output() does withVisible() and print(...
2014-06-24 13:40:45 -05:00
Joe Cheng
1983f60ec6 Revert "tweak after #492: capture.output() does withVisible() and print() if necessary"
This reverts commit 451f950d0d.

Too-aggressive capture.output was causing browser() not to work in
renderPlot().
2014-06-24 06:09:36 -07:00
Yihui Xie
27f8909406 bump version 2014-06-19 13:50:39 -05:00
Yihui Xie
9988206911 add news for #512 2014-06-19 13:50:06 -05:00
Joe Cheng
31fe1fdfa6 Merge pull request #523 from rstudio/feature/navbarPage-windowTitle
Add windowTitle parameter to navbarPage
2014-06-19 10:45:01 -07:00
Joe Cheng
77b125ce2d Add windowTitle parameter to navbarPage
Fixes #493
2014-06-19 09:36:50 -07:00
Joe Cheng
6e68e07aa2 Merge pull request #512 from yihui/feature/inline-output
Inline output in R Markdown documents
2014-06-19 09:20:13 -07:00
Yihui Xie
86bb010a93 roxygenize 2014-06-19 00:24:10 -05:00
Yihui Xie
4a623b596b ignore width/height in plotOutput() when inline=TRUE, and document required width/height values in renderPlot() 2014-06-19 00:24:10 -05:00
Yihui Xie
bcf098ea7d merge doc of width and height in renderPlot() and plotOutput() 2014-06-19 00:24:10 -05:00
Yihui Xie
4bfb226fb5 roxygenize 2014-06-19 00:24:10 -05:00
Yihui Xie
691615108b preserve the formal arguments of plotOutput() in outputFunc()
otherwise outputFunc() loses the `inline` argument, and knit_print() won't be able to produce inline plots
2014-06-19 00:24:10 -05:00
Yihui Xie
858ab00e36 closes #501: knit_print() for inline output uses the inline argument passed from knitr 2014-06-19 00:24:10 -05:00
Yihui Xie
7023f5b145 add an inline argument to textOutput(), imageOutput(), plotOutput(), and htmlOutput() 2014-06-19 00:24:10 -05:00
Yihui Xie
eb4fabeac6 fix two more staticdocs errors 2014-06-19 00:17:55 -05:00
Joe Cheng
a5e09f9ce4 Merge tag 'v/0/10/0-staticdocs' 2014-06-18 11:12:40 -07:00
Joe Cheng
c2fe4e8b6d Fix staticdoc errors 2014-06-18 11:11:21 -07:00
Winston Chang
5d22648d34 Merge pull request #513 from rstudio/feature/json-precision
Control precision of JSON numeric representation
2014-06-17 11:51:47 -05:00
Yihui Xie
066fd15184 quiet apt-get and install.packages() 2014-06-16 10:32:04 -05:00
Yihui Xie
fe90c230d5 r-base should just work, since the packages that need to be compiled are installed via apt-get 2014-06-16 10:07:54 -05:00
Yihui Xie
0b5ae92136 shiny 0.10.0 is on CRAN now, so the dependencies knitr and htmltools should be automatically detected 2014-06-16 10:05:33 -05:00
Yihui Xie
1c5565aaee a news item for #427 2014-06-16 09:59:32 -05:00
Yihui Xie
69c177a3ec a news item for #516 2014-06-16 09:50:59 -05:00
Joe Cheng
0645b3f65b Merge pull request #507 from yihui/doc/runUrl
Merge doc for runUrl(), runGist(), and runGitHub()
2014-06-16 01:19:52 -07:00
Joe Cheng
9e7471fcc0 Merge pull request #516 from yihui/bugfix/windows-encoding
Fixes #136: I18N support for Windows
2014-06-16 01:17:31 -07:00
Joe Cheng
c520f53799 Bump to development version number 2014-06-16 01:13:33 -07:00
Yihui Xie
0bf1386802 UTF8 encoding for runApp(list(ui, server)) as well 2014-06-13 18:53:36 -05:00
Yihui Xie
b2ab3797aa the cachedSource() function is not used anywhere, so perhaps we can remove it 2014-06-13 18:08:16 -05:00
Yihui Xie
ede0ca8bd1 make sure JSON messages are always encoded and decoded with UTF8
- per @jcheng5's suggestion, RJSONIO::fromJSON(encoding = 'UTF-8') actually works (no longer need my markUTF8 function)
- removed the global option 'shiny.transcode.json'
2014-06-13 18:07:01 -05:00
Yihui Xie
81e35f0cc3 make sure the HTML content is encoded in UTF8 2014-06-13 18:02:20 -05:00
Yihui Xie
237522a1f7 assuming all the input R scripts are UTF8 encoded
ui.R, server.R, and global.R
2014-06-13 18:00:46 -05:00
Joe Cheng
2f94e1d2c9 Fix timing issue with random seed test on windows 2014-06-13 14:40:19 -07:00
Joe Cheng
2689dd32bb Bump version to 0.10.0 2014-06-13 09:41:45 -07:00
Winston Chang
ad5e703b8f Merge pull request #514 from yihui/doc/news-flowLayout
typo: flowPanel --> flowLayout
2014-06-13 11:12:07 -05:00
Yihui Xie
d3bc2e9279 typo: flowPanel --> flowLayout 2014-06-13 11:05:36 -05:00
Joe Cheng
0cd1644cf1 Control precision of JSON numeric representation
Uses 16 digits by default, set shiny.json.digits option to customize
2014-06-10 15:02:45 -07:00
Winston Chang
f02b405c12 Merge pull request #508 from yihui/bugfix/large-margin
set the margin to 0 before plot.new()
2014-06-10 11:57:07 -05:00
Yihui Xie
baa7036799 explain why par(mar = rep(0, 4)) is necessary before plot.new(), per @wch's suggestion 2014-06-10 11:50:49 -05:00
Yihui Xie
431aecaf00 set the margin to 0 before plot.new()
otherwise users will be unable to draw small plots because of the common error "figure margin too large", e.g. renderPlot(..., width=200, height=100)
2014-06-10 00:46:33 -05:00
Yihui Xie
f31bb56ea6 subdir=NULL is the default of runUrl() 2014-06-09 22:24:51 -05:00
Yihui Xie
cf3b805c46 the message is not very useful, given that download() will emit a message 2014-06-09 22:24:51 -05:00
Yihui Xie
517283ca58 closes #427: runGitHub() can accept "username/repo" in its first argument 2014-06-09 22:24:51 -05:00
Yihui Xie
f416b7ba47 the name variable is not used anywhere 2014-06-09 22:21:29 -05:00
Yihui Xie
973190b7a1 let download() fail in this case (ref=NULL); since ref='master' by default, if the user provides another value, he/she is expected to have read the documentation 2014-06-09 22:21:29 -05:00
Yihui Xie
f536a9d3d3 move runGist() and runGithub() after runUrl(), and merge their descriptions into the description of runUrl() 2014-06-09 22:21:29 -05:00
Yihui Xie
1348ec3bcf closes #213: merge the documentation of runUrl(), runGithub(), and runGist()
removed the port and launch.browser arguments, and used ... instead
2014-06-09 21:45:52 -05:00
Joe Cheng
9a250a4861 Merge pull request #506 from yihui/bugfix/stats
stats may not have been loaded in the event of .onLoad()
2014-06-09 09:09:11 -07:00
Yihui Xie
6450927192 stats may not have been loaded in the event of .onLoad() 2014-06-09 10:56:36 -05:00
Joe Cheng
7c9dbdc802 Merge pull request #504 from rstudio/news-update
Update NEWS
2014-06-07 12:29:04 -07:00
Joe Cheng
8d460afe2d Merge pull request #505 from yihui/doc/splitLayout
there should not be #' before @examples
2014-06-06 23:38:00 -07:00
Yihui Xie
6c44c2cf24 there should not be #' before @examples 2014-06-07 01:00:13 -05:00
Joe Cheng
cea550ebba Update NEWS 2014-06-06 22:41:04 -07:00
Joe Cheng
911a352ee6 Bump version 2014-06-06 22:20:17 -07:00
Joe Cheng
3fadfbe06e Merge pull request #503 from rstudio/bugfix/shinyapps-in-tags-2
Allow shinyApp objects to appear inside tags
2014-06-06 22:16:35 -07:00
Joe Cheng
5bf362927f Allow shinyApp objects to appear inside tags 2014-06-06 15:23:13 -07:00
Yihui Xie
4da5ca5ba9 bump version 2014-06-06 17:07:17 -05:00
Yihui Xie
d747005b30 copy the documentation of knit_print methods from htmltools, too 2014-06-06 17:07:10 -05:00
Yihui Xie
03a395107d export knit_print methods imported from htmltools 2014-06-06 16:59:12 -05:00
Yihui Xie
58ef4ccabf declare the method through @method per @hadley's suggestion at klutometis/roxygen#256 2014-06-04 16:30:12 -05:00
Yihui Xie
71ed082bb5 bump version 2014-06-04 15:41:33 -05:00
Yihui Xie
0819ac8124 no need to assign a copy of these functions in shiny, because we have imported htmltools, and we only need to export the imported functions 2014-06-04 15:38:41 -05:00
Yihui Xie
0cdd223172 cosmetic changes after #481, and roxygenize 2014-06-04 15:30:15 -05:00
Yihui Xie
571393f146 htmltools was in both Depends and Imports, against which R CMD check will warn
I guess it was in Depends because otherwise as.tags could not be treated as an S3 generic; sounds like a bug of roxygen2 or devtools
2014-06-04 15:30:15 -05:00
Yihui Xie
c85868c652 use @export instead of @S3method 2014-06-04 15:10:45 -05:00
Joe Cheng
a7a6f3b020 Merge pull request #499 from yihui/bugfix/456
Bugfix/456
2014-06-03 10:01:44 -07:00
Yihui Xie
3a0a11d55a introduce an equivalent function to achieve set.seed(NULL) in R 2.15.x 2014-06-02 16:14:22 -05:00
Yihui Xie
7eb8ddf372 fixes #456: use .Random.seed only if it exists in the global environment
e.g. the expr may not trigger the creation of .Random.seed, such as set.seed(NULL) under R 2.15.x, which will fail
2014-06-02 16:13:37 -05:00
Yihui Xie
87af63644a bump version 2014-06-02 15:38:40 -05:00
Yihui Xie
0a9dd18070 news for #433 2014-06-02 15:36:36 -05:00
Yihui Xie
f82b061ba7 news for #481 2014-06-02 15:18:54 -05:00
Yihui Xie
c17509e2a0 news for #495 2014-06-02 15:18:54 -05:00
Yihui Xie
cb383d4f62 serverInfo() returns list(shinyServer=FALSE) by default, per suggestion of @trestletech 2014-06-02 15:18:54 -05:00
Yihui Xie
451f950d0d tweak after #492: capture.output() does withVisible() and print() if necessary 2014-06-02 15:18:54 -05:00
Yihui Xie
bd0eae0961 Merge pull request #481 from saurfang/master
Add inline option for RadioButton and checkboxGroupInput
2014-06-02 15:03:40 -05:00
Joe Cheng
53a401f847 Merge pull request #495 from yihui/feature/serverInfo
add functions serverInfo() (exported) and setServerInfo() (not exported)
2014-06-02 09:16:51 -07:00
JJ Allaire
b288f5ca19 bump version 2014-06-02 08:11:02 -04:00
JJ Allaire
7a2fc19c4f bump version 2014-06-01 10:00:40 -04:00
JJ Allaire
046d712d6a Revert "Allow Shiny apps to appear inside other tags"
This reverts commit 9ab2f5338e.
2014-06-01 09:59:50 -04:00
Joe Cheng
e829aaecf1 Install htmltools in travis script 2014-05-31 10:58:54 -07:00
Joe Cheng
9ab2f5338e Allow Shiny apps to appear inside other tags 2014-05-31 09:06:54 -07:00
Joe Cheng
d7bda924be Depend on htmltools, not just Import
This seems to be necessary to get the knit_print.shiny.tags to work
with Rmarkdown without an explicit library(htmltools)
2014-05-31 08:26:09 -07:00
Joe Cheng
07eb2e51b7 Bump version to 0.9.1.9009 2014-05-31 08:08:08 -07:00
Joe Cheng
dfafa7ae40 Merge branch 'htmltools-refactor' 2014-05-31 08:06:56 -07:00
Joe Cheng
dde266768c Restore HTML generating functions
These functions were temporarily ripped out of Shiny and moved
to the htmltools package. We've discovered that it's safe to
keep including them in shiny; as long as the functions in shiny
and the functions in htmltools are identical, the user won't
receive a conflict warning.
2014-05-31 08:06:03 -07:00
Joe Cheng
01c81675f7 Make S3 method consistent with base 2014-05-31 08:06:03 -07:00
Joe Cheng
71972eb362 Update htmltools version 2014-05-31 08:06:03 -07:00
Joe Cheng
eb9f5f9025 Dependency fixes
1) Give bootstrap deps a path so they can be used in static docs
2) Resolve dependencies before rendering page (whoops)
2014-05-31 08:06:03 -07:00
Joe Cheng
eb4d4d7437 Adapt to htmltools 0.2.1 API 2014-05-31 08:06:03 -07:00
Joe Cheng
1cb5e09109 Remove obsolete entry from staticdocs index 2014-05-31 08:06:03 -07:00
Joe Cheng
cc82fff5d3 Add S3 method for turning render function into tags 2014-05-31 08:06:03 -07:00
Joe Cheng
3212e59dcc Fix broken client-side HTML dependency rendering 2014-05-31 08:06:03 -07:00
Joe Cheng
44a795bf18 Extract HTML functionality to htmltools library 2014-05-31 08:06:03 -07:00
Joe Cheng
376e6f35a2 Merge pull request #496 from eiriksm/travis-fix
Use svg version of build status. Only show master build status.
2014-05-31 08:03:16 -07:00
Eirik S. Morland
3b324e9532 Use svg version of build status. Only show master build status. 2014-05-31 12:04:00 +02:00
Joe Cheng
a0df8f3490 Merge pull request #491 from rstudio/bugfix/validation-silent
Suppress validation errors from printing at console
2014-05-28 13:14:02 -07:00
Winston Chang
6c14789362 Merge pull request #492 from rstudio/bugfix/0.10misc
Miscellaneous bug fixes
2014-05-23 14:13:29 -05:00
Winston Chang
880a12b914 Merge pull request #489 from yihui/bugfix/428
Fixes #428
2014-05-23 14:09:43 -05:00
Joe Cheng
93d69400e6 Merge pull request #486 from yihui/feature/widget-width
Width of selectize and sliders
2014-05-22 15:43:34 -07:00
Joe Cheng
d4829e49ea Doc tweak to validation 2014-05-21 19:57:18 -07:00
Joe Cheng
1c56be3a6b Suppress validation errors from printing at console 2014-05-21 19:51:24 -07:00
Joe Cheng
07a0dfddc7 Validation error causes real errors to look like validation errors
The CSS class for validation errors was not being properly removed between different kinds of errors
2014-05-21 19:32:44 -07:00
Joe Cheng
b86f9086ef Avoid black background when renderPlot doesn't actually plot 2014-05-21 19:28:51 -07:00
Joe Cheng
343ca12c6f Don't print NULL to the console during renderPrint 2014-05-21 19:28:28 -07:00
Joe Cheng
af3c4f84b6 Merge pull request #488 from yihui/bugfix/220
fix #220: the first entry in zip is not necessarily a directory
2014-05-20 00:32:11 -07:00
Yihui Xie
3679e8795f fix #220: the first entry is not necessarily a directory
in that case, we use dirname() on the first entry
2014-05-20 01:43:48 -05:00
Yihui Xie
39b4805a76 make sure the selected argument never contains names; fixes #428 2014-05-20 01:31:14 -05:00
Yihui Xie
3bdcdf96d4 upgrade selectize.js to v0.9.1 to fix the third issue in #428 2014-05-20 01:30:37 -05:00
Yihui Xie
b54e5d33bc roxygenize 2014-05-19 11:25:40 -05:00
Yihui Xie
85e020a513 examples of the 'width' argument 2014-05-19 11:24:56 -05:00
Yihui Xie
5b6268f5bc add width to selectInput() as well 2014-05-19 11:23:52 -05:00
saurfang
063b58eebb Merge remote-tracking branch 'upstream/master' 2014-05-17 16:33:25 -04:00
Yihui Xie
f8b38e4683 validateCssUnit(width) for selectize and slider 2014-05-17 01:32:01 -05:00
Yihui Xie
18e85c32b4 roxygenize 2014-05-17 01:28:27 -05:00
Yihui Xie
831fba9a53 add a 'width' option to selectizeInput() and sliderInput() to specify the width of these widgets 2014-05-17 01:28:27 -05:00
Joe Cheng
b1f233cd8c Merge remote-tracking branch 'origin/pr/485'
Conflicts:
	NEWS
2014-05-16 23:22:01 -07:00
Yihui Xie
3d0caba695 \emph cannot be used in \code{}: only \var and \link are allowed 2014-05-17 00:53:57 -05:00
Yihui Xie
79c92f1f8e fixes #429, which is yet yet another WAT of RJSONIO
perhaps we really should consider switching to jsonlite...
2014-05-17 00:43:13 -05:00
Yihui Xie
87f26e47bb a news item for the 'width' argument of renderPrint() 2014-05-16 14:19:55 -05:00
Yihui Xie
9d8d04ae28 add a 'width' argument so we can control the width of the text output 2014-05-16 14:17:50 -05:00
Yihui Xie
a42f046ff8 capture.output() has already considered withVisible(), and we do not need to redo it 2014-05-16 14:12:50 -05:00
Yihui Xie
01c24a578b add functions serverInfo() (exported) and setServerInfo() (not exported)
we can call shiny:::setServerInfo() in Shiny Server before launching an app, so that the app author can make use of the info to decide the behavior of the app
2014-05-16 13:22:22 -05:00
saurfang
6b82354129 Merge remote-tracking branch 'upstream/master' 2014-05-15 12:30:52 -04:00
Joe Cheng
b00fbda1ae Make sure random bytes are formatted with 2 chars 2014-05-14 17:11:18 -07:00
saurfang
bab200ff03 Merge remote-tracking branch 'upstream/master' 2014-05-14 15:44:23 -04:00
Joe Cheng
357e81aeca Bump version 2014-05-14 09:14:19 -07:00
Joe Cheng
3189c748b5 Merge pull request #479 from rstudio/not-just-last-expressions
Allow shinyUI and shinyServer calls to not be the last expression in ui....
2014-05-14 09:06:28 -07:00
Joe Cheng
2700643cbf Merge pull request #480 from jcheng5/bugfix/renderplot-height-overlap
Fix #477: renderPlot in shinydoc with height > 400 overlaps subsequent c...
2014-05-14 09:06:15 -07:00
saurfang
b0f95cd9e0 Add inline options
inline options for radiobuttons and checkboxgroupinput to allow choices
rendered horizontally
2014-05-13 17:18:20 -04:00
Winston Chang
ff628ac0b2 Fixes for jshint 2014-05-12 20:46:24 -05:00
Joe Cheng
f21aefe9e9 Merge pull request #467 from yihui/cosmetic
Some cosmetic changes regarding NULL
2014-05-10 02:13:25 -07:00
Joe Cheng
8babbd69d8 Merge branch 'session-documentation'
Conflicts:
	inst/staticdocs/index.r
2014-05-09 19:41:19 -07:00
Joe Cheng
11bf02eb56 Merge pull request #478 from rstudio/remove-literate-programming
Stop using literate programming
2014-05-09 19:39:58 -07:00
Joe Cheng
f5fa7d6d4b Fix #477: renderPlot in shinydoc with height > 400 overlaps subsequent content 2014-05-09 18:14:28 -07:00
Joe Cheng
77bff6e6c2 Allow shinyUI and shinyServer calls to not be the last expression in ui.R and server.R 2014-05-08 16:14:53 -07:00
Joe Cheng
e84a76cebd Merge pull request #457 from yihui/feature/unsatisfied-input
A first attempt of the custom error type for unsatisfied input dependencies
2014-05-08 16:12:58 -07:00
Yihui Xie
342265be94 put the custom class(es) in the first as Hadley suggested 2014-05-08 15:04:49 -07:00
Yihui Xie
62ec9291d8 Merge pull request #1 from jcheng5/feature/unsatisfied-input
Refactoring/renaming of validation
2014-05-08 14:57:54 -07:00
Joe Cheng
dee6fbcb8f Stop using literate programming
Couldn't build from source without knitr installed, and knitr
is not a required dependency
2014-05-08 14:54:11 -07:00
JJ Allaire
72fa9a2dcb bump version 2014-05-08 12:54:20 -04:00
Joe Cheng
ca27a9e31a Validation refactoring
- Move validation logic from shinywrappers.R to utils.R
- Don't coerce validation results; fail if not FALSE, NULL, or character
- Reverse order of stopWithCondition args
2014-05-07 16:20:07 -07:00
Joe Cheng
18d0f45cf9 Refactoring/renaming of validation
- validateInput renamed to validate
- validateCondition renamed to need
- Removed ability to provide "bare" conditions. It is
  still possible to fail validation silently by passing
  FALSE as the second argument to need()
- Rather than using a two-element list to convey results,
  use a single result protocol; NULL is success, FALSE is
  silent failure, string is failure with message
- Tweak "missing input" semantics, add tests
2014-05-07 16:09:06 -07:00
Joe Cheng
424fd515a4 Merge pull request #472 from rstudio/bugfix/delayed-assign-2
Remove delayedAssign which causes problems for downstream packages
2014-05-07 09:56:19 -07:00
Joe Cheng
00b40d64a1 Remove delayedAssign which causes problems for downstream packages
See comment on d7eb9b2d18
2014-05-06 20:40:57 -07:00
Joe Cheng
3a7d0a5a9f Document session object
Also allow http handlers to return standard Rook responses instead of
httpResponse objects.
2014-05-06 11:56:23 -07:00
Joe Cheng
57a02318e3 Clearer error message when shinyAppDir is given a nonexistent path 2014-05-05 15:31:21 -07:00
Joe Cheng
8f6d8cf0d6 Slider with label=NULL doesn't have tag classes attached 2014-05-05 14:00:43 -07:00
Joe Cheng
5b6605b296 Add inputPanel to staticdocs index 2014-05-05 13:59:46 -07:00
Joe Cheng
4d83596595 Merge pull request #465 from rstudio/feature/horizontal-layout
New layouts for horizontal placement
2014-05-05 13:10:26 -07:00
Joe Cheng
7e12a281f5 Remove slider from bundled jquery-ui 2014-05-05 12:12:41 -07:00
Joe Cheng
c63c10e48a Merge pull request #466 from rstudio/bugfix/space-urls
Encode pathname when necessary on browsers that supply it decoded
2014-05-05 11:40:06 -07:00
Joe Cheng
155554f0b7 Only double-encode on Qt 2014-05-05 11:36:27 -07:00
Yihui Xie
26b0836756 since NULL in tag() will be dropped, there is no need to check is.null(icon) 2014-05-03 01:11:32 -05:00
Yihui Xie
a87dc9bab2 cosmetic: both if (FALSE) expr and for-loops return NULL 2014-05-03 01:09:51 -05:00
Yihui Xie
9c1555a110 tweak an RStudio project option 2014-05-03 00:54:44 -05:00
Joe Cheng
fbda2db884 Only show special "No UI defined" message for shinyAppDir 2014-05-02 18:52:37 -07:00
Joe Cheng
2a229774ef Merge pull request #463 from jcheng5/bugfix/faster-select
Massively faster selectInput
2014-05-02 17:31:38 -07:00
Joe Cheng
137e5b13ef Update tests 2014-05-02 17:28:01 -07:00
Joe Cheng
7920d66cd0 Separate option tags with newline 2014-05-02 17:26:27 -07:00
Yihui Xie
9f2dae7f3b the logic of the last commit was not correct: we should not stop immediately on a non-condition object; instead, we still need to collect all messages on all conditions before we stop 2014-05-02 18:13:35 -05:00
Yihui Xie
ffde0ad1f5 roxygenize 2014-05-02 17:56:20 -05:00
Yihui Xie
2c2658a8ec rewrite the lapply()/vapply() with a plain dumb for-loop 2014-05-02 17:54:05 -05:00
Yihui Xie
6f2f8f6f7a add a function validateCondition() to avoid ambiguity
two advantages:

1. we no longer need to worry about input$foo being a list;
2. users have to explicitly call validateCondition() when they want error messages, so we know when to silently stop;
2014-05-02 17:33:29 -05:00
Yihui Xie
4b6dcdd1b0 use shiny-output-error as the _prefix_ for CSS classes of error messages 2014-05-02 16:56:43 -05:00
Yihui Xie
de346fd6c3 filter out some common error classes 2014-05-02 16:56:43 -05:00
Yihui Xie
bf9d7c2012 use the classes of the error condition as names of CSS classes in the output 2014-05-02 16:56:43 -05:00
Yihui Xie
143803f86d factor out stop() code to a separate function 2014-05-02 16:56:43 -05:00
Yihui Xie
311143451d condition first, message second 2014-05-02 16:56:43 -05:00
Jonathan McPherson
c9030f401d encode pathname when necessary on browsers that supply it decoded 2014-05-02 14:41:11 -07:00
Joe Cheng
8668ddce74 Tweak padding 2014-05-02 12:21:34 -07:00
Joe Cheng
7a495357f7 Update NEWS, tweak description for flowLayout 2014-05-02 12:06:15 -07:00
Joe Cheng
13864a811d Add inputPanel 2014-05-02 12:06:15 -07:00
Joe Cheng
5b65e4b250 Replace horizontalLayout with flowLayout; add splitLayout 2014-05-02 12:06:15 -07:00
Joe Cheng
dfe4a80501 Update NEWS 2014-05-02 12:06:15 -07:00
Joe Cheng
bf82b9742a Use standardized widths for selectize/jslider
The 100% width worked well inside of a sidebar, but in other situations
like full-width columns or zero-min-width tables a fixed width is better.
If there's demand we can add parameters for setting the width to custom
values including 100%.
2014-05-02 12:06:14 -07:00
Joe Cheng
829a466f72 New horizontalLayout function; put elements in a single table row 2014-05-02 12:06:14 -07:00
Yihui Xie
1206c70c42 we -> you 2014-05-02 12:45:35 -05:00
Joe Cheng
3c32c349b9 Always use Rscript from R_HOME 2014-05-02 09:12:28 -07:00
Yihui Xie
0709f08d65 a custom type of errors for unsatisfied input dependencies 2014-05-01 18:13:56 -05:00
Joe Cheng
50f78c6e40 Make tabsetPanel result visible
Without this, they don't show up in knitr documents
2014-05-01 14:31:25 -07:00
Joe Cheng
7e7afc6d38 Massively faster selectInput 2014-05-01 14:16:27 -07:00
Winston Chang
1130eadac8 Bump version 2014-04-30 14:35:08 -05:00
Winston Chang
959fc2bbb2 Merge pull request #459 from jcheng5/naked-render-in-tags
Allow naked renderXXX functions to be used inside other tags
2014-04-30 13:24:38 -05:00
Joe Cheng
f8ae505011 Change all "is()" calls to "inherits()"
According to ?inherits
2014-04-30 11:19:49 -07:00
Joe Cheng
cd183a1926 Merge branch 'feature/mask-reactive-context'
Conflicts:
	NEWS
2014-04-30 11:14:50 -07:00
Joe Cheng
bb2796fbc3 Add tests 2014-04-30 11:14:20 -07:00
Joe Cheng
5de7103890 Upgrade jqueryui to 1.10.4 and remove datepicker
The jqueryui datepicker collides with our bootstrap datepicker

Reviewed by @wch
2014-04-30 11:07:51 -07:00
Joe Cheng
a78c91ba7e Make renderPlot print result if visible
Reviewed by @wch
2014-04-30 10:10:06 -07:00
Joe Cheng
fca50da57b Fix staticdoc test 2014-04-29 12:09:37 -07:00
Joe Cheng
61f2c908b1 Add maskReactiveContext function 2014-04-29 12:01:03 -07:00
Joe Cheng
4c096ac068 Merge pull request #454 from yihui/feature/selectize-more
Make `selected` work for server-side selectize input
2014-04-28 15:41:19 -07:00
Jonathan
2c95678be1 Merge pull request #451 from jcheng5/suppress-reactlog
Don't send reactlog messages to the client unless showcase mode
2014-04-28 14:52:42 -07:00
Joe Cheng
1a643cecf3 Allow naked renderXXX functions to be used inside other tags 2014-04-28 10:13:16 -07:00
Joe Cheng
aa10b2e8c4 Merge pull request #455 from trestletech/feature/subapp
Added extra query param to identify sub apps.
2014-04-28 09:33:37 -07:00
Joe Cheng
0b9317d047 Merge pull request #453 from yihui/feature/nocache
Prevent caching conditionally
2014-04-28 09:33:03 -07:00
Yihui Xie
4d58f05f38 fix the warning about stats not being imported in namespace
see https://travis-ci.org/rstudio/shiny/builds/23791246

but do we even have to import stats? I guess no; it is part of base R
2014-04-25 18:26:07 -05:00
trestletech
6e879c8156 Added extra query param to identify sub apps. 2014-04-25 11:30:48 -05:00
Yihui Xie
b6ee67aa41 make the selected argument of updateSelectizeInput() work even in the server mode 2014-04-25 02:38:38 -05:00
Yihui Xie
07bed0c7c7 factor out a function columnToRowData() so both updateSelectInput() and selectizeJSON() can use it 2014-04-25 02:37:45 -05:00
Yihui Xie
d2bd59d149 Make meta of length 0 when we are sure there are no dependencies
knitr::asis_output() will decide whether x is cacheable by checking length(meta) == 0. Some shiny tags are cacheable, such as numericInput(), because it is pure HTML without dependencies, whereas selectizeInput() is not cacheable due to the dependency on selectize.js.
2014-04-25 01:14:14 -05:00
Yihui Xie
7bdac5a44e Make sure shiny app objects are not cacheable using asis_output(..., cacheable = FALSE)
This commit reverts d08a2507fa
2014-04-25 01:14:08 -05:00
Joe Cheng
51f5db4374 Bump version 2014-04-24 16:38:07 -07:00
Joe Cheng
e395ae6555 Merge branch 'html-deps'
Code reviewed by @jmcphers

Conflicts:
	DESCRIPTION
	R/bootstrap.R
	R/reactives.R
2014-04-24 16:37:46 -07:00
Joe Cheng
1df9c498cf Ensure dep.meta is an array 2014-04-24 16:32:18 -07:00
Joe Cheng
57b3b919a5 Don't send reactlog messages to the client unless showcase mode 2014-04-24 16:05:54 -07:00
Joe Cheng
00c6bbb297 Merge pull request #449 from rstudio/bugfix/knitr-cached-shiny
Fail when attempting to insert a Shiny app into a cached knitr chunk
2014-04-24 15:50:05 -07:00
Joe Cheng
b6536a0af3 Merge pull request #445 from wch/str
Add str.reactivevalues
2014-04-24 15:49:21 -07:00
Jonathan McPherson
d08a2507fa fail when attempting to insert a shiny app into a cached chunk 2014-04-24 15:28:07 -07:00
Joe Cheng
8bc8829577 Merge pull request #448 from trestletech/rate-element
Add element argument to getRateCallback()
2014-04-24 10:01:42 -07:00
trestletech
c843e6f68c Bump version 2014-04-24 10:58:36 -05:00
trestletech
84583e5501 Add element argument to getRateCallback() 2014-04-24 10:13:06 -05:00
Winston Chang
4548562138 Fix argument name for S3 method 2014-04-23 15:03:49 -05:00
Joe Cheng
32c170b10a Remove Rmd examples 2014-04-23 13:02:40 -07:00
Joe Cheng
97dafa0a55 Include html-preserving comments in knit_print 2014-04-23 12:46:56 -07:00
Yihui Xie
0be1ee46f2 do not ignore the names of choices data 2014-04-22 17:58:19 -05:00
Winston Chang
34c9ab7643 Add str.reactivevalues 2014-04-21 21:59:49 -05:00
Joe Cheng
59dbca250f Fix roxygen 2014-04-21 16:50:06 -07:00
Joe Cheng
4028dbfda1 Do typeset mathjax on initial load
Fixes initial load not being typeset on e.g.:
https://gist.github.com/jcheng5/a0d123d58737590ac21e
2014-04-21 13:59:17 -07:00
Joe Cheng
b9dbf610b0 Fix bootstrap themes 2014-04-21 13:16:34 -07:00
Joe Cheng
d443810520 Use html_dependency for date inputs, datatables 2014-04-21 12:35:12 -07:00
Joe Cheng
fcd941d33d Use html_dependencies for showcase mode
For some reason, showcase mode was breaking sliders without this (e.g. 06_tabsets)
2014-04-21 12:34:54 -07:00
Yihui Xie
9c063fa37c bump version 2014-04-21 01:02:46 -05:00
Yihui Xie
2720cfe346 should use MathJax.Hub.Queue() instead of .Typeset(): finally solved the mysterious race condition issue
http://docs.mathjax.org/en/latest/typeset.html
2014-04-21 01:02:05 -05:00
Joe Cheng
c39e38081e Remove unused class 2014-04-18 16:29:13 -07:00
Joe Cheng
3deb4c3f42 Fix initial dependency handling, fix knit_print 2014-04-18 16:21:13 -07:00
Joe Cheng
6945091238 First attempt at HTML dependency management 2014-04-18 15:18:51 -07:00
JJ Allaire
c758c4785a bump version 2014-04-17 07:38:06 -04:00
Jonathan McPherson
19269a20fb fix subapps on portmapped URLs
Reviewed by @jcheng5
2014-04-16 13:08:57 -07:00
Joe Cheng
45669cacb1 Merge pull request #434 from jcheng5/private-random
Refactor private random seed code
2014-04-15 15:24:41 -07:00
Joe Cheng
840bc52aae Merge pull request #433 from yihui/selectize/server
server-side selectize input
2014-04-15 15:22:11 -07:00
Joe Cheng
bbc36e349f Merge pull request #439 from jcheng5/feature/uiPattern
Add uiPattern param to shinyApp to allow one ui to serve multiple URLs
2014-04-15 15:13:08 -07:00
Joe Cheng
a4325adcdd Add uiPattern param to shinyApp to allow one ui to serve multiple URLs
Reviewed by @jmcphers
2014-04-15 12:53:18 -07:00
Yihui Xie
23f39649d0 Merge pull request #438 from jcheng5/fix-roxygen
Fix PriorityQueue error message during roxygenize
2014-04-15 00:12:43 -05:00
Yihui Xie
87b09a534e Merge pull request #435 from jcheng5/null-input-labels
Allow NULL input labels; add actionLink
2014-04-14 22:59:28 -05:00
Yihui Xie
39f0e5ae0c fix some JShint issues 2014-04-14 22:53:08 -05:00
Yihui Xie
62aaab0926 closes #422: update the selectize options via updateSelectizeInput(options) 2014-04-14 22:34:48 -05:00
Yihui Xie
cddfe999aa cosmetic changes (re-indent code) 2014-04-14 22:28:45 -05:00
Joe Cheng
fcbb658ac2 Fix PriorityQueue error message during roxygenize 2014-04-14 15:37:06 -07:00
Joe Cheng
3bbf06ba49 Don't indent HTML during knit_print 2014-04-14 15:31:45 -07:00
Yihui Xie
d9be6f1d2e implementing the server-side selectize input
also added a new method shinysession$registerDataObj(), which was designed to be a general data retrieval method: we can store arbitrary data objects as "downloads", and return arbitrary http response based on the filter function; see renderDataTable() and updateSelectizeInput() for two examples
2014-04-13 00:02:08 -05:00
Yihui Xie
5d70e68a0b the development version of knitr can be installed from rforge.net 2014-04-12 12:33:42 -05:00
Joe Cheng
529f2325b2 Fix tests 2014-04-11 15:46:32 -07:00
Joe Cheng
314d433f86 Merge pull request #436 from rstudio/feature/shiny-docs
Improved support for R Markdown Shiny documents
2014-04-11 15:43:43 -07:00
Jonathan McPherson
12ea950c5f use more idiomatic syntax for resolving rmarkdown.runtime 2014-04-11 14:41:06 -07:00
Yihui Xie
f4d12220ca Merge pull request #437 from trestletech/winslash
Make normalizePath calls Windows-compatible.
2014-04-11 15:41:54 -05:00
Jonathan McPherson
6a9cba90f4 have validateCssUnit treat unit-free character strings as pixel units 2014-04-11 12:59:15 -07:00
trestletech
6873e1f1cb Make normalizePath calls Windows-compatible. 2014-04-11 14:55:51 -05:00
Joe Cheng
fa0a91a75d Fix createUniqueId logic 2014-04-11 12:06:22 -07:00
Joe Cheng
020bb659c5 Rename tempSet to withTemporary 2014-04-11 12:00:55 -07:00
Joe Cheng
b1d6687fb0 Fix private random seed mechanism
- Introduce randomInt/p_randomInt to generate random integers in a half-open range
- Stop using runif to generate integers
- Explicitly reset the private seed during .onLoad. I was getting the same "random" numbers from Shiny every time I restarted R!
2014-04-11 11:57:39 -07:00
Joe Cheng
f67e17b287 Allow NULL input labels; add actionLink 2014-04-11 11:00:41 -07:00
Jonathan McPherson
81bd57c5ea emit appropriate warnings in R Markdown mode; collect <head> contents 2014-04-11 10:53:25 -07:00
Joe Cheng
d803bae874 Bump ver 2014-04-11 10:17:51 -07:00
Joe Cheng
14606f4087 Remove unnecessary ::: 2014-04-11 10:16:59 -07:00
Joe Cheng
599fdc7ee5 Refactor private random seed code 2014-04-10 15:03:18 -07:00
Joe Cheng
722e205db5 Isolate createUniqueId randomness from rest of R
TODO: Generalize to our own runif

Reviewed by @jmcphers
2014-04-09 17:16:36 -07:00
Joe Cheng
f67849eb47 Doc cleanup 2014-04-09 17:10:37 -07:00
Joe Cheng
662ca4e40a Fix travis? 2014-04-09 16:28:36 -07:00
Joe Cheng
aa61be74d8 Merge pull request #432 from jcheng5/multiple-apps
Support reactive documents and embedded Shiny apps in rmarkdown
2014-04-09 16:23:43 -07:00
Joe Cheng
10296fcd6b Clear handler manager anytime runApp is called 2014-04-09 16:10:43 -07:00
Joe Cheng
f8bf146b6c Render functions can be inserted directly into .Rmd
All render functions need to call markRenderFunction on their
return values for this mechanism to work.

Also:
- Remove runRmdContainer (it's moved to rmarkdown)
- Remove some bad .Rbuildignore entries
- Make height/width in shinyApp respected
2014-04-09 14:53:56 -07:00
Joe Cheng
52f104c517 Automatically remove subapps when their owning session finishes 2014-04-08 16:38:45 -07:00
Joe Cheng
6c1fc224f0 Merge pull request #1 from jcheng5/reactive-domains
Reactive domains
2014-04-08 15:44:51 -07:00
Joe Cheng
6b9ae3a8b3 Move "@include globals.R" directives to top; slight doc fixes 2014-04-08 15:39:57 -07:00
Joe Cheng
07f73030c6 More reactive domain work
- observers' autodestroy behavior is now optional
- tests
2014-04-08 14:47:32 -07:00
Joe Cheng
47130c79ee Code review feedback 2014-04-08 11:16:42 -07:00
Joe Cheng
f3a3bdfe4f Port showcase mode execution highlighting to domains 2014-04-07 21:59:16 -07:00
Joe Cheng
e5e54fe4c1 Implement reactive domains 2014-04-07 21:55:05 -07:00
Joe Cheng
29c0f9a43a Ignore additional knit_print parameters 2014-04-07 17:10:44 -07:00
Joe Cheng
0b78229c77 Travis!! *shakes fist* 2014-04-07 11:45:46 -07:00
Joe Cheng
c2a1d70070 Fix travis 2014-04-07 10:54:21 -07:00
Joe Cheng
260ecd1d9f Travis: Install latest knitr master 2014-04-07 09:24:51 -07:00
Joe Cheng
3dce2e761a Fix debugging of server files
.global$server becomes appvars$server
2014-04-04 10:30:42 -07:00
Joe Cheng
80a54200ce Comment tweaks 2014-04-03 23:45:08 -07:00
Joe Cheng
51227d438a Get rid of "incomplete final line" warnings in showcase code 2014-04-03 21:33:40 -07:00
Joe Cheng
6fb4199d37 Make the worker ID global across all sub apps 2014-04-03 21:30:56 -07:00
Joe Cheng
6ba46aff6b Fix gitignores for literate 2014-04-03 21:02:18 -07:00
Joe Cheng
5da34d0646 Fix makefile on windows 2014-04-03 20:43:55 -07:00
Joe Cheng
f215088939 Update staticdocs index, tests pass 2014-04-03 19:43:11 -07:00
Joe Cheng
df34dcdb0c Pass R CMD check 2014-04-03 19:37:45 -07:00
Joe Cheng
89f464af99 Fix Rmd build tooling 2014-04-03 15:31:49 -07:00
Joe Cheng
3f6f02f7d2 More docs 2014-04-03 14:41:59 -07:00
Joe Cheng
0d861e5389 Add documentation for handlers/middleware 2014-04-03 14:24:55 -07:00
Joe Cheng
b290c8700c Allow the use of .Rmd files for shiny impl scripts 2014-04-03 11:12:16 -07:00
Joe Cheng
81b6fbe263 Remove obsolete print.shiny.appdir 2014-04-03 01:51:30 -07:00
Joe Cheng
b3af293f66 Fix scoping bug 2014-04-03 01:49:36 -07:00
Joe Cheng
b187485172 Major refactor of runApp/addSubApp pipeline
- shinyUI and shinyServer calls are no longer required in ui.R and server.R
- shinyAppObj renamed to shinyApp
- runApp can take pathname, list(ui=..., server=...), shinyApp, and shinyAppDir
  as appDir argument
- Unify all Shiny app representations around shiny.appobj
- BREAKING CHANGE: shinyUI no longer has a "path" argument
- Instead of returning UI, ui.R can return a function that returns UI; it will
  be invoked each time the page is requested. (Note that this is NOT the same
  as saying ui.R will be run each time the page is requested.) The function can
  take either no args or a single "req" arg which is the request.
2014-04-03 01:42:01 -07:00
Joe Cheng
b449d9759c Minor doc updates 2014-04-01 23:02:22 -07:00
Joe Cheng
d9d63a3a2e Painful refactoring of server.R
This refactor changes the level of abstraction where sub-apps are implemented.
Sub-apps can basically be thought of as routing (previously called "proxying"
which was way too confusing). A call comes in to /1e8f937a8934/ and it matches
a sub-app path--we need to change the path from /1e8f937a8934/ to / for the
duration of the sub-app's handling of the request.

We used to do routing (nee proxying) at the httpuvCallback level, which added
a lot of complexity because it meant we were compositing HTTP handlers at both
the httpHandler level, and then again at the httpuvCallback level. This
refactor changes it so nobody speaks the language of httpuv except at the very
boundary of Shiny (webserver$createHttpuvApp), everything inside is either an
httpHandler or a wsHandler. So whether you're combining or routing or whatever,
everything now works the same way.
2014-04-01 22:55:56 -07:00
Joe Cheng
fd7b54fb77 Clean up exports, examples 2014-03-31 16:40:17 -07:00
Joe Cheng
887f8a606d Restore index.r entries for app obj 2014-03-31 12:11:16 -07:00
Joe Cheng
7e3717243f Merge remote-tracking branch 'origin/master' into multiple-apps
Conflicts:
	staticdocs/index.r
2014-03-31 12:08:59 -07:00
Joe Cheng
221849aa3a More-correct impl of %OR% 2014-03-31 12:07:57 -07:00
Joe Cheng
b52d40ab28 Merge pull request #430 from rstudio/v0.10
V0.10
2014-03-31 11:55:29 -07:00
Joe Cheng
3ed68ffd92 knit_print tags and tagLists 2014-03-31 11:17:24 -07:00
Joe Cheng
cc3cd2c141 Implement Shiny apps embedded as iframes in knitr 2014-03-31 10:03:22 -07:00
Winston Chang
5e30f7efc4 Merge pull request #424 from rstudio/bugfix/zero-arg-sourcerefs
Handle zero-argument outputs gracefully
2014-03-27 12:48:41 -05:00
Yihui Xie
35090251ef basically library(pkg) = if (!require(pkg)) stop() 2014-03-24 23:49:34 -05:00
Yihui Xie
338afb4893 change the deprecated @S3method to @export 2014-03-24 23:46:46 -05:00
Yihui Xie
194d8a05f8 using the latest master of klutometis/roxygen (d823c3a088b20ea5e38a60d78d42ccbe9f1e1eec)
Rd text for arguments is no longer wrapped by default
2014-03-24 23:46:46 -05:00
Winston Chang
93e276bd9b Fix capitalization of markdown package 2014-03-24 12:35:12 -05:00
Winston Chang
a69517519c Add more information to selectInput docs 2014-03-21 16:30:25 -05:00
Winston Chang
f646b1efb4 Bump version to 0.9.1.9000 for development 2014-03-21 16:16:53 -05:00
Jonathan McPherson
fc9bedacc0 guard against null source references 2014-03-20 16:00:51 -07:00
Jonathan McPherson
795eeee809 handle no-argument output calls gracefully 2014-03-20 15:46:25 -07:00
Joe Cheng
6d7818962e Redirect on no trailing slash 2014-03-20 10:14:02 -07:00
Joe Cheng
068517c933 Prevent multiple apps from stomping on server func 2014-03-20 09:09:54 -07:00
Joe Cheng
5b030200df initial prototyping of subapps 2014-03-20 08:49:18 -07:00
Joe Cheng
c732122966 Merge pull request #421 from jcheng5/bugfix/context-new
Fix 'Error in Context$new : could not find function "loadMethod"'
2014-03-19 11:40:19 -07:00
Joe Cheng
d7eb9b2d18 Fix 'Error in Context$new : could not find function "loadMethod"'
This warning was happening to dependent packages on R CMD check.

The problem is due to delayedAssign; it appears this can't be used safely, at least not to define package-level symbols that contain S4 or reference class objects.

If you call this in a package's .R file:

`delayedAssign("hello", stop("boom"))`

but don't refer to "hello" anywhere, when you run R CMD check on a dependent package you'll see the error.

If the expression needs the methods package (like Context$new()), you'll get an error unless the dependent package itself depends on methods.
2014-03-19 09:49:33 -07:00
Yihui Xie
b8b09adda1 submit shiny 0.9.0 to CRAN 2014-03-18 14:32:46 -05:00
Yihui Xie
07c8f0c4b7 use skipStartupTypeset: true for MathJax config, and call MathJax.Hub.Typeset() later
setTimeout() is necessary for uiOutput(); we need to wait for a short while before typesetting math, otherwise two bad things can happen:

1. a math expression may be rendered twice (static output)
2. it is not rendered at all (dynamic ui output)

so the compromise is to typeset math after a short while when the document is ready; 200 ms is an arbitrary choice here
2014-03-18 14:24:23 -05:00
Joe Cheng
2bd201de63 Refactor server logic into separate file 2014-03-17 16:08:59 -07:00
Joe Cheng
0b7e118a37 Merge branch 'staticdocs-0.9.0' into v0.10
Conflicts:
	staticdocs/index.r
2014-03-17 14:40:45 -07:00
Joe Cheng
a546769225 Use new staticdocs packaging standards 2014-03-17 14:37:38 -07:00
Joe Cheng
81745f932d Include withMathJax in staticdocs; improve test 2014-03-17 14:07:05 -07:00
Joe Cheng
4415bf31d2 Mention showcase mode and allow-dots in NEWS 2014-03-13 14:33:25 -07:00
Joe Cheng
5c1bcb41d8 Merge pull request #418 from jcheng5/allow-dots
Allow '.' character in input/output IDs

Fixes #358
2014-03-13 14:25:56 -07:00
Joe Cheng
b659c4c2bb Proper escaping of name, value, and type queries 2014-03-13 12:18:21 -07:00
Joe Cheng
65adc8a405 Wrap for= value in double quotes 2014-03-13 10:16:07 -07:00
Joe Cheng
4141f78717 It's important to return values in JS. heh. 2014-03-13 10:13:26 -07:00
Joe Cheng
80cb02d206 Allow '.' character in input/output IDs
I always thought dots and colons were illegal in HTML ID attributes,
but I was wrong. They are legal and because they are commonly used
in identifier names in R, Shiny users often like to use them. Worse,
Shiny gave no warnings when using dots and only a pretty advanced
subset of functionality would NOT work when using dots, causing
everyone to think they were fully supported in Shiny.

This commit ought to bring reality in line with perception. It turns
out that jQuery has an escaping scheme in its queries that allow us
to support dots after all. As long as we are always careful to
surround IDs with $escape when putting them in a query, we'll be in
good shape.

Colons will probably still cause problems at the moment because we
use colons internally to separate input type from input name. But
we've never seen users try to use colon in IDs before, so we can
wait to fix it until that becomes a problem.
2014-03-12 17:39:05 -07:00
Joe Cheng
a5a4510a1e Merge pull request #417 from yihui/bugfix/showcase-escape
fixes #416: use tags$code() to escape the code in showcase mode
2014-03-11 21:09:48 -07:00
Yihui Xie
95c30649d3 fixes #416: use tags$code() to escape the code in showcase mode
and format(..., indent = FALSE) to prevent indentation of <code>
2014-03-11 22:40:16 -05:00
Joe Cheng
8e5cbde08c Change example licenses to MIT 2014-03-11 10:49:32 -07:00
Yihui Xie
6df8632e29 Merge pull request #414 from jcheng5/bugfix/selectize-order
Fix selectize misordering on updateSelectInput
2014-03-11 11:55:26 -05:00
Jonathan
3c1218fff1 Merge pull request #415 from jcheng5/bugfix/showcase-case
Make showcase mode work with .r files
2014-03-11 09:13:04 -07:00
Joe Cheng
69c0414791 Make showcase mode work with .r files 2014-03-11 09:08:20 -07:00
Joe Cheng
d63f83fcbb Fix selectize misordering on updateSelectInput
Repro case at https://gist.github.com/jcheng5/9403917
2014-03-06 18:27:02 -08:00
Joe Cheng
75c3bf0c2f Change version to 0.9.0 2014-03-06 10:20:25 -08:00
Joe Cheng
c9a8ab2389 Merge pull request #411 from yihui/selectize/backspace
Selectize backspace issue
2014-03-05 00:29:44 -08:00
Yihui Xie
2c467c00e1 10px bottom margin for selectize input 2014-03-04 16:53:29 -08:00
Yihui Xie
c63ec5a1f2 update the test for selectInput() accordingly 2014-03-03 12:02:39 -08:00
Yihui Xie
e886558cbb roxygenize 2014-03-03 12:02:39 -08:00
Yihui Xie
8dd6dabe50 add selectize=TRUE to selectInput(), but disable deletion for single input
hopefully this is a good compromise for #404
2014-03-03 12:02:39 -08:00
Joe Cheng
c090c6adf9 Merge pull request #410 from wch/faster-tags-2
Faster tags
2014-02-28 09:43:57 -08:00
Yihui Xie
84da0befcd fixes #306: remove the possible trailing slash under Windows
the cause:

normalize('foo',  '/') => C:/foo
normalize('foo/', '/') => C:/foo/

under Windows. For unix, the trailing slash will always be removed in normalizePath()
2014-02-27 18:17:50 -06:00
Yihui Xie
267751c8b9 we have specified winslash='/' before, so it is impossible that this char is \ 2014-02-27 17:59:14 -06:00
Winston Chang
8add9f7188 Restructure logic and simplify 2014-02-27 16:02:30 -06:00
Yihui Xie
a100b0991b closes #119: we do not have to do this special treatment to the label for sliderInput, and no other inputs do as.character() for their labels
if users pass an HTML() object to the label argument, the HTML will be preserved instead of being escaped
2014-02-25 23:58:59 -06:00
Winston Chang
9ce9c5e535 More tag writing tweaks 2014-02-25 23:16:50 -06:00
Yihui Xie
b2d004ca1a closes #31: try to avoid scientific notation of numbers 2014-02-25 22:58:08 -06:00
Yihui Xie
657d50f9a3 add the missing Readme's and DESCRIPTION's for the examples 2014-02-25 21:18:16 -06:00
Joe Cheng
60e355c4f5 Faster singleton detection 2014-02-25 18:56:47 -08:00
Yihui Xie
adb444a60f the original hello-world example makes little practical sense -- it is unclear what really changed when moving the slider, especially when obs is large (we always see a "bell-shaped" histogram)
let's make the number of bins reactive instead; now it is very clear what the slider really controls

a histogram with different number of bins also serves as a good demo of the property of histograms (small bins --> small variance + large bias)
2014-02-25 20:44:19 -06:00
Yihui Xie
e7e13ff70d document the new features of DataTables in NEWS 2014-02-25 19:48:46 -06:00
Yihui Xie
a1e81db597 roxygenize 2014-02-25 19:25:48 -06:00
Yihui Xie
f23f2ff0a0 the url of selectiz.js is already in Details 2014-02-25 19:25:48 -06:00
Yihui Xie
c1b18098f1 Revert "add selectize=TRUE to selectInput(), instead of adding a separate function selectizeInput(), per suggestion of @jjallaire"
This reverts commit d3115a3bf3 and closes #404
2014-02-25 19:25:33 -06:00
Joe Cheng
31c39592e3 Faster tag rendering 2014-02-25 16:37:54 -08:00
Yihui Xie
82a1dad22a roxygenize 2014-02-25 15:45:59 -06:00
Yihui Xie
1ecec24727 add a callback argument in renderDataTable() so that users can have access to the DT object 2014-02-25 15:44:00 -06:00
Yihui Xie
607841e947 cosmetic changes 2014-02-25 15:42:36 -06:00
Yihui Xie
e234b403ae when the options is null, also show the search boxes 2014-02-25 00:41:57 -06:00
Yihui Xie
80ce7a36f8 make it possible to filter numeric columns based on the specified range of the form "lower,upper" 2014-02-24 23:59:12 -06:00
Yihui Xie
705a8666be iDisplayLength == -1 means "display all data" (#400)
we can use this feature via several ways, e.g.

- renderDataTable(..., options = list(bPaginate = FALSE))
- iDisplayLength = -1
- aLengthMenu = list(c(10, 30, -1), list(10, 30, 'All'))
2014-02-24 23:31:28 -06:00
Yihui Xie
9167905118 respect the bRegex option (as well as bRegex_j for individual columns)
however, this option is not part of the initialization options, so actually users can never use it...

#400
2014-02-24 23:29:10 -06:00
Yihui Xie
bdeb6734d8 data.options is null by default 2014-02-24 21:46:50 -06:00
Yihui Xie
9a7b042594 respect the individual bSearchable_j options in datatables 2014-02-24 21:02:25 -06:00
Winston Chang
7aea256fd8 Use YYYY/MM/DD format only as a fallback 2014-02-24 16:43:49 -06:00
Yihui Xie
857b5e6932 Merge pull request #406 from wch/date-ie8
Add function for handling date strings in IE8
2014-02-24 16:24:35 -06:00
Winston Chang
1a2d675439 Add function for handling date strings in IE8 2014-02-24 16:02:57 -06:00
Yihui Xie
0c749643de Merge pull request #405 from jcheng5/bugfix/ie8-debounce
Fix debounce error in IE8
2014-02-24 15:46:03 -06:00
Winston Chang
09bb1548f9 Fixes for jshint 2014-02-24 15:35:01 -06:00
Joe Cheng
5ffe531844 Fix debounce error in IE8
In the repo https://github.com/rstudio/shiny-testapp/ the test app
called "setinput" threw errors in IE8 due to the debouncer getting
triggered incorrectly. Essentially Debouncer.$invoke was being
called twice without an intervening normalCall or immediateCall,
which caused apply to be called with this.args === null. Upon
careful inspection/debugging it seems like this may be a bug in the
IE8 implementation of setTimeout/clearTimeout:
http://stackoverflow.com/questions/5853571/clarifying-cleartimeout-behavior-in-ie

In any case, the workaround is to check for a null timer id, which
means we tried to clear the timer at least.
2014-02-24 13:14:17 -08:00
Yihui Xie
fab24a3200 httpuv 1.2.3 is in marutter precise ppa now
hopefully this can save some time for travis ci
2014-02-23 11:09:54 -06:00
Yihui Xie
899d5e9d1d spent two hours on this weird issue of disappearing checkboxes and radio buttons, just to find two missing backslashes for <label>
http://api.jquery.com/jQuery/#creating-new-elements
2014-02-22 01:01:49 -06:00
Joe Cheng
ba510884f2 Avoid using browser URL with host of 0.0.0.0
(reviewed by @jmcphers)
2014-02-21 14:58:19 -08:00
Joe Cheng
78e8df8e17 Fix tags$head + renderUI in IE8, which was broken
Repro case: https://github.com/rstudio/shiny-testapp/tree/master/dynamic_singletons

Reviewed by @jmcphers
2014-02-21 10:15:03 -08:00
Yihui Xie
deba1609c3 implement bSearchable for individual columns (#400)
if a column is not searchable, hide its search box
2014-02-20 18:29:44 -06:00
Yihui Xie
88d2425ca3 respect the bFilter option: when it is false, do not show the search boxes 2014-02-20 18:21:36 -06:00
Yihui Xie
7117f9e058 closes #392: options in renderDataTable() can also take a function to return a list 2014-02-20 16:55:44 -06:00
Yihui Xie
c21c407416 a few cosmetic changes
= to <-, and camelCase instead of under_score
2014-02-20 16:44:09 -06:00
Yihui Xie
4b4ad42063 xtable was built from R < 3.0.0 in the official ubuntu repo hence fails to load 2014-02-20 15:09:42 -06:00
Yihui Xie
474d514c7d the httpuv binary is not in the marutter/c2d4u PPA yet
we can wait for a couple of days and revert this commit
2014-02-20 14:48:08 -06:00
Yihui Xie
6239466da8 klutometis/roxygen#191 has been fixed, so no longer need explicit @usage 2014-02-20 14:30:51 -06:00
Yihui Xie
7746d75582 bug fix: when evalOptions is of length 1, toJSON() converts it to a scalar; we need to use I() to make sure it is always a vector so that we can later $.each() 2014-02-20 14:24:51 -06:00
Yihui Xie
642c9ded08 install some R package dependencies through apt-get instead of R to save time 2014-02-20 13:42:39 -06:00
Joe Cheng
e0ae931ddd Merge pull request #402 from rstudio/bugfix/ie8-compatibility
Bugfix/ie8 compatibility
2014-02-20 10:24:10 -08:00
Jonathan McPherson
0d7727a405 fix Markdown content extraction on IE8 2014-02-20 10:22:43 -08:00
Joe Cheng
28f689498a fix browser height detection and CSS on IE8 2014-02-20 09:16:18 -08:00
Yihui Xie
eb8fec7f2d when searching is turned on, we need to make sure the filtering indices i are smaller than nrow(filtered data) instead of original data
this fixes the bug reported at https://groups.google.com/forum/#!topic/shiny-discuss/xk2Gh7KJQBM
2014-02-19 23:09:25 -06:00
Yihui Xie
2e16fa1d70 fixes #401: pass numbers as character strings in updateNumericInput() to preserver numeric precision when possible 2014-02-19 22:11:39 -06:00
Jonathan McPherson
1b856c4909 use IE8-compatible events; turn off highlighting in IE8 2014-02-19 16:45:58 -08:00
Jonathan
585ad30af1 Merge pull request #396 from jcheng5/bugfix/395-monospace-fonts-qt
Fix issue #395: Monospace fonts broken on QtWebKit
2014-02-19 16:32:38 -08:00
Joe Cheng
c0cdc4083c Merge pull request #397 from yihui/select2
Selectize.js
2014-02-19 15:45:56 -08:00
Joe Cheng
9b9db4f161 Merge pull request #398 from jcheng5/feature/suppress-tag-indent
Add indent argument to format.shiny.tag
2014-02-19 15:21:51 -08:00
Joe Cheng
84a1d8d25e Add comment about format.shiny.tag's indent param 2014-02-19 15:21:28 -08:00
Yihui Xie
d3115a3bf3 add selectize=TRUE to selectInput(), instead of adding a separate function selectizeInput(), per suggestion of @jjallaire 2014-02-19 12:04:23 -06:00
Yihui Xie
964789e9a6 add a note in the NEWS that DataTables also works for IE8 now 2014-02-19 11:58:34 -06:00
Yihui Xie
eeded51ff8 IE8 does not have map() and forEach() methods
use $.each() and $.map() instead
2014-02-19 11:58:34 -06:00
Yihui Xie
8f24f1b4d6 localize es5-shim.js 2014-02-19 11:58:34 -06:00
Yihui Xie
ad910a295a For IE8, $.text() does not work on <script>, so use $.html() instead 2014-02-19 11:58:34 -06:00
Yihui Xie
cf14c6b1e9 add es5-shim.js for IE8 2014-02-19 11:58:34 -06:00
Yihui Xie
49da114caa add a note about the I() options in selectizeInput() 2014-02-19 11:58:34 -06:00
Yihui Xie
b8376ebbf7 it is safer to evaluate the string inside ()
e.g. one cannot directly evaluate {a: 1, b: 2}, although it is legitimate JSON; eval("({a: 1, b: 2})") always works
2014-02-19 11:58:33 -06:00
Yihui Xie
29701d7295 apply checkAsIs() to selectizeInput(), and store the names in data-eval in the script
note we switched the order of {} and config.text() in $.extend(), so that users can overwrite the default options like labelField, valueField, ...
2014-02-19 11:58:33 -06:00
Yihui Xie
16279695a9 factor out the code to determine which options should be evaluated into a utility function checkAsIs(), so that it can be applied to selectizeInput() as well 2014-02-19 11:58:33 -06:00
Yihui Xie
999fc86bc6 news for selectizeInput() 2014-02-19 11:58:33 -06:00
Yihui Xie
0276d533fb the search field must be renamed accordingly 2014-02-19 11:58:33 -06:00
Yihui Xie
b77fc34a7b new function selectizeInput() to use selectize.js
closes #287
2014-02-18 23:25:25 -06:00
Joe Cheng
60c450d57e Add indent argument to format.shiny.tag 2014-02-18 12:55:11 -08:00
Joe Cheng
73411c75db Fix staticdocs test on Linux 2014-02-18 12:52:59 -08:00
Yihui Xie
8d146f7dff Revert "math expressions may come in through renderUI()/uiOutput(), in which case we have to re-typeset the math expressions"
This reverts commit 58471c6971.
2014-02-17 18:52:25 -06:00
Yihui Xie
5c34aa0bb5 remove includeMathJax() and use withMathJax() instead, per suggestion of @jcheng5 2014-02-17 18:52:25 -06:00
Joe Cheng
2b2ed8162d Staticdocs prefers lower-case .r on linux 2014-02-17 16:40:08 -08:00
Joe Cheng
9770bd8005 Tweak two Rd titles 2014-02-17 16:01:57 -08:00
Yihui Xie
4e020818ae white spaces 2014-02-17 14:43:33 -06:00
Yihui Xie
58471c6971 math expressions may come in through renderUI()/uiOutput(), in which case we have to re-typeset the math expressions 2014-02-17 14:43:33 -06:00
Joe Cheng
2a2e02bf56 Remove border radius on showcase well 2014-02-17 11:34:07 -08:00
Joe Cheng
75d8cee766 Fix issue #395: Monospace fonts broken on QtWebKit 2014-02-17 11:31:39 -08:00
Joe Cheng
1aed36bd16 Revert "Merge pull request #298 from jcheng5/remove-catools"
This reverts commit 0ad9a5f9c6, reversing
changes made to c31d91668a.
2014-02-14 13:35:41 -08:00
Joe Cheng
00ce58ed18 Merge pull request #390 from dmbates/patch-2
Update server.R
2014-02-12 13:50:49 -08:00
Douglas Bates
d11aa1a61c Update server.R
Minor typo
2014-02-12 15:25:30 -06:00
Yihui Xie
56a62d3b4d Merge pull request #356 from yihui/feature/datatables-options
To be able to evaluate options passed to DataTables
2014-02-11 23:19:13 -06:00
Yihui Xie
e6dd668657 news for #356 2014-02-11 23:18:32 -06:00
Yihui Xie
f60a64c8db instead of assuming fnFooBar should be evaluated, let's use I() explicitly 2014-02-11 23:12:19 -06:00
Yihui Xie
eff1c298c9 it is okay to include README.md in an R package on CRAN now 2014-02-11 22:59:37 -06:00
Yihui Xie
358b0a122b ignore staticdocs/ when building the source package 2014-02-11 22:59:37 -06:00
Yihui Xie
c0f7ba9d46 use Rd2roxygen to convert the two manually written Rd files to roxygen comments, so that all Rd files are automatically generated from roxygen2 now 2014-02-11 22:59:37 -06:00
Yihui Xie
c4edae8196 using roxygen2 4.0.0: the spurious changes are due to klutometis/roxygen#184 (text in Rd is no longer wrapped by default) 2014-02-11 22:59:37 -06:00
Yihui Xie
398dab808c use @include to make sure the Collate field is correctly generated 2014-02-11 22:59:37 -06:00
Yihui Xie
3530871560 strip white spaces 2014-02-11 22:59:37 -06:00
Yihui Xie
1ba26fdb98 tweak news 2014-02-11 22:31:21 -06:00
Joe Cheng
a3b85b4e3e Add index entry for absolutePanel 2014-02-11 14:19:04 -08:00
Joe Cheng
e37a5d0394 Merge remote-tracking branch 'jcheng5/feature/absolute-panel'
Conflicts:
	NEWS
	man/validateCssUnit.Rd
2014-02-11 14:17:58 -08:00
Yihui Xie
e5a8e77e2a Merge pull request #364 from yihui/feature/mathjax
closes #25: a new function includeMathJax()
2014-02-11 16:06:48 -06:00
Yihui Xie
314b59798f a news item for includeMathjax() 2014-02-11 16:06:20 -06:00
Yihui Xie
e9ae16e534 closes #25: a new function includeMathJax() 2014-02-11 15:59:23 -06:00
Joe Cheng
c971ca0ce2 Merge pull request #388 from rstudio/feature/example-display-mode
Allow display.mode to be provided for examples
2014-02-11 12:45:11 -08:00
Joe Cheng
0ad9a5f9c6 Merge pull request #298 from jcheng5/remove-catools
Remove caTools dependency
2014-02-11 12:40:09 -08:00
Joe Cheng
c31d91668a Suppress staticdocs test when run on a built package 2014-02-10 11:58:40 -08:00
Jonathan McPherson
f5c196d717 allow display.mode to be provided for examples 2014-02-10 10:40:34 -08:00
Joe Cheng
3b90eed89f Add check for staticdocs index correctness 2014-02-05 13:45:25 -08:00
Joe Cheng
9828c8b787 Minor doc tweaks
- Combine sliderInput and animationOptions topics
- Provide better staticdoc index descriptions

Sorry for the big diff, that's due to using a newer version of roxygen.
2014-02-05 13:23:57 -08:00
Winston Chang
b3e997134f Merge branch 'bugfix/381-sendOutputHiddenState' 2014-02-05 12:30:39 -06:00
Winston Chang
f560baa69b Add comments about debouncing 2014-02-05 12:30:25 -06:00
Winston Chang
8cf5f00c87 Remove console logging code 2014-02-05 12:12:38 -06:00
Winston Chang
482c3895d3 Udpate to jQuery 1.11.0
This makes the following JS console message (as noted in #271) go away:
event.returnValue is deprecated. Please use the standard event.preventDefault() instead.
2014-02-05 12:07:28 -06:00
Joe Cheng
fc0d4bde35 Staticdocs index info 2014-02-04 16:13:37 -08:00
Joe Cheng
33ed89a036 Merge pull request #363 from yihui/text-output-container
fixes #90: textOuput() can be put in any tag now
2014-02-03 16:44:08 -08:00
Joe Cheng
0a5953c104 Remove caTools dependency 2014-02-03 16:41:28 -08:00
Joe Cheng
77f6be1a8b Fix URL for slider number format details
Fixes issue #384
2014-02-03 16:37:22 -08:00
Yihui Xie
5bd3f9a571 just to avoid the spurious warning in R CMD check "doRenderTags: no visible binding for global variable 'htmlResult'" 2014-01-30 21:27:43 -06:00
Yihui Xie
ef59119663 Merge pull request #383 from jcheng5/bugfix/broken-image
Don't show broken image when plot is empty
2014-01-30 19:09:08 -08:00
Joe Cheng
45baca7018 Don't show broken image when plot is empty
Repro case:

shiny::runApp(list(
  ui=basicPage(plotOutput('foo')),
  server=function(input, output, session) {
    output$foo <- renderPlot({})
  }
))
2014-01-30 16:46:07 -08:00
Joe Cheng
9b1edb7a97 Fix issue #381: sendOutputHiddenState is called too many times 2014-01-30 10:18:43 -08:00
Yihui Xie
31c071d086 use values instead of names for radioButtons due to #340
the problem was revealed from #377
2014-01-27 22:55:37 -06:00
Joe Cheng
ecf4c5c104 Merge pull request #376 from wch/singleton
Add exports.renderHtml function
2014-01-22 14:35:23 -08:00
Winston Chang
35fbfece0d Export renderHtml function and un-export singletons object 2014-01-21 13:14:21 -06:00
Winston Chang
b7721e42d3 Fixes for jshint 2014-01-21 10:19:50 -06:00
Winston Chang
386346cee9 Add new object for handling singletons 2014-01-21 10:19:50 -06:00
Winston Chang
bbecccc45e Missing semicolon 2014-01-21 10:02:52 -06:00
Yihui Xie
1a8f84c134 and reactive({}) also works; closes #366 2014-01-17 22:41:00 -06:00
Yihui Xie
66181fdcdf reactive(NULL) works now 2014-01-17 22:40:13 -06:00
Winston Chang
b9c05e8a9c Bind both mouse and touch events for jslider
This brings in commit 468002a for jslider. It fixes the problem where
dragging the slider didn't work in the RStudio viewer pane.
2014-01-17 09:36:31 -06:00
Joe Cheng
9c22d6c12a Merge pull request #371 from trestletech/no-write-closed
Don't attempt to write if the connection is closed.
2014-01-16 16:41:23 -08:00
trestletech
f3cedbbd6f Don't attempt to write if the connection is closed. 2014-01-16 14:34:55 -06:00
Joe Cheng
3f3a660ca1 Merge pull request #369 from trestletech/input-handler-patch
Define a non-S3 input registry.
2014-01-16 11:37:28 -08:00
Joe Cheng
1c6ded8416 Merge pull request #370 from trestletech/flush-on-session-end
Flush react/output onSessionEnd
2014-01-16 11:13:16 -08:00
trestletech
aa63fdb26f Flush react/output onSessionEnd 2014-01-16 12:26:29 -06:00
trestletech
3932330ce6 Added tests, stop converting NULLs to NA. 2014-01-15 14:24:13 -06:00
trestletech
3b946b1c69 Removed deprecated function documentation. 2014-01-14 21:26:04 -06:00
trestletech
14df829f18 Update inputHandler docs, revamped tests. 2014-01-14 21:19:12 -06:00
trestletech
788d024be6 Define a non-S3 input registry.
Revises the approach taken in #233.

!! No longer casts NULLs in incoming JSON to NA.
2014-01-14 18:02:19 -06:00
Joe Cheng
c20b56e089 Merge pull request #367 from jcheng5/bugfix/dynamic-singleton
Better handling of dynamically generated singletons
2014-01-14 10:41:10 -08:00
Joe Cheng
287f4f239e Better handling of dynamically generated singletons
Ref:
https://groups.google.com/d/msg/shiny-discuss/cgSHsM1FCjY/vgU1-jmkGjkJ

The user reported that on a page with multiple uiOutputs whose corresponding
renderUI calls all returned sliderInputs (but no sliderInput was present in
ui.R), some but not all of the sliders were initialized correctly; the ones
that were not didn't receive the jquery-slider treatment and just looked like
text boxes.

This was caused by a fundamental flaw in our handling of singletons in
renderUI. The implicit assumption in the old renderUI code was that:

1) Any HTML we generate in renderUI would be rendered in the client
2) Given multiple calls to renderUI, the HTML we return will be rendered in
   the client in the order that we generated it

Both assumptions are incorrect. #1 would be incorrect in cases where an output
is rendered twice before flushOutput is called (this is possible when using an
observer to modify a reactive input, for example), and #2 is incorrect when
output is flushed with multiple values (very common, and exactly what was
happening to the user scenario linked above).

This commit fixes the problem by deferring singleton-handling for uiOutput to
the client. We don't assume that a singleton has been rendered until right
before we render it. The implementation uses a surroundSingletons function on
the server side to surround all singletons with <!--SHINY.SINGLETON[sig]-->
and <!--/SHINY.SINGLETON[sig]-->, which will then be parsed and removed in
the htmlOutputBinding in shiny.js. (And because singletons may contain <head>
elements, we also need to defer <head> hoisting to htmlOutputBinding as well.)

The context$filter mechanism previously used in tagWrite was not flexible
enough to handle this kind of singleton processing. The new rewriteTags
function does tag walking and rewriting much more robustly and flexibly than
context$filter, so I also refactored renderTags to use it instead.

One unrelated problem I noticed was that singleton only worked reliably on
tags, possibly on characters and definitely not on list() or tagList(). This
is because list flattening was happening at tag construction time, which
can cause singleton objects to be trampled. (Among other reasons, such as
context$filter not being called on list objects.) I changed tags.R to not do
any flattening or NULL dropping at tag construction time, but instead to do
it at the last minute during tagWrite.
2014-01-13 17:04:30 -08:00
Joe Cheng
dce66945ec Merge pull request #340 from yihui/bug/null-choices
fix the bug when choices=NULL in updateCheckboxGroupInput()
2014-01-11 13:43:05 -08:00
Yihui Xie
92bd1d5200 Merge pull request #360 from jcheng5/bugfix/359-factors-as-numbers
Fix issue #359: Factors in HTML attributes are being converted to their numeric, not character, equivalent
2014-01-10 19:30:33 -08:00
Yihui Xie
06d2df8211 fixes #90: textOuput() can be put in any tag now
for the example, we use <span>, which is allowed in <h3>
2014-01-10 20:54:28 -06:00
Joe Cheng
36256856b5 Fix issue #359: Factors in HTML attributes are being converted to their numeric, not character, equivalent
This bug was introduced in 3fc1410. Essentially it boils down to the difference between stringifying a factor, and stringifying a list containing a factor:

> as.character(factor('a'))
[1] "a"
> as.character(list(factor('a')))
[1] "1"
The call to split that was introduced in this commit ends up generating lists of factors, not vectors of them.

The best fix I could find was to convert all the attribute values to character before doing the split.
2014-01-10 01:48:02 -08:00
Joe Cheng
a771ae853c Remove extraneous library calls 2014-01-09 10:44:50 -08:00
Yihui Xie
ef4e10bbb1 Merge pull request #355 from jcheng5/bugfix/null-children
Ignore NULL tag elements
2014-01-08 18:51:20 -08:00
Yihui Xie
0dbe4d936e cosmetic changes, just because Github is not aware of my new commits for the PR after it was down 2014-01-08 16:23:23 -06:00
Yihui Xie
731fee11d4 a missing space 2014-01-08 15:54:05 -06:00
Yihui Xie
6759df52c3 show the log files after R CMD check failures 2014-01-08 15:18:01 -06:00
Yihui Xie
914b997076 need to run roxygen2 2014-01-08 15:18:01 -06:00
Yihui Xie
0b8a2fea72 and a missing ) 2014-01-08 14:47:40 -06:00
Yihui Xie
fb2538135c an extra ) 2014-01-08 14:46:57 -06:00
Yihui Xie
b4c547c278 move assignments out, per suggestion of @hadley 2014-01-08 14:22:38 -06:00
Yihui Xie
b243bc846b also include input ID in the warning message, per suggestion of @jcheng5 2014-01-08 14:21:41 -06:00
Joe Cheng
6b8f6162b6 Unify jqueryui copies 2014-01-08 09:41:38 -08:00
Joe Cheng
158db1532b Merge remote-tracking branch 'origin/master' into feature/absolute-panel
Conflicts:
	DESCRIPTION
2014-01-08 09:32:14 -08:00
Joe Cheng
99c3c2fc80 Ignore NULL tag elements
We already do the right thing for NULL tag attributes
2014-01-07 15:59:42 -08:00
Yihui Xie
a86fc96730 yes, it is weird to select by names instead of values; now the weirdness has gone 2013-12-31 17:03:43 -06:00
Yihui Xie
cf51af17fd news for #340 2013-12-31 16:50:07 -06:00
Yihui Xie
8c1b6a5cf0 update JS tests accordingly
note that getState() no longer includes state.options.selected/checked, which is actually redundant since there is state.value
2013-12-31 16:49:48 -06:00
Yihui Xie
bcecb8cd76 roxygenize 2013-12-31 16:48:17 -06:00
Yihui Xie
557790b0e5 selected refers to values instead of labels/names of choices
added validateSelected() for backward compatibility
2013-12-31 16:48:04 -06:00
Yihui Xie
8eb5a45718 per suggestion of @wch, we do not pass the selected/checked status in the message; the selected values are in message$value, and they will be set via this.setValue(el, data.value) in receiveMessage() in shiny.js
also note that updateSelectInput() is essentially the same as updateCheckboxGroupInput() now, because it does not matter if the attribute name is 'selected' or 'checked'
2013-12-31 15:06:17 -06:00
Yihui Xie
7b64cef73b fixes #176: the problem of choices=NULL also affects selectInput() 2013-12-31 15:06:17 -06:00
Yihui Xie
106203170e pass the selected value(s) to checkbox/radio group 2013-12-31 15:06:17 -06:00
Yihui Xie
174d2bfc11 only do mapply() when length(choices) > 0, otherwise options = list(), and this will lead to an empty checkbox/radio group 2013-12-31 15:06:17 -06:00
Yihui Xie
abda9c7f97 it is not possible to do names(NULL) <- value, so return early when choices is NULL
https://groups.google.com/forum/?pli=1#!topic/shiny-discuss/K7chwrMCvkU
2013-12-31 15:06:17 -06:00
Joe Cheng
6f627fca96 Minor doc tweak 2013-12-21 11:01:10 -08:00
Joe Cheng
339fbc482b Smarter cursor choosing for absolutePanel 2013-12-21 10:53:23 -08:00
Joe Cheng
a9750fb088 Add absolutePanel and fixedPanel (and jquery-ui)
jQueryUI is needed for draggable functionality.
2013-12-20 14:53:11 -08:00
222 changed files with 27473 additions and 11600 deletions

View File

@@ -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
View File

@@ -0,0 +1,2 @@
^tools$
^Rmd$

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
/NEWS merge=union

View File

@@ -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

View File

@@ -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'

View File

@@ -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
View File

@@ -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
View 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
}

View File

@@ -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)
))
}

File diff suppressed because it is too large Load Diff

View File

@@ -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)
}
}

View File

@@ -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
View 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())
}

View File

@@ -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()

View File

@@ -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
View 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
View 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

View File

@@ -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
View 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
View File

@@ -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
View 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
View 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.

View File

@@ -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

View File

@@ -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
View 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.
# ```

View File

@@ -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
})
}

View File

@@ -1,114 +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 \code{"auto"},
#' which uses the \code{shiny.launch.browser} preference if present, and
#' invokes the browser specified by \code{shiny.browser} if specified.
#'
#' @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="auto") {
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 \code{"auto"},
#' which uses the \code{shiny.launch.browser} preference if present, and
#' invokes the browser specified by \code{shiny.browser} if specified.
#'
#' @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 = "auto") {
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 \code{"auto"},
#' which uses the \code{shiny.launch.browser} preference if present, and
#' invokes the browser specified by \code{shiny.browser} if specified.
#'
#' @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')
@@ -117,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 = "auto") {
runUrl <- function(url, filetype = NULL, subdir = NULL, ...) {
if (!is.null(subdir) && ".." %in% strsplit(subdir, '/')[[1]])
stop("'..' not allowed in subdir")
@@ -139,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))
@@ -150,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
View 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)
}
}

View File

@@ -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
}

1438
R/shiny.R

File diff suppressed because it is too large Load Diff

View File

@@ -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)))
}
}

View File

@@ -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')
)
})
}

View File

@@ -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()))

View File

@@ -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
View File

@@ -1,449 +0,0 @@
htmlEscape <- local({
.htmlSpecials <- list(
`&` = '&amp;',
`<` = '&lt;',
`>` = '&gt;'
)
.htmlSpecialsPattern <- paste(names(.htmlSpecials), collapse='|')
.htmlSpecialsAttrib <- c(
.htmlSpecials,
`'` = '&#39;',
`"` = '&quot;',
`\r` = '&#13;',
`\n` = '&#10;'
)
.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))
}
}

View File

@@ -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))

View File

@@ -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...?

View File

@@ -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
View File

@@ -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))
}

View File

@@ -1,6 +1,6 @@
# Shiny
[![Build Status](https://travis-ci.org/rstudio/shiny.png)](https://travis-ci.org/rstudio/shiny)
[![Build Status](https://travis-ci.org/rstudio/shiny.svg?branch=master)](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.

View File

@@ -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
----------------------------------------------------------------------

View File

@@ -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

View File

@@ -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.

View File

@@ -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')
})
})

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"),

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View 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

View 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.

View File

@@ -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

View 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.

View File

@@ -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')

View File

@@ -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

View 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.

View File

@@ -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

View 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
View 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"
)
)

View File

@@ -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);

View File

@@ -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>&lt;A&gt;</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)
))
})

View 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))
)
})

View File

@@ -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))
)
})

View File

@@ -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)
})

View 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 = ""))
})

View File

@@ -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>&lt;a&amp;b&gt;</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="&lt;a&amp;b&gt;">text</div>')
# HTML() has no effect in a property like 'class'
expect_equivalent(format(div(class = HTML("<a&b>"), "text")),
'<div class="&lt;a&amp;b&gt;">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
View 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))
})

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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>

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

950
inst/www/shared/jqueryui/1.10.4/jquery-ui.css vendored Executable file
View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

22
inst/www/shared/json2-min.js vendored Normal file
View 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")})}()

View 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;
}

View 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)}});

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More