Compare commits

..

721 Commits

Author SHA1 Message Date
Joe Cheng
53c05128b3 Firefox compatibility; visual tweaks 2013-07-06 18:14:30 -07:00
Joe Cheng
99013f7998 Launch reactlog from Shiny app with F3 2013-07-03 12:17:36 -07:00
Joe Cheng
fc396800db reactlog: Automatically run first step 2013-07-03 08:56:28 -07:00
Joe Cheng
6d03ae57ac reactlog: Show value changes 2013-07-03 00:05:28 -07:00
Joe Cheng
4a0aa57355 reactlog node shapes, visible labels 2013-07-02 17:48:09 -07:00
Joe Cheng
7db737494c Reverse reactlog arrow orientation 2013-07-02 13:21:39 -07:00
Joe Cheng
b285501c44 reactlog code cleanup 2013-07-02 08:38:24 -07:00
Joe Cheng
2f9b29994f Add showReactLog function 2013-07-02 03:17:30 -07:00
Joe Cheng
917434cb6b Introduce shiny.reactlog function 2013-07-02 03:03:29 -07:00
Joe Cheng
28a52bb658 More visual improvements to reactlog 2013-07-02 02:59:37 -07:00
Joe Cheng
82bc19374c Improve appearance of reactlog 2013-07-02 02:12:07 -07:00
Joe Cheng
0b23f30bb7 Work in progress 2013-07-02 01:29:33 -07:00
Joe Cheng
64a62d7aed Add doc for workerId param 2013-06-28 13:55:45 -07:00
Joe Cheng
de31cf8e7d Merge pull request #175 from trestletech/master
Add workerID to upload, download, and file URLs.
2013-06-28 13:52:38 -07:00
Winston Chang
3484f9afb3 Merge pull request #179 from wch/faster-tags
Faster tags
2013-06-25 09:57:34 -07:00
Winston Chang
81df0ff390 Fix typo 2013-06-25 11:40:54 -05:00
Winston Chang
4268570166 Add tests for escaping in tags 2013-06-20 14:13:11 -05:00
Winston Chang
ead508c0d0 Preserve attributes in child tags 2013-06-20 12:16:51 -05:00
Joe Cheng
f8e1be8565 Update shiny-package 2013-06-19 13:09:48 -07:00
Winston Chang
360f1af32f Export tagSetChildren and tagAppendChildren 2013-06-19 15:01:45 -05:00
Winston Chang
ba4f3a1553 Speed up input update functions 2013-06-19 00:34:57 -05:00
Winston Chang
6ba9534da4 In tag functions, drop NULL attributes 2013-06-19 00:25:46 -05:00
Winston Chang
c16ef96754 Don't use named list items for selectInput and radioButtons
The names aren't used anyway, and this matches previous behavior (Shiny 0.6.0)
2013-06-18 23:53:30 -05:00
Winston Chang
e728491aa2 Refactor checkboxGroupInput 2013-06-18 23:52:41 -05:00
Winston Chang
ce356fa266 Fix handling of empty tags 2013-06-18 23:52:13 -05:00
Winston Chang
5e46323ca3 Refactor tag()
This is much faster when there are large lists of children (and the code is
much simpler!)
2013-06-18 23:33:28 -05:00
Winston Chang
0a7d047246 Add tests for creating nested tags 2013-06-18 22:40:17 -05:00
Winston Chang
3fa534a3eb Add tests for adding children 2013-06-18 22:39:34 -05:00
Winston Chang
c6405f70d3 Add tagSetChildren() and tagAppendChildren() 2013-06-18 20:12:04 -05:00
Joe Cheng
acae6c2c49 Expose session$input and session$output
This makes it possible for packaged Shiny components to only ask
for the session variable to get access to all inputs and outputs
(along with the other good stuff on session).
2013-06-18 17:08:48 -07:00
Joe Cheng
141fdc2197 Do away with dependsOnFile error
This error is causing more problems than it solves.
2013-06-18 17:07:42 -07:00
Joe Cheng
a7ed8a006f includeText should be HTML escaped 2013-06-18 17:07:08 -07:00
Joe Cheng
b1a0ebd531 Add includeCSS and includeScript functions 2013-06-18 17:06:34 -07:00
Winston Chang
e8021acccd Speed up radioButtons when there are many choices 2013-06-17 23:49:50 -05:00
Winston Chang
39b0da2a3f Speed up selectInput when there are many choices 2013-06-17 23:44:13 -05:00
Joe Cheng
fd3d18f6c5 Add stopApp function, for returning a value from runApp 2013-06-14 22:27:58 -07:00
trestletech
ecc27d1674 Incorporated a worker ID specification.
Accept this as a parameter from the runApp() function and pass it through into the shinysession object so that it can be used in file uploads, downloads, and HTTP image fallbacks on non-websocket browsers.
2013-06-13 21:34:10 -07:00
Joe Cheng
7d0514ab36 Merge pull request #172 from wch/cairo-option
Add option for not using Cairo
2013-06-12 10:10:14 -07:00
Winston Chang
44c3024c00 Add option for not using Cairo 2013-06-12 10:56:21 -05:00
Winston Chang
253c92bab7 Bump version to 0.6.0.99 for development 2013-06-12 10:53:51 -05:00
Joe Cheng
c10850118d Merge pull request #170 from hadley/master
Fix typo
2013-06-11 13:26:14 -07:00
Joe Cheng
4f017e9173 Remove annoying title="foo" tooltip on all tabset tabs 2013-06-11 09:19:49 -07:00
Joe Cheng
5ed46c82cb Document observer methods 2013-06-11 09:18:57 -07:00
hadley wickham
64391e906d Update reactives.R
Add newline (guessing that's how it's supposed to be)
2013-06-11 10:37:02 -05:00
Joe Cheng
47b4ee07ab Merge pull request #165 from trestletech/master
Export validateCSSUnit function
2013-06-10 11:32:01 -07:00
trestletech
3000cbf763 Reorder namespace using latest roxygen2 code. 2013-06-07 16:33:19 -05:00
trestletech
76b3d314a8 Exported validateCSSUnit function. 2013-06-05 15:15:54 -05:00
Winston Chang
ba646de0ad Bump version to 0.6.0 2013-06-05 12:12:46 -05:00
Winston Chang
395f746a05 Update NEWS 2013-06-04 22:20:07 -05:00
Winston Chang
f7e57cd398 Update NEWS 2013-06-04 21:21:26 -05:00
Winston Chang
3ea6d97ed2 Documentation updates 2013-06-04 21:21:26 -05:00
Winston Chang
affc0d8b67 Remove sendJavascript 2013-05-31 10:48:39 -05:00
Joe Cheng
c637e310e9 Merge pull request #164 from jcheng5/suspend-bugfix
Fix several bugs relating to suspendWhenHidden
2013-05-28 08:58:52 -07:00
Joe Cheng
6ee7dcdd51 Fix several bugs relating to suspendWhenHidden
- If an output is bound in the UI before it exists on the server, the
  server will suspend the new output until something else causes
  manageHiddenOutputs to be triggered. The refactor in shiny.R sets
  the suspended flag to .shouldSuspend.
- As part of the previous refactor, we don't set suspendOnHidden in
  defineOutput; instead we leave it empty and .shouldSuspend knows
  what the default should be.
- In shiny.js, unbound controls were not being suspended. This was
  fixed by having sendOutputHiddenState be stateful; anything in
  a visible state that disappears on the next iteration is marked
  as hidden. Also unbindAll now calls sendOutputHiddenState.
2013-05-27 12:52:33 -07:00
Joe Cheng
23470267fe Merge pull request #163 from jcheng5/app-object
Run apps without creating files on disk
2013-05-24 22:07:36 -07:00
Winston Chang
4a92bb91df Initialize slider at correct time 2013-05-24 18:58:47 -05:00
Joe Cheng
69522c422c Fix rendering issues when slider gets too wide
Synced to jslider commit (from rstudio/jslider fork):
6de6ef7b2c788cbcec74dd51e0008e12247e6638
2013-05-23 12:02:43 -07:00
Joe Cheng
bc5e3524eb Export basicPage 2013-05-23 11:56:55 -07:00
Joe Cheng
479297fc35 Add basicPage function; like bootstrapPage with padding 2013-05-23 11:23:28 -07:00
Joe Cheng
516feafcfb Run apps without creating files on disk
With this commit, runApp can now accept a list instead of a path.
This list should have elements named "ui" and "server" that contain
what would normally go in shinyUI and shinyServer, respectively.
(Note that there is no equivalent to global.R, nor should there
need to be since you can just directly execute in the global env
before calling runApp.)

Example:

runApp(list(
  ui = bootstrapPage(
    numericInput('n', 'Number of obs', 100),
    plotOutput('plot')
  ),
  server = function(input, output) {
    output$plot <- renderPlot({ hist(runif(input$n)) })
  }
))
2013-05-23 10:00:38 -07:00
Joe Cheng
a135c82ab5 Merge pull request #160 from wch/reactive-timer
reactiveTimer: don't invalidate when session closed
2013-05-15 14:08:56 -07:00
Winston Chang
10996f1cbd reactiveTimer: don't invalidate when session closed 2013-05-15 15:50:37 -05:00
Joe Cheng
23b060e1f5 Merge pull request #159 from wch/session-invalidate
invalidateLater: don't invalidate when session closed
2013-05-15 11:32:09 -07:00
Winston Chang
622ff3a256 invalidateLater: don't invalidate when session closed 2013-05-15 12:41:07 -05:00
Winston Chang
5d457b6834 Bump httpuv version dependency 2013-05-15 12:09:21 -05:00
Winston Chang
f10f76d127 Fixes for R CMD check 2013-05-13 10:54:54 -05:00
Winston Chang
58f3382daf Update NEWS 2013-05-10 14:09:51 -05:00
Winston Chang
0e1139446e Merge pull request #149 from jcheng5/observer-priorities
Add priority levels to observers
2013-05-10 12:03:28 -07:00
Winston Chang
f433216fae Merge pull request #156 from jcheng5/fix-submit-button
Fix submit button interactions with tabs, plot sizes
2013-05-10 12:02:14 -07:00
Winston Chang
ed680baaac selectInput: correctly handle choices with duplicate names. Fixes #157 2013-05-08 18:32:05 -05:00
Winston Chang
e0a9d908ed Update NEWS 2013-05-06 11:14:42 -05:00
Winston Chang
bfa4a46bd5 Merge pull request #154 from wch/tag
Add withTags function
2013-05-06 08:46:09 -07:00
Winston Chang
03f3ff991e Update NEWS 2013-05-06 10:45:41 -05:00
Winston Chang
619b4824f0 Add tests for withTags 2013-05-06 10:45:41 -05:00
Winston Chang
021af0186b Add withTags function 2013-05-06 10:45:41 -05:00
Winston Chang
d3caad8b8d Add comment in test 2013-05-01 01:17:07 -04:00
Winston Chang
ec6bec3326 Fix selected items in checkboxGroupInput 2013-05-01 01:12:03 -04:00
Joe Cheng
dd54740d36 Fix submit button interactions with tabs, plot sizes 2013-04-29 13:08:04 -07:00
Winston Chang
8f65156bda Add Makefile for building web assets 2013-04-24 12:29:46 -05:00
Winston Chang
96c7df5afa Documentation fixes 2013-04-24 12:27:35 -05:00
Winston Chang
0c19105fbf Add dateInput and dateRangeInput
Also:
* add initialize() method for input bindings
* cleanups for JShint
2013-04-24 12:18:20 -05:00
Joe Cheng
4145d83248 Merge remote-tracking branch 'jcheng5/session-close-callbacks'
Conflicts:
	R/shiny.R
2013-04-23 15:40:38 -07:00
Winston Chang
6490705e2a Clarification of tagList help 2013-04-23 12:06:22 -05:00
Joe Cheng
10d2432df5 Merge pull request #148 from wch/sendmessage
Add support for sending arbitrary messages to client
2013-04-22 16:40:01 -07:00
Joe Cheng
815db72671 Add callback for session end 2013-04-22 16:29:40 -07:00
Joe Cheng
6d0ba61c54 Add priority to outputOption 2013-04-19 15:48:56 -07:00
Joe Cheng
5f61267f75 Allow integers or numerics for priority 2013-04-19 15:42:18 -07:00
Joe Cheng
94ee42cebb Add priority levels to observers
Observers can now take priority levels, which allow the user to control
the order of execution. Note that reactive expressions do not have
priority levels; since they are lazily evaluated, it wouldn't make any
sense to speak of priorities.

Another commit will be needed to add an API for changing the priorities
of outputs (probably in outputOptions?).
2013-04-19 10:34:34 -07:00
Winston Chang
b6795e5c63 Move actionbutton into Shiny proper 2013-04-18 15:11:13 -05:00
Winston Chang
ef85d063c2 Make external message handlers use 'custom' 2013-04-18 13:40:32 -05:00
Winston Chang
59755971e5 Add message handlers 2013-04-18 12:40:19 -05:00
Winston Chang
c5ab831a87 Fixes for jshint
Conflicts:
	inst/www/shared/shiny.js
2013-04-18 11:41:52 -05:00
Winston Chang
6715dc2a5d Merge pull request #139 from wch/set-input
Add ability to set inputs from server
2013-04-17 14:58:14 -07:00
Winston Chang
af6de64ec0 Add clientData to session object 2013-04-10 13:02:48 -05:00
Winston Chang
1ac2448f90 Split sendMessage into type-specific functions 2013-04-10 12:30:58 -05:00
Winston Chang
b5f34b30d3 Add documentation for input updater functions 2013-04-10 12:13:53 -05:00
Winston Chang
01f4e080df Workaround for Cairo resolution bug 2013-04-09 17:13:26 -05:00
Winston Chang
d55335e70b Make updateFooInput functions behave like fooInput functions 2013-04-08 18:38:16 -05:00
Winston Chang
a8c1dc4bc6 Add dropNulls function 2013-04-08 18:13:26 -05:00
Winston Chang
2897059503 Allow setting labels for all input objects 2013-04-08 18:09:40 -05:00
Winston Chang
d491f9df5a updateCheckboxGroupInput: clean up and allow setting labels 2013-04-08 17:01:00 -05:00
Winston Chang
bc40318e40 Wrap slider in div so that it can be manipulated more easily 2013-04-08 16:01:31 -05:00
Joe Cheng
3935434f04 Merge branch 'domain-sockets' 2013-04-06 20:44:40 -07:00
Joe Cheng
4cf1f2de94 Fix bug where multiple file uploads get same path
https://groups.google.com/d/msg/shiny-discuss/Km_pQL87paM/4V0AiIoTgwIJ

The problem is that length(.files) gives the number of columns in the
data frame, not the number of rows. So it's always either 0 or 4.
2013-04-06 18:14:21 -07:00
Winston Chang
73156c6780 Add update-input functions 2013-04-06 09:32:01 -05:00
Winston Chang
bc52bafa8d Add methods for setting bootstrapTabInputBinding 2013-04-06 01:00:18 -05:00
Winston Chang
5c9007b242 Streamline tests 2013-04-06 00:28:26 -05:00
Winston Chang
5857e3f75e Allow setting value of sliders when they have range 2013-04-05 22:56:12 -05:00
Winston Chang
e202831013 Add empty message tests 2013-04-05 21:57:45 -05:00
Winston Chang
4cbbfccb6d Make sure not to bind new-style multi-inputs 2013-04-05 21:57:45 -05:00
Winston Chang
21f3b1cf34 Add checkboxGroupInputBinding 2013-04-05 21:57:45 -05:00
Winston Chang
f7b384e9b6 Add radioInputBinding 2013-04-05 21:57:45 -05:00
Winston Chang
1e6ab47ee4 Fixes to tests for jshint 2013-04-05 21:57:45 -05:00
Winston Chang
78341ea2f1 Trigger change when message sent to input bindings 2013-04-05 21:57:45 -05:00
Winston Chang
3f8a4d4273 Add getState and receiveMessage support to selectInputBinding 2013-04-05 21:57:45 -05:00
Winston Chang
bae517c9f8 Align checkboxes with labels 2013-04-05 21:57:45 -05:00
Winston Chang
c88ccbf9bc Put inputMessage in a queue 2013-04-05 21:57:45 -05:00
Joe Cheng
5e40f5d509 Allow listening on domain sockets 2013-04-02 15:24:24 -07:00
Winston Chang
46389131bc Add functions for sending messages to client 2013-03-30 21:07:22 -05:00
Joe Cheng
c6a344d0d9 Add selected argument to tabsetPanel 2013-03-29 23:14:01 -07:00
Winston Chang
bcc2c377a0 Add messenge-sending support 2013-03-29 14:32:34 -05:00
Winston Chang
bb6afc847e Add input binding tests 2013-03-29 14:30:22 -05:00
Winston Chang
e0193151db shiny.js: allow setting inputs and getting input state 2013-03-28 16:15:24 -05:00
Winston Chang
42a80bad8e Undo license change 2013-03-28 10:45:26 -05:00
Joe Cheng
6e3e77f65d Code review feedback for version checking stuff 2013-03-25 11:26:16 -07:00
Winston Chang
e155f022a0 Bump version to 0.5.0 2013-03-22 22:49:55 -05:00
Joe Cheng
db65aab347 Give warning message on obsolete Shiny Server version 2013-03-22 14:33:44 -07:00
Winston Chang
a180c5f357 renderPlot: just return if width or height is NULL 2013-03-22 12:52:43 -05:00
Winston Chang
1c0279f17c Undo commit 'Remove redundant code in shiny.js'
The code wasn't actually redundant.
2013-03-22 12:51:29 -05:00
Winston Chang
8866eb292b Add section breaks in shiny.js 2013-03-21 21:09:16 -05:00
Winston Chang
6fdda3391e Send initial value of URL hash 2013-03-20 16:53:40 -05:00
Winston Chang
fdb8dd4e5b Remove redundant code in shiny.js 2013-03-20 13:22:21 -05:00
Joe Cheng
9a1d3783ee Unbind controls in htmlOutput before displaying error 2013-03-19 13:32:14 -07:00
Winston Chang
3841d9e322 Update license information 2013-03-18 15:33:12 -07:00
Winston Chang
e392eadf8a Update NEWS 2013-03-18 11:22:27 -07:00
Winston Chang
f743d5d0b5 New method for detecting hidden outputs 2013-03-17 11:08:28 -07:00
Winston Chang
4a76bf59ef Fix access to .clientData 2013-03-17 09:54:18 -07:00
Winston Chang
205b29e2f5 Update NEWS 2013-03-15 16:44:42 -05:00
Winston Chang
d511b82264 Add imageOutput function 2013-03-15 16:44:18 -05:00
Winston Chang
aaae112e60 Add to on.exit() instead of replace 2013-03-15 16:26:05 -05:00
Winston Chang
955fd6207f Change license to GPL>=2 2013-03-15 12:32:05 -05:00
Joe Cheng
4e56c96612 Fix allowDataUriScheme 2013-03-13 11:13:26 -07:00
Joe Cheng
dd046f3442 Merge remote-tracking branch 'jcheng5/master'
Conflicts:
	R/shiny.R
2013-03-13 10:47:06 -07:00
Winston Chang
5a947f83a1 Separate private and public fields for input and clientData 2013-03-12 21:41:38 -05:00
Winston Chang
b87b8b54fd Update NEWS 2013-03-12 16:14:28 -05:00
Winston Chang
233c0537a1 Merge pull request #122 from wch/url
Send URL components in clientData
2013-03-12 13:42:05 -07:00
Winston Chang
63d4798a50 Add tests for parseQueryString 2013-03-12 14:32:52 -05:00
Winston Chang
6c47517684 Move allowDataUriScheme into .clientdata 2013-03-12 14:24:48 -05:00
Winston Chang
c58b1a0143 Add parseQueryString function 2013-03-12 14:24:48 -05:00
Joe Cheng
f489d9131b File uploads failed when no content type was provided
The simple fix for this would've been to just guess the content
type on the server (or use empty string or something), but by
doing the fix this way we're also set up to handle uploads by
servers that don't allow custom headers on AJAX calls.
2013-03-12 09:50:01 -07:00
Winston Chang
f0109c5588 Send URL in clientdata 2013-03-11 18:44:58 -05:00
Winston Chang
c16becba56 renderTable: check for empty data frame. Fixes #55 2013-03-11 18:43:57 -05:00
Winston Chang
4605788696 Add informative comments 2013-03-11 18:43:03 -05:00
Joe Cheng
87908313cc Merge remote-tracking branch 'jcheng5/httpuv'
Conflicts:
	R/shiny.R
2013-03-11 09:36:47 -07:00
Winston Chang
9cc2eba7b8 Merge pull request #109 from wch/retina
Add clientdata channel for sending data

This branch includes support for Retina-resolution displays.
2013-03-11 07:45:24 -07:00
Winston Chang
2459cee57b Add renderImage function 2013-03-11 09:39:39 -05:00
Winston Chang
0bf6ce57ed renderPlot: send height and width along with image 2013-03-11 09:39:38 -05:00
Winston Chang
7041424f96 Add plotPNG function 2013-03-07 17:12:24 -06:00
Winston Chang
9509285c16 Rename ShinySession to saveFile 2013-03-07 17:12:24 -06:00
Winston Chang
e55ee0e65d Create ShinySession$sendFile() and use from renderPlot() 2013-03-07 17:12:18 -06:00
Winston Chang
9ea70497c2 Bump version to 0.4.1.99 for development 2013-03-06 17:26:35 -06:00
Joe Cheng
3389b9e9fd Memory-efficient file downloads 2013-03-05 23:06:13 -08:00
Winston Chang
76d4d54639 Allow shinyServer() to take clientData argument 2013-03-05 19:32:10 -06:00
Winston Chang
1b692b6c37 Rename shinyapp to shinysession, and .shinyout_xx to .clientdata_output_xx 2013-03-05 19:07:36 -06:00
Winston Chang
40d8cef1a2 Rename .metadata to clientData 2013-03-05 16:21:02 -06:00
Winston Chang
23550c0062 Add manageInputs() to handle metadata and normal inputs 2013-03-05 16:07:28 -06:00
Winston Chang
949bd940ee Partial change to reactiveValues 2013-03-04 20:33:29 -06:00
Winston Chang
79bdb9eed5 Add shiny metadata channel and send pixel ratio
This adds support for retina-resolution displays
2013-03-04 20:33:29 -06:00
Winston Chang
a141f08298 Bump version to 0.4.1 2013-03-04 20:27:48 -06:00
Joe Cheng
dee43a3911 Don't animate when showing file upload error 2013-03-04 14:47:26 -08:00
Joe Cheng
ef227d0139 More memory-efficient file uploading 2013-03-04 11:14:39 -08:00
Winston Chang
cbcf9ce645 reactivePlot: fix infinite recursion when height/width is function 2013-03-04 11:20:14 -06:00
Joe Cheng
0e5af2b16c Check for excessively large uploads before they begin
The onHeaders callback is supposed to be able to stop large uploads before
they begin, but do not appear to be having the desired effect. The browsers
continue uploading until completion, before noticing the response. To work
around this for now, upload the sizes explicitly when the job begins and
let Shiny pre-emptively reject the whole thing. This is also beneficial
in cases where multiple files are being uploaded and not all of them
exceed the maximum upload size.
2013-03-01 19:31:18 -08:00
Joe Cheng
85ca3a3b27 Update upload docs 2013-03-01 15:54:19 -08:00
Joe Cheng
fc5f5f3b6c Don't initiate file upload if no files were chosen 2013-02-28 21:14:06 -08:00
Joe Cheng
716fd8c0b9 File upload improvements
- Add "shiny.maxRequestSize" option
- Show upload progress
2013-02-28 21:06:06 -08:00
Joe Cheng
a517393c43 Remove dead upload code 2013-02-28 07:46:31 -08:00
Joe Cheng
c2311faffe httpuv-style file uploading
Use HTTP POST to upload files rather than sending 4K chunks
one at a time over the websocket. This is massively faster and
also means no binary websocket support is needed. In theory
this approach should be compatible with Shiny Server.

Currently the client side code still uses File API which means
IE8 and 9 users are out of luck.
2013-02-27 16:47:18 -08:00
Joe Cheng
fe453b0d66 Restore filter functionality 2013-02-26 16:59:56 -08:00
Joe Cheng
7e75b0fc02 eventloop package renamed to httpuv 2013-02-26 16:27:26 -08:00
Joe Cheng
11b0a0a73d Conform to API changes in eventloop package 2013-02-25 20:06:21 -08:00
Joe Cheng
82fdb5c3eb Greatly improve responsiveness of interruption on Windows 2013-02-25 15:14:24 -08:00
Joe Cheng
3f1d532c8b Restore startApp/serviceApp division of labor 2013-02-25 15:14:23 -08:00
Joe Cheng
f258b00aa7 Initial implementation on eventloop
Timers don't work yet
2013-02-25 15:13:30 -08:00
Winston Chang
4e71b9576d Update NEWS 2013-02-25 15:11:24 -06:00
Winston Chang
f36567a5cd reactivePlot: correctly pass width and height to renderPlot 2013-02-25 12:35:39 -06:00
Winston Chang
924ebb6c7f Bump version to 0.4.0.99 for development 2013-02-22 15:30:04 -06:00
Winston Chang
6e7e8eb44a Bump version to 0.4.0 2013-02-21 16:50:01 -06:00
Winston Chang
308c583254 setRatePolicy based on effectiveId. Fixes #110
Previously when getType() was defined for a type of object, shiny.js would
send updates immediately instead of applying the rate policy.
2013-02-20 11:39:22 -06:00
Winston Chang
97b2f7e5ca Fix call to manageHiddenOutputs in timer callback. Fixes #112 2013-02-19 12:20:49 -06:00
Winston Chang
3ea88a07d9 sliderInputBinding inherits from text instead of number. Fixes #110 2013-02-18 22:25:38 -06:00
Winston Chang
588f8bb96a Merge pull request #107 from wch/numeric-na
Empty numericInput gets converted to NA
2013-02-18 14:01:04 -08:00
Winston Chang
c93c0dd721 Update NEWS 2013-02-18 16:00:25 -06:00
Winston Chang
fc59c254fd Merge pull request #108 from wch/unused-hidden
Treat unused outputs as hidden
2013-02-18 13:56:49 -08:00
Winston Chang
2f8b6a150f Treat unused outputs as hidden 2013-02-18 15:53:31 -06:00
Winston Chang
db60ac5c17 Empty numericInput gets converted to NA 2013-02-18 15:11:41 -06:00
Winston Chang
e1f09853c5 Make shiny.deprecation.messages option actually work 2013-02-17 16:17:41 -06:00
Winston Chang
24656713a5 Remove unnecessary function() in renderXX 2013-02-17 12:02:00 -06:00
Winston Chang
7dd0269292 Update NEWS 2013-02-14 14:09:42 -06:00
Winston Chang
8b87cea7aa Merge pull request #104 from wch/reactive-exp
Change reactive() and observe() to take expressions
2013-02-14 12:08:18 -08:00
Winston Chang
c7559a6946 Suspend overwritten output objects 2013-02-14 12:14:08 -06:00
Winston Chang
945c6080ad Export exprToFunction 2013-02-14 11:48:01 -06:00
Winston Chang
44590965d1 Add renderXX Rd files 2013-02-14 11:48:01 -06:00
Winston Chang
7ab64d678f reactivePlot: call height and width properly 2013-02-14 11:48:01 -06:00
Winston Chang
e406a76b62 Update documentation for renderXX 2013-02-14 11:48:01 -06:00
Winston Chang
e26f175a8f Change reactiveXX to renderXX 2013-02-13 12:11:39 -06:00
Winston Chang
d4ab84745d Make function for expr-to-function conversion 2013-02-12 15:55:51 -06:00
Winston Chang
32dbc3101e Add shinyDeprecated function 2013-02-12 15:24:50 -06:00
Winston Chang
0a924eb718 Fix deprecation message for observe() 2013-02-12 15:24:50 -06:00
Winston Chang
a284327bfc Re-roxygenize 2013-02-12 15:24:50 -06:00
Winston Chang
2ea38d6ecc Clean up instances of reactive() and observe() 2013-02-12 15:24:50 -06:00
Winston Chang
6a34bbfddd Add label argument to reactive and observe 2013-02-12 15:24:50 -06:00
Winston Chang
58323ada4b Change references of reactive 'functions' to 'expressions' 2013-02-12 15:24:49 -06:00
Winston Chang
5fd723cb80 reactive() and observe() now take expressions 2013-02-12 15:24:49 -06:00
Winston Chang
5c626e6957 Documentation fixes 2013-02-12 15:24:39 -06:00
Winston Chang
5d949842eb Add garbage collection tests 2013-02-11 20:26:23 -06:00
Winston Chang
b595c17d78 observe: add option to start suspended 2013-02-11 19:48:22 -06:00
Winston Chang
b84973ba2b Remove leftover testing string 2013-02-11 19:36:06 -06:00
Winston Chang
61be49e7b2 Merge pull request #97 from wch/suspend-hidden
Suspend hidden outputs. Fixes #24
2013-02-11 16:48:39 -08:00
Winston Chang
8faf5659ee Re-roxygenize 2013-02-11 18:47:53 -06:00
Winston Chang
cc9267a646 manageHiddenOutputs: check that output object exists 2013-02-11 18:45:45 -06:00
Winston Chang
55838bb032 Call manageHiddenOutputs after timer callbacks 2013-02-11 18:37:18 -06:00
Winston Chang
67619ac5e8 Don't allow another flush if currently in one 2013-02-11 18:35:32 -06:00
Winston Chang
952b342859 Better checks for hidden output objects 2013-02-11 18:31:44 -06:00
Winston Chang
c7149c460d Add documentation for suspendWhenHidden option 2013-02-11 16:08:30 -06:00
Winston Chang
fd0613ea0e Call manageHiddenOutputs when suspendWhenHidden is set 2013-02-11 15:16:04 -06:00
Winston Chang
36d2dddc59 Run manageHiddenOutputs on app init 2013-02-09 00:02:52 -06:00
Joe Cheng
63c5b05584 Stop extra update message from occurring on startup 2013-02-08 16:37:55 -08:00
Winston Chang
4b235e5b87 Send output hidden state on init 2013-02-07 14:29:03 -06:00
Winston Chang
6c51fffdaa Fix tests 2013-02-07 14:29:03 -06:00
Winston Chang
5d6d638c85 Clarify suspend description 2013-02-07 14:29:03 -06:00
Winston Chang
90eb515167 Observer: .flushCallbacks to .invalidateCallbacks 2013-02-07 14:29:03 -06:00
Joe Cheng
17526711a2 Change resume behavior for Observer
Eliminate multiple runs when resumed multiple times
2013-02-07 14:29:03 -06:00
Winston Chang
cf0118e090 Add tests for suspended observers 2013-02-07 14:29:03 -06:00
Winston Chang
868d6fec42 Add suspended option to Observer 2013-02-07 14:29:03 -06:00
Winston Chang
851f5854bf Add outputOptions function 2013-02-07 14:29:03 -06:00
Winston Chang
eb5428c971 Suspend hidden outputs 2013-02-07 14:29:03 -06:00
Winston Chang
81188df7ef Update runUrl help and re-document 2013-02-07 10:46:20 -06:00
Winston Chang
9fd365cc41 isolate help: mention debugging use and fix typos 2013-02-06 14:38:12 -06:00
Winston Chang
999df6e40f httpResponse: make sure headers is a list. Fixes #102 2013-02-06 12:29:24 -06:00
Winston Chang
076d069568 runGist: accept new URL format with username 2013-02-06 12:06:14 -06:00
Joe Cheng
2738648197 Merge pull request #101 from jcheng5/chrome-frame
Chrome Frame compatibility
2013-02-05 15:18:03 -08:00
Joe Cheng
36013009a1 Chrome Frame compatibility 2013-02-05 15:15:03 -08:00
Winston Chang
1b60233862 Fix closing brace in isolate help 2013-02-05 10:56:54 -06:00
Winston Chang
2cba10dd05 Follow redirects with curl for http
The previous logic added the -L option to curl when downloading https, but
    not for http.
2013-02-04 13:06:15 -06:00
Winston Chang
b3944127ea Add note about using local() with isolate() 2013-02-01 15:16:33 -05:00
Winston Chang
f1674378ca Remove unneeded reactive() wrappers 2013-01-31 15:47:02 -05:00
Winston Chang
6f0191e1cf Block some operators for shinyoutput objects 2013-01-31 15:45:31 -05:00
Winston Chang
1848844be6 Cleaner method for creating objects with class 2013-01-30 15:06:17 -05:00
Winston Chang
8b6362c749 Add section markers 2013-01-30 15:04:55 -05:00
Winston Chang
d860d13361 Add comments to test 2013-01-30 15:04:50 -05:00
Winston Chang
4b077dbf4c Observers can be suspended/resumed 2013-01-30 14:47:19 -05:00
Winston Chang
40f73bbfe2 Bump version to 3.1.99 for development 2013-01-30 13:51:54 -05:00
Winston Chang
f455706d7c Bump version to 0.3.1 2013-01-29 21:16:44 -05:00
Winston Chang
23e9672476 Update NEWS 2013-01-26 13:16:42 -06:00
Winston Chang
36f992f95f Add [[<-.shinyoutput operator 2013-01-26 13:08:40 -06:00
Joe Cheng
b2c6d526ab Merge pull request #92 from wch/fix-download
Use correct default label for contexts. Fixes #91
2013-01-25 13:17:04 -08:00
Winston Chang
fe1e833677 Use correct default label for contexts. Fixes #91
NULL apparently is not a valid value for a field in a reference class.
2013-01-25 14:57:05 -06:00
Joe Cheng
8df1b9e8e5 Merge pull request #85 from jcheng5/flush-all
Flush all shinyapp instances
2013-01-25 08:52:51 -08:00
Joe Cheng
38b0f71b01 Merge pull request #89 from wch/reactive-invisible
Store visibility state of functions called from Observable
2013-01-25 00:47:42 -08:00
Winston Chang
29d2f115f8 Better reactiveText test 2013-01-24 23:10:02 -06:00
Winston Chang
0f677b4891 Add tests for reactive function return visibility 2013-01-24 22:45:07 -06:00
Winston Chang
2f7dd04168 Observable: save visibility state of function 2013-01-24 21:57:49 -06:00
Winston Chang
ed3b667985 Remove unneeded eval.parent 2013-01-24 21:38:25 -06:00
Joe Cheng
6ae1d8c158 Flush all shinyapp instances
Allows reactivity to affect all app instances at once.
(It already does but the outputs don't currently update)
2013-01-24 13:48:05 -08:00
Winston Chang
404bced97b Bump version to .99 for development 2013-01-24 13:58:58 -06:00
Winston Chang
5af49c8a82 Bump version and update NEWS 2013-01-23 14:54:39 -06:00
Winston Chang
85aa98e8e2 Fixes for R CMD check 2013-01-23 14:30:11 -06:00
Joe Cheng
330d102f62 Fix test on Linux (sort locale) 2013-01-23 12:17:45 -08:00
Joe Cheng
32b33a7910 Add res dir to .Rbuildignore 2013-01-23 12:13:40 -08:00
Joe Cheng
17c6a0f28a Merge branch 'reactivePrint-invisible'
Conflicts:
	man/plotOutput.Rd
2013-01-23 12:09:53 -08:00
Joe Cheng
7341eed1cf Merge pull request #80 from wch/run-github
Add functions runGithub and runUrl
2013-01-23 12:06:46 -08:00
Joe Cheng
ff99fbfbc9 Fix #64: Hitting Enter in textbox causes form submit 2013-01-23 11:54:06 -08:00
Winston Chang
9f67fdc771 Re-document 2013-01-23 13:44:18 -06:00
Winston Chang
521143a16b Add subdir argument for runGitHub and runUrl 2013-01-23 13:44:17 -06:00
Winston Chang
2622a25b12 Add runGitHub and runUrl functions 2013-01-23 13:44:17 -06:00
Joe Cheng
a91e925221 Remove failure comment 2013-01-23 11:33:06 -08:00
Joe Cheng
6c3289d5a5 Documentation and examples for reactivePrint/reactiveText 2013-01-23 11:32:13 -08:00
Joe Cheng
988a91ac06 reactiveText shouldn't capture print output 2013-01-23 11:31:51 -08:00
Winston Chang
aa7c913e9a Escape percent sign in documentation 2013-01-23 09:42:08 -06:00
Joe Cheng
56db9feaa4 reactivePrint should not display invisibles 2013-01-22 23:36:51 -08:00
Winston Chang
5ace0f13c9 Move validateCssUnit to separate function 2013-01-23 00:02:16 -06:00
Winston Chang
076e6c9479 Re-roxgenize 2013-01-22 23:25:36 -06:00
Winston Chang
8277b1192e Update NEWS 2013-01-22 23:23:02 -06:00
Winston Chang
150b978b0e Fix tests with reactiveValuesToList 2013-01-22 23:22:41 -06:00
Winston Chang
6c72096bfe Better CSS unit validation 2013-01-22 19:18:18 -06:00
Winston Chang
87c18cea80 Merge pull request #79 from wch/better-deps
Finer grained dependencies when converting reactiveValues to list
2013-01-22 17:15:38 -08:00
Winston Chang
e658734084 Rename reactivevalues_to_list to reactiveValuesToList 2013-01-22 19:14:30 -06:00
Winston Chang
ec4f350baa reactivevalues_to_list: add all.names option 2013-01-22 14:53:14 -06:00
Winston Chang
095f583211 Deprecate as.list.reactivevalues and add reactivevalues_to_list 2013-01-22 14:51:43 -06:00
Winston Chang
3c864cf6d2 reactiveValues(): improved check for unnamed arguments 2013-01-22 13:59:31 -06:00
Joe Cheng
eb4b21ce9f Fix #77: tagWriteChildren error 2013-01-21 22:40:08 -08:00
Joe Cheng
ff5349fd90 Fix #65: tagWrite doesn't expect strings except as direct children of tags 2013-01-21 16:31:09 -08:00
Winston Chang
1f34ffa85d plotOutput: check that height has proper format 2013-01-18 19:16:50 -06:00
Winston Chang
e98cab1f7c Fix test 2013-01-17 00:11:38 -06:00
Winston Chang
aabc9659a2 Update NEWS
Some news items were under the wrong version heading. Those have also been
fixed.
2013-01-16 23:00:07 -06:00
Winston Chang
8d8d308f7a Rename 'dependencies' to 'dependents' 2013-01-16 22:42:03 -06:00
Winston Chang
3ebd4595c6 Add read-write wrapper class for ReactiveValues 2013-01-16 19:02:26 -06:00
Winston Chang
7e1168946f Re-roxygenize 2013-01-16 16:08:12 -06:00
Joe Cheng
134689d8aa Remove subsetting operators from Map and Values
The correct operators would be [[/[[<- but since we don't use them I
just removed them instead.
2013-01-16 13:48:50 -08:00
Winston Chang
56282f9cbb Merge branch 'lazy' 2013-01-16 12:32:32 -06:00
Joe Cheng
b4713741b1 Two new recursion/circularity tests 2013-01-16 10:27:20 -08:00
Joe Cheng
e42fe3bd61 Fix problem with circular dependencies
The first of the included tests did not pass without the changes to
Observable. The problem occurred when a function read a reactive value
and then wrote it. Any dependents on the function would not receive
any invalidations, then or ever after.

The first problem was that the dirty state was unilaterally set to FALSE
after the function finished executing, which might not be accurate if
the function's newly created was invalidated during its own execution.
Instead we set dirty state to FALSE before executing. But to prevent
reentrant calls from thinking the cached value can be used, we add
a .running field that is also consulted during getValue.

The second problem was that Observable$getValue didn't register the
dependent until after updateValue. That is a problem if updateValue
creates *and* invalidates a context before returning. So now we
register the dependent before calling updateValue.
2013-01-15 17:37:26 -08:00
Winston Chang
4fd2dade60 reactiveTable: don't return blank if first element is NA. Fixes #71 2013-01-15 16:04:18 -06:00
Joe Cheng
e12b03504c Fix bad calls to on.exit
I didn't realize on.exit replaces previous calls to on.exit by default.
2013-01-15 12:07:27 -08:00
Winston Chang
153156c1fa Add back onInvalidate to Observer class 2013-01-15 11:13:46 -06:00
Winston Chang
3ecc69da2b Un-export execCount 2013-01-15 11:13:46 -06:00
Winston Chang
07ad29da41 Clarify isolation test 2013-01-15 11:13:46 -06:00
Winston Chang
7d0de0b26f Remove onInvalidateHint
The recent changes to onInvalidate make it do almost exactly the same thing.
2013-01-15 11:13:46 -06:00
Winston Chang
77fab9c78f Remove all pendingInvalidate 2013-01-15 11:13:46 -06:00
Winston Chang
3a8f3272c7 Don't call observers until flushReact() 2013-01-15 11:13:46 -06:00
Joe Cheng
2d44cbac1b Failing overreactivity test 2013-01-08 14:06:10 -06:00
Joe Cheng
893d72677b Try LIFO pendingInvalidates? 2013-01-08 14:06:10 -06:00
Joe Cheng
979eca4066 Add execCount 2013-01-08 14:06:10 -06:00
Joe Cheng
258d13e746 Add ctx$.label to help with debugging
Shows the code that the context "belongs" to.
2013-01-08 14:06:10 -06:00
Winston Chang
779531da5d Use lazy evaluation of reactive functions 2013-01-08 14:06:10 -06:00
Winston Chang
31d71006d7 Add tests for isolate() 2013-01-08 14:06:10 -06:00
Winston Chang
64ca66c062 Add test for reactive evaluation order 2013-01-08 14:06:10 -06:00
Winston Chang
6e1a2b3427 reactive tests: count number of times observers are run 2013-01-08 14:06:10 -06:00
Winston Chang
f585235192 Add reactivity tests 2013-01-08 14:06:10 -06:00
Winston Chang
9355643554 Update NEWS 2013-01-08 14:03:23 -06:00
Winston Chang
ccc6055926 Fix reactivity for empty checkbox groups. Fixes #58 2013-01-08 13:57:10 -06:00
Joe Cheng
6639446bb8 Update README.md 2013-01-07 22:39:07 -08:00
Joe Cheng
e2925c585f Add isolate function for accessing reactives non-reactively 2013-01-03 12:16:50 -08:00
Joe Cheng
6c76b0473c Add implementation of reactive values 2013-01-02 16:00:21 -08:00
Joe Cheng
e1e19632a5 Update URL in DESCRIPTION 2012-12-21 14:46:52 -08:00
Winston Chang
3e5364d5c0 Bump version number to .99 for development 2012-12-18 11:17:12 -06:00
Winston Chang
6c98de4c8b Update NEWS 2012-12-17 16:24:40 -06:00
Winston Chang
9613dde4d2 Increment version to 0.2.4 2012-12-17 15:30:08 -06:00
Winston Chang
d47df2e538 Re-roxygenize 2012-12-17 15:23:59 -06:00
Winston Chang
6fcacd5159 Use different method of accessing CairoPNG
R CMD check didn't like Cairo::CairoPNG. With this method, check wants
Cairo to be imported in NAMESPACE, but it shouldn't be - Cairo should
be optional.
2012-12-17 15:23:08 -06:00
Winston Chang
11b39cb020 Change maintainer 2012-12-17 14:30:47 -06:00
Winston Chang
d81f132db6 Update NEWS 2012-12-17 13:40:50 -06:00
Winston Chang
095697e789 Use new URL for runGist. Fixes #57 2012-12-17 12:18:19 -06:00
Joe Cheng
62d98c3137 Revert "Run invalidated hints only once per context"
This reverts commit e80d5dc172.

The original commit could cause under-reporting of progress.
2012-12-14 16:41:12 -08:00
jeffreyhorner
e80d5dc172 Run invalidated hints only once per context 2012-12-13 16:02:47 -06:00
jeffreyhorner
421e29db2d Suppress base64 output when tracing websocket messages 2012-12-13 16:00:58 -06:00
Joe Cheng
9e6e53583c Merge pull request #49 from wch/png-cairo
For png output, try quartz and CairoPNG before plain png
2012-12-05 09:56:11 -08:00
Joe Cheng
3f59a7d84e Fix bug where reactiveUI doesn't accept plain lists 2012-12-05 09:54:31 -08:00
Winston Chang
21ffd788ab For png output, try quartz and CairoPNG before plain png 2012-12-03 12:06:31 -06:00
Joe Cheng
8dadfea724 Separate request parameters from path; version 0.2.3 2012-11-30 09:31:09 -08:00
Joe Cheng
00ce52ecf7 Fix CRAN warning; version 0.2.2 2012-11-30 09:05:20 -08:00
Joe Cheng
50ac13d3fd [BREAKING] Modify API of downloadHandler
The `content` function now takes a file path, not writable connection, as an argument.
This makes it much easier to work with APIs that only write to file paths, not
connections.
2012-11-29 17:14:44 -08:00
Joe Cheng
58318fec46 Update package metadata for v0.2.0 2012-11-27 16:32:27 -08:00
Joe Cheng
a49941113e Require Shiny at app startup
Some of our examples omit library(shiny) from the top of ui.R and server.R,
which worked fine before but not with the namespace fix from yesterday.
Requiring shiny at startup fixes the problem.
2012-11-27 16:29:01 -08:00
Joe Cheng
595801cb99 Trivial style copy edits to example 10_download 2012-11-26 21:48:12 -08:00
Joe Cheng
0b469f09df Fix subtle name resolution bugs
See in particular:
http://stackoverflow.com/questions/13575353/how-does-the-shiny-r-package-deal-with-data-frames

Also reported at different times by Dirk Eddelbuettel and Jay Emerson.

The observed behavior is that S3/S4 method dispatch does not always seem to
work; the desired methods are not invoked despite appearing to be in the
search path.

The problem was that sourcing files with local=TRUE creates a new environment
based on the parent frame, which in our case is Shiny's package environment.
What we really want is to read from the global environment but write to a
throwaway environment. The correct way to do that is to make a new environment
with .GlobalEnv as the parent.
2012-11-26 21:45:28 -08:00
Joe Cheng
1e1f4e4a47 Update metadata for 0.1.14 2012-11-24 01:47:47 -08:00
Joe Cheng
c63e2ae7c8 Fix slider animation controls 2012-11-24 00:30:44 -08:00
Joe Cheng
d3d3fa990e Update version metadata 2012-11-23 23:47:27 -08:00
Joe Cheng
21980b7e71 Clean up PNG file when no longer needed 2012-11-23 22:44:37 -08:00
Joe Cheng
844ca0d387 I am stupid. 2012-11-21 23:02:40 -08:00
Joe Cheng
972ae35300 Update metadata for 0.1.12 2012-11-21 22:44:19 -08:00
Joe Cheng
57bfb8eb96 Bring untar operations in-house
Very simple tweak to R's untar2 code was all that was
required to fix the built-in untar's problems with
gists. Seemed best to just fork it and start using
the forked version directly, regardless of what is
installed on your machine.
2012-11-21 22:37:47 -08:00
Joe Cheng
ed6e6a9fb2 Squash another cygwin warning 2012-11-21 21:43:32 -08:00
Joe Cheng
ed402267b6 Fix runGist cygwin warning bug 2012-11-21 21:39:16 -08:00
Joe Cheng
6eec570828 Add CSS hooks for app-wide busy indicators 2012-11-21 00:04:16 -08:00
Joe Cheng
22fc1e3f0b Add param docs 2012-11-20 18:08:59 -08:00
Joe Cheng
ae9bd868f1 Implement arbitrary file downloads 2012-11-20 17:42:34 -08:00
Joe Cheng
a887012aca Update metadata for v0.1.11 2012-11-19 17:22:57 -08:00
Joe Cheng
bc73048ab9 Fix IE8 slice bug
IE8 doesn't like slice(0, undefined)--rather than interpreting it as slice(0),
it returns an array of length 0.
2012-11-19 17:19:51 -08:00
Joe Cheng
c89dd6c379 Fix issue #41: reactiveTable should allow print options too 2012-11-19 15:26:34 -08:00
Joe Cheng
9662debe5e Dynamic plot sizing 2012-11-19 15:26:02 -08:00
Joe Cheng
057262d917 Update metadata for v0.1.10 2012-11-19 13:11:07 -08:00
Joe Cheng
b6723a6219 Add per-session GET infrastructure. Allow IE8/9 to avoid data URIs. 2012-11-19 13:08:09 -08:00
Joe Cheng
068f3e0a43 Merge pull request #32 from edwindj/master
small bug: checkboxInputGroup sets html attribute "selected" in stead of "checked"
2012-11-15 23:30:28 -08:00
Joe Cheng
95635a8c47 Issue #37: headerPanel HTML argument shows up in title 2012-11-13 01:52:33 -08:00
Joe Cheng
3ec2071820 Address issue #35: Allow modification of untar args 2012-11-13 00:09:27 -08:00
Joe Cheng
1696db3044 Fix issue #36: reactiveUI does not always correctly render sliders
There is a deeper problem here, that reactiveUI output that renders stuff to the <head> will generally not work. We're not in a position to fix that yet and this problem has been reported twice, so we'll just fix this instance by making the slider dependencies built into the framework.
2012-11-11 18:32:34 -08:00
Joe Cheng
e1a1eab2b3 More MIME types 2012-11-10 15:18:29 -08:00
Edwin de Jonge
f7865f3358 changed html attribute of checkboxInputGroup from "selected" into "checked" 2012-11-08 23:09:08 +01:00
Joe Cheng
6d5f8ed5f3 Pointer to Shiny homepage 2012-11-08 03:29:23 -08:00
Joe Cheng
96a737379f Add linked example 2012-11-07 10:36:42 -08:00
Joe Cheng
d73feec013 Turns out GitHub doesn't like iframes 2012-11-07 10:28:47 -08:00
Joe Cheng
2ccead1da5 Add example to README 2012-11-07 10:28:06 -08:00
Joe Cheng
8885f2717e Update version 2012-11-06 13:53:53 -08:00
Joe Cheng
4448ffc777 Add methods for including text, HTML, and Markdown files in UI 2012-11-06 13:38:52 -08:00
Joe Cheng
022d10c598 Export and document observe function 2012-11-06 10:03:11 -08:00
Joe Cheng
8e6b7043bd Shut down timer callbacks before runApp returns 2012-11-06 09:36:49 -08:00
Joe Cheng
66eaaff598 More customizable error display 2012-11-02 09:49:17 -07:00
Joe Cheng
478c6c134f Much less flicker when updating plots 2012-11-02 09:48:36 -07:00
Joe Cheng
b5d333ba6c Rev downloader code 2012-10-31 15:36:52 -07:00
Joe Cheng
81723d55ac Change T and F to TRUE and FALSE
TRUE and FALSE are keywords whereas T and F are just predefined variables that can be reassigned
2012-10-31 11:35:41 -07:00
Joe Cheng
fb784ce962 Merge pull request #28 from rstudio/list-to-vec
Change lists to vectors in UI elements
2012-10-31 10:00:21 -07:00
Winston Chang
5a37380900 Capture stderr in download() 2012-10-30 16:19:14 -05:00
Joe Cheng
b6300f3a5c More robust setInternet2 workaround 2012-10-30 12:31:36 -07:00
Winston Chang
a3e8a2d623 Re-roxygenize 2012-10-30 10:49:55 -05:00
Winston Chang
7b3a4bdc39 Use vectors instead of lists in UI elements 2012-10-30 10:47:05 -05:00
Joe Cheng
cc0b5e5e0f Remove problematic link (fails R CMD check)
I first attempted to remove \code, but I couldn't figure out how to get the # in the URL to work right under both web-based help and PDF.
2012-10-29 11:56:59 -07:00
Joe Cheng
5c3f7d8f94 Update NEWS 2012-10-29 11:47:27 -07:00
Joe Cheng
8c3f8cd450 Add wellPanel and bootstrapPage functions 2012-10-29 11:45:58 -07:00
Joe Cheng
046582711a Update NEWS 2012-10-29 11:22:30 -07:00
Joe Cheng
15756ec92d Case insensitive probing for server.R, ui.R, et al 2012-10-29 11:19:23 -07:00
Joe Cheng
fc49abc9fb Fix issue #27: Warnings cause reactive functions to stop executing 2012-10-29 11:09:13 -07:00
Winston Chang
4a9ff27f3e Download gists in binary mode 2012-10-26 16:38:18 -05:00
Joe Cheng
790e6f370f Remove RCurl dependency 2012-10-26 14:07:07 -07:00
Joe Cheng
16ccc1321d Update docs 2012-10-26 10:46:42 -07:00
Joe Cheng
8648c94dd4 Update version to 0.1.8 2012-10-26 10:43:53 -07:00
Joe Cheng
dc4eb720ae Introduce input type hints
These allow the server to use custom deserialization code on a per-type basis.
2012-10-26 10:28:40 -07:00
Joe Cheng
0b891ad557 Run a GitHub gist 2012-10-25 20:41:52 -07:00
Joe Cheng
e96193ae28 Do .Random.seed restoring correctly 2012-10-24 23:19:13 -07:00
Joe Cheng
3ff9075959 Update NEWS 2012-10-24 21:11:56 -07:00
Joe Cheng
c03842056c Convert JSON to UTF-8
If reactivePrint or reactiveText return non-ASCII characters on
Windows, it causes invalid UTF-8 strings to be received by the
browser which closes the websocket connection.

I'm not sure this is the right place to do encoding, but it seems
to me like this approach is likely to work best for the most
users (especially those who just aren't thinking about encoding).
If you want to handle encoding in the reactives themselves (for
example), use `options(shiny.transcode.json=F)`.
2012-10-24 21:10:09 -07:00
Joe Cheng
6df226b21c Add repeatable utility function to stabilize RNGs 2012-10-24 16:12:08 -07:00
Joe Cheng
7dfa7d7426 Fix issue #26: Shiny.OutputBindings not correctly exported 2012-10-24 14:41:32 -07:00
Joe Cheng
b8b1a891cf Add custom message handler support, console logging
If the server sends a message with a "custom" field, that field's value will
be passed to a custom window.Shiny.oncustommessage function, if it is defined.

Also add support for messages like so:
{
  console: [
    'line one',
    'line two'
  ]
}

This will cause "line one" and "line two" to be printed at the browser console.
2012-10-04 17:45:20 -07:00
Joe Cheng
7df0e8b0f9 Update docs for 0.1.6 2012-09-25 03:08:31 -07:00
Joe Cheng
ff072ae9d9 bindAll should send initial values to server 2012-09-25 01:29:52 -07:00
Joe Cheng
f81ca39741 Add uiOutput. Tweak comments. 2012-09-25 00:33:00 -07:00
Joe Cheng
3db1f2a98c Don't animate showing/hiding of conditionalPanel 2012-09-21 19:51:24 -07:00
Joe Cheng
4865df9be1 Mark fileInput and reactiveUI as experimental. 2012-09-21 19:50:50 -07:00
Joe Cheng
0c16f2c334 Fix broken imports 2012-09-21 14:00:03 -07:00
Joe Cheng
d01149620f Fix issue #19: Checkboxes and radios can't be added dynamically 2012-09-19 11:48:28 -05:00
Joe Cheng
ab9401f390 Fix issue #20: DESCRIPTION file should use Imports instead of Depends 2012-09-19 11:47:12 -05:00
Joe Cheng
3223c17b74 Update websockets dependency version 2012-09-15 00:52:04 -07:00
Joe Cheng
404035bcf0 Bump version number 2012-09-14 19:16:03 -07:00
Joe Cheng
a0185bb0b4 Introduce shiny.http.response.filter option
Allows post-processing of HTTP responses
2012-09-14 13:15:58 -07:00
Joe Cheng
1a591cd9f1 conditionalPanel now triggers show/shown/hide/hidden event 2012-09-07 00:44:20 -07:00
Joe Cheng
e9b81b2033 [BREAKING] Simplify input binding callbacks
InputBinding.subscribe used to have to call callbacks with at least two arguments,
now there is only one optional argument (allowDeferred). The binding argument in
particular was problematic because it required "var self=this;".
2012-09-06 12:06:15 -07:00
Joe Cheng
cbfc1e8ed1 Add reactiveUI output type 2012-09-05 15:22:34 -07:00
Joe Cheng
cb63338805 Allow htmlOutput to contain inputs/outputs 2012-09-05 11:17:39 -07:00
Joe Cheng
bcdc82ccee Add conditionalPanel; JS API changes
- bindAll/unbindAll added
- bindInput/bindOutput/unbindInput/unbindOutput removed
2012-09-05 09:40:40 -07:00
Joe Cheng
76a4cf6c34 Update NEWS 2012-08-31 23:21:04 -07:00
Joe Cheng
872f23b0f0 Improvements for output binding/unbinding
- When bound, outputs receive cached error/value
- On binding, (potentially all) output plot sizes are resent
2012-08-31 23:12:20 -07:00
Joe Cheng
e61f7405fd Upload example app should accept text/plain files 2012-08-31 22:39:45 -07:00
Joe Cheng
0714871b56 Improve blob handling browser compatibility 2012-08-31 22:39:26 -07:00
Joe Cheng
8a89fb2a1a Expose and fix Shiny.unbindOutputs 2012-08-31 18:29:42 -07:00
Joe Cheng
036544e3ed Eagerly evaluate output name 2012-08-31 12:33:13 -07:00
Joe Cheng
7a6784d809 Add missing param to prototype method 2012-08-31 11:48:21 -07:00
Joe Cheng
ed9301705b Refactor JS to use more consistent OOP style
(function() { }).call(Foo.prototype) for extending prototypes manually, and
$.extend for extending objects manually or prototypes inheriting from each
other.
2012-08-31 10:00:20 -07:00
Joe Cheng
21f9694574 Add NEWS for file upload 2012-08-30 22:10:16 -07:00
Joe Cheng
3a0b11b89d Add file upload feature
This feature is currently pretty rough. It only works in the most modern
browsers (depends on HTML5 File API, including Blob.slice) and doesn't
show upload progress.
2012-08-30 22:07:00 -07:00
Joe Cheng
d5272e3e74 Update version 2012-08-30 12:27:05 -07:00
Joe Cheng
b5197869db Update NEWS 2012-08-30 12:18:46 -07:00
Joe Cheng
5f775db40a Enhancements to Shiny transport
- JS can now do remote procedure calls (with return value or exception), not just message passing
- RPC calls can include non-JSON-compatible binary data (not compatible with IE)
2012-08-30 12:16:12 -07:00
Joe Cheng
9b84b83627 Allow binding and unbinding of Shiny input/output 2012-08-30 12:04:17 -07:00
Joe Cheng
b0d9b5762a Don't use WebSocket constant, it's not on IE8 2012-08-24 11:28:46 -07:00
Joe Cheng
8d9fd402be Check inheritance properly 2012-08-23 18:07:09 -07:00
Joe Cheng
73a44a4f8e Packages can register their own URL namespace
Helpful for serving up custom stylesheets, CSS, images, etc.
2012-08-23 13:08:08 -07:00
Joe Cheng
a7dd62249e Add checkboxGroupInput control 2012-08-22 13:39:19 -07:00
Joe Cheng
42fac871fb Extensible websocket creation 2012-08-22 12:32:33 -07:00
Joe Cheng
2782bf6735 Execute sendPlotSize when anything is shown/hidden 2012-08-21 14:18:00 -07:00
Joe Cheng
f2a1ce4977 Update NEWS 2012-08-21 14:16:25 -07:00
Joe Cheng
c8969c4cc0 Upgrade to Bootstrap 2.1 2012-08-21 11:35:37 -07:00
Joe Cheng
cfefb4a07c Update NEWS 2012-08-20 17:16:23 -07:00
Joe Cheng
653509368b Let Bootstrap tabset send its selected tab as input 2012-08-20 17:01:41 -07:00
Joe Cheng
51b269f329 roxygen2 on my dev box
For some reason my machines can't agree on the order of the NAMESPACE file
2012-08-20 13:49:28 -07:00
Joe Cheng
c92d2cc32e Documentation for numericInput(step) 2012-08-20 13:46:36 -07:00
Joe Cheng
cb4091895a Fix S3 generic method consistency 2012-08-20 13:44:04 -07:00
Joe Cheng
b96bc5a710 Fix broken roxygen declaration 2012-08-20 13:43:49 -07:00
Joe Cheng
4ace825c58 Add step param to numericInput 2012-08-20 13:32:10 -07:00
Joe Cheng
589e0f7bb5 Bump version/date 2012-08-20 10:05:58 -07:00
Joe Cheng
347b9e1d7a Add NEWS 2012-08-20 10:05:16 -07:00
Joe Cheng
363633b01f Fix issue #10: Plots in tabsets not rendered 2012-08-20 09:54:50 -07:00
Joe Cheng
575350842a Fix broken progress indication 2012-08-18 00:01:38 -07:00
Joe Cheng
d49e8d172f Improvements to reactives and UI
- `input` object now implements names() and as.list()
- Simpler dependency tracking impl using Dependencies class
- New `singleton` function for making HTML content appear only once
- Fix issue #4: head deduplication should not be line-oriented
2012-08-18 00:01:16 -07:00
Joe Cheng
642d153202 Dynamic output bindings 2012-08-14 01:21:25 -07:00
Joe Cheng
8cf7d8b4cb Input binding enhancements
- Add textarea binding
- Deterministic priority ordering
- Allow getting/setting priorities for existing bindings
2012-08-14 00:58:56 -07:00
Joe Cheng
b0b7cfa3a5 Remove comment cruft 2012-08-13 14:45:49 -07:00
Joe Cheng
3692d5f008 Delay Shiny init to after document-ready
Also remove some dead code
2012-08-13 11:51:53 -07:00
Joe Cheng
2344dc04a5 Fix bug in deferred submission 2012-08-13 10:09:44 -07:00
Joe Cheng
cf37072bed Don't debounce when animating 2012-08-13 10:04:06 -07:00
Joe Cheng
cc5c933e7d Use InputBinding for sliders 2012-08-09 17:13:14 -07:00
Joe Cheng
ad1737f16b Infrastructure for extensible inputs 2012-08-09 16:38:21 -07:00
Joe Cheng
2ac1895a6e Inputs without names shouldn't be sent 2012-08-08 17:13:19 -07:00
Joe Cheng
4dc7630adc Cleanup code, exports, radio values
- Radio values based on id could not be kept in sync because implicitly deselected radios don't trigger a change event. So don't pass id-based values for radios at all (still works for checkboxes though)
- Make onInputChange available in a Shiny namespace on the window
- Remove no longer used debounce/throttle code
2012-08-07 15:25:00 -07:00
Joe Cheng
66a3d68755 Hook up modular input pipeline 2012-08-07 15:25:00 -07:00
Joe Cheng
ce9213cdc1 Infrastructure for more flexible input handling 2012-08-07 15:25:00 -07:00
Joe Cheng
99b1ed51a6 Update install instructions
Adding ourselves to the repo list means we don't have to serve up
all the CRAN dependencies on our repo.
2012-08-06 15:40:05 -07:00
Joe Cheng
c7dcff56c7 New, simpler install instructions 2012-08-02 18:11:49 -07:00
Joe Cheng
fcdb8f08b8 Bump version 2012-08-02 14:02:32 -07:00
Joe Cheng
daa03999b6 Fix error when no ui.R file exists
Error message was:
'Error: argument "handler is missing, with no default'

Previously I was intentionally allowing the main dynamicHandler code to run, intending to allow ui.R to be created even after the application started. Hopefully I can bring that capability back when I figure out more deeply why the error is happening.
2012-08-02 13:55:32 -07:00
JJ Allaire
cd7c5dc048 add .Rprofile to gitignore 2012-07-30 06:55:25 -07:00
JJ Allaire
09f0f85b9c update README with installation instructions 2012-07-30 08:02:57 -04:00
JJ Allaire
8aee7563f0 bump version to 0.1.1 2012-07-30 07:27:07 -04:00
Joe Cheng
6d6c8cecd6 Fix path bug on Windows 2012-07-29 18:39:10 -07:00
JJ Allaire
334f6c8757 sync readme to welcome 2012-07-29 11:00:03 -07:00
Joe Cheng
8455343fee Only instantiate sliders if sliders are loaded 2012-07-29 11:04:27 -07:00
JJ Allaire
d234ab016f tweaks to readme 2012-07-29 08:54:43 -07:00
JJ Allaire
a312b46e97 man page for shiny-package 2012-07-29 08:32:55 -07:00
JJ Allaire
ff06c7997b update package DESCRIPTION 2012-07-29 08:12:53 -07:00
JJ Allaire
3dc6d84d1f docs for output elements 2012-07-29 07:39:44 -07:00
JJ Allaire
ef74483ebb fix types in bootstrap docs 2012-07-29 07:39:24 -07:00
JJ Allaire
d8cf7bcbf8 docs for tabsets 2012-07-29 07:02:04 -07:00
JJ Allaire
33336a7ad2 docs for text and numeric inputs 2012-07-29 06:21:26 -07:00
JJ Allaire
79865b39d1 docs for radio buttons and submit button 2012-07-29 06:12:52 -07:00
JJ Allaire
375125e992 additional control docs 2012-07-29 06:03:39 -07:00
JJ Allaire
ebc5a992dc remove docs for startApp 2012-07-29 05:40:48 -07:00
JJ Allaire
da01184fc9 remove more unexported functions from docs 2012-07-29 05:36:13 -07:00
JJ Allaire
e0a6a6c558 remove internal functions from docs 2012-07-29 05:34:48 -07:00
JJ Allaire
93ec81bc57 break tag/tagAppendChild out into a separate help topic 2012-07-29 05:27:57 -07:00
JJ Allaire
29295fa8a7 add client param (rd file checkin) 2012-07-29 05:15:42 -07:00
JJ Allaire
74e7130bee fix typo in main panel docs 2012-07-29 05:15:08 -07:00
JJ Allaire
30cd83662a add docs for client param to registerClient 2012-07-29 05:14:49 -07:00
JJ Allaire
5f8f3ca328 fix type in mainPanel example 2012-07-29 05:14:32 -07:00
JJ Allaire
5744f1c7ee don't export tagWrite or tagWriteChildren 2012-07-29 04:57:24 -07:00
JJ Allaire
ba05f03359 simple docs shims for clearClients and registerClient 2012-07-29 04:54:44 -07:00
JJ Allaire
43c9ed0655 document top level shiny ui defintion functions 2012-07-29 04:40:57 -07:00
JJ Allaire
43fa8f53d3 eliminate problematic usage section for HTML Builder Functions 2012-07-29 04:16:36 -07:00
JJ Allaire
258dad0389 change usage of tags in docs (was yielding a warning) 2012-07-29 04:13:50 -07:00
JJ Allaire
5d5eaa2065 update namespace 2012-07-29 03:45:51 -07:00
Joe Cheng
1329136792 Get rid of R CMD CHECK warnings 2012-07-28 14:26:13 -07:00
Joe Cheng
c6cbcff9ce Document HTML function 2012-07-28 14:25:53 -07:00
Joe Cheng
ed2e637596 Fix bug where HTML() nodes were still being escaped 2012-07-28 14:24:54 -07:00
Joe Cheng
c97aecf9ff Document and enhance builder functions
Add easy string conversion and printing
2012-07-28 14:00:50 -07:00
JJ Allaire
9672088158 remove helpText from animation example 2012-07-28 11:12:23 -04:00
JJ Allaire
995908d3c6 remove docs since they have been folded into the tutorial 2012-07-28 11:04:49 -04:00
JJ Allaire
74314457ba update readme 2012-07-28 05:06:22 -07:00
Joe Cheng
d64c99ed28 Fix broken Rd link 2012-07-28 01:54:33 -07:00
Joe Cheng
38bf13e9bf Add doc for sliderInput's animate param 2012-07-28 01:52:41 -07:00
Joe Cheng
4101c1efd0 Rd docs for observe, reactive, reactiveTimer
Also improve some error messages
2012-07-28 01:47:19 -07:00
Joe Cheng
f095700485 Rd docs for runApp, runExample, shinyServer 2012-07-28 01:17:21 -07:00
Joe Cheng
4ff1c95083 Slider and animation docs 2012-07-27 17:46:15 -07:00
Joe Cheng
c3e14933e2 Use simpler output format for 05_sliders 2012-07-27 14:46:11 -07:00
Joe Cheng
1b3cf52a17 More control over slider animation
- Slider now takes animate argument that expects NULL, TRUE, FALSE, or a list that can be constructed using animationOptions()
- Update examples/05_sliders to use new animation format
- Tweak spacing around slider
2012-07-27 14:45:22 -07:00
Joe Cheng
e2f8163b21 Change git URL to SSH-style 2012-07-27 14:34:35 -07:00
Joe Cheng
54d3e1a5e1 Serialize logical attrib values using lowercase 2012-07-27 14:05:40 -07:00
Joe Cheng
57e088f6e1 Implicit initialization of jslider 2012-07-27 14:05:12 -07:00
Joe Cheng
c759dcd7df Add htmlOutput function 2012-07-27 13:21:08 -07:00
Joe Cheng
033eb41a1d Make slider send only 1 event per animation frame 2012-07-27 13:21:08 -07:00
JJ Allaire
77f6e138ac shorten first readme bullet 2012-07-27 13:17:32 -07:00
JJ Allaire
c5c70b0f49 bold recommended 2012-07-27 13:16:41 -07:00
JJ Allaire
6b37e026fd update readme 2012-07-27 13:15:33 -07:00
JJ Allaire
731018082b Merge branch 'master' of github.com:rstudio/shiny 2012-07-27 12:57:26 -07:00
JJ Allaire
a7eab9f00e use c for install packages 2012-07-27 12:57:19 -07:00
Joe Cheng
0d3aebc077 Slider improvements
- Get rid of smooth--it doesn't make sense for our purposes since we always provide step
- Don't do any rounding by default (this required changes in jslider)
- Switch order of format and locale arguments
- Animation should pause automatically when it reaches the end
- Default to 1s animation interval
- If animation is started when sliders are at the end, restart
- Animation button click target ran the width of the slider
2012-07-27 11:52:57 -07:00
JJ Allaire
fb37e3254d updated readme 2012-07-27 11:40:21 -07:00
JJ Allaire
6d9da1260a add comments to sliders example 2012-07-27 09:53:32 -07:00
JJ Allaire
0d749f333a don't use as.integer since it's no longer required 2012-07-27 09:34:03 -07:00
JJ Allaire
338463057c initial code for slider example 2012-07-27 09:05:23 -07:00
JJ Allaire
35c131f661 use span for textOutput 2012-07-27 08:05:52 -07:00
Joe Cheng
da6771eaae Back out accidentally-committed test code 2012-07-27 01:37:24 -07:00
Joe Cheng
fbf3623343 Add rudimentary animation to sliders 2012-07-27 01:35:09 -07:00
Joe Cheng
2d43817b2f Slider improvements, typed input values
- Slider now has 'smooth' parameter that, if false, snaps slider to step
- Two-handle slider (provide a vector of length 2 to value=)
- Slider and number inputs yield numeric values
2012-07-26 17:43:45 -07:00
Joe Cheng
01905c51dd Expose more slider options, add tick logic 2012-07-26 17:43:45 -07:00
JJ Allaire
84494b8a0a dont import datasets into mpg ui 2012-07-26 10:12:50 -07:00
JJ Allaire
aded289558 use reactiveText where appropriate 2012-07-26 10:07:35 -07:00
JJ Allaire
f1462fa0d2 fix spelling error 2012-07-26 09:21:41 -07:00
JJ Allaire
5cfd546b2a change indentation for tabset app 2012-07-26 09:18:14 -07:00
JJ Allaire
3b38792481 user new header panel syntax for tabsets 2012-07-26 07:27:04 -07:00
JJ Allaire
31b347e8dd headerPanel now includes a title element and just inserts a plain h1 2012-07-26 07:22:41 -07:00
JJ Allaire
d87149ab5d roxygenzie 2012-07-25 19:01:49 -07:00
JJ Allaire
fd2f4789d3 inlcude radioButtons in example 6 2012-07-25 19:01:38 -07:00
Joe Cheng
0d8d35743d Observers defer first execution until flushReact
This allows us to greatly simplify the way outputs are defined
2012-07-25 16:07:40 -07:00
Joe Cheng
b5a65040b3 Comment tweaks 2012-07-25 15:50:11 -07:00
Joe Cheng
d44289f036 reactiveText -> reactivePrint 2012-07-25 15:30:53 -07:00
Joe Cheng
cb4b45aff1 Support radio/checkbox; unlist input lists when unnamed 2012-07-25 14:53:08 -07:00
JJ Allaire
0f4851e77d add comment noting immediate rendering of caption 2012-07-25 14:44:54 -07:00
JJ Allaire
42fe86e024 add comments for examples 7 and 8 2012-07-25 14:42:50 -07:00
JJ Allaire
3bb0ebb98f add comments for example 6 2012-07-25 14:33:49 -07:00
JJ Allaire
391310faa5 add main server comment for examples 3 and 4 2012-07-25 14:26:56 -07:00
JJ Allaire
ab0552f409 different prefix for output comments 2012-07-25 14:21:04 -07:00
JJ Allaire
8a6f59e350 add comments to example 4 2012-07-25 14:19:38 -07:00
JJ Allaire
8e859e53c2 add comments to examples 2 and 3 2012-07-25 14:12:52 -07:00
JJ Allaire
a44e475451 add comments to example 1 2012-07-25 14:02:31 -07:00
JJ Allaire
f958839af1 load mpgData at startup 2012-07-25 11:16:04 -07:00
JJ Allaire
f741851250 eliminate animation example 2012-07-25 11:01:18 -07:00
JJ Allaire
acd68b5de8 more widgets example 2012-07-25 10:10:03 -07:00
JJ Allaire
466ea7277f correct handling of variable inputs for helpText and HTML functions 2012-07-25 10:09:39 -07:00
JJ Allaire
c80072a62e example 2 should use a reactive function 2012-07-25 10:09:03 -07:00
JJ Allaire
bc0a37e8da export radioButtons function 2012-07-25 10:07:09 -07:00
JJ Allaire
a323f40da2 stubs for examples we haven't built yet 2012-07-25 09:36:42 -07:00
JJ Allaire
ee05e6ba03 use numeric sequences for example directories 2012-07-25 09:32:32 -07:00
JJ Allaire
ae9ef5c13f use mpg dataset for user example 2012-07-25 12:20:27 -04:00
JJ Allaire
fcc90df31c implement radioButtons (note that initial value isn't correctly sent right now) 2012-07-25 11:53:50 -04:00
JJ Allaire
d6b6719b54 rename model function to formulaText 2012-07-25 11:15:33 -04:00
JJ Allaire
21e8af827f add titanic example 2012-07-25 09:42:53 -04:00
JJ Allaire
5e5d233d83 add library(datasets) where required 2012-07-25 09:20:55 -04:00
JJ Allaire
214fd92b12 add html ui example 2012-07-25 09:14:20 -04:00
JJ Allaire
3687790730 remove br tags from reactivity example 2012-07-25 08:57:33 -04:00
JJ Allaire
d0f86078aa add tabsets example 2012-07-25 08:51:18 -04:00
JJ Allaire
649cb69466 allow helpText to take multiple strings 2012-07-25 08:47:38 -04:00
JJ Allaire
2f342e7664 remove allcaps and hash examples 2012-07-25 08:40:29 -04:00
JJ Allaire
e4fccc2f84 add reactivity example 2012-07-25 08:37:10 -04:00
JJ Allaire
61bd2d356b add text example 2012-07-25 08:07:36 -04:00
JJ Allaire
66ddb6ce0a make min and max optional for numeric input 2012-07-25 08:06:23 -04:00
JJ Allaire
573b3b1dfd tweak hello initial value 2012-07-25 08:04:58 -04:00
JJ Allaire
560bd3ca85 use condensed style for tables 2012-07-25 02:48:02 -07:00
Joe Cheng
1f5fe5b570 Use Sys.time instead of C code 2012-07-24 22:09:23 -07:00
Joe Cheng
d18d2df417 More robust runExample logic 2012-07-24 21:53:16 -07:00
Joe Cheng
91731a86bf Fix CSS for jslider 2012-07-24 18:47:14 -07:00
Joe Cheng
7108761e8f Bootstrap-styled tables
Other UI packages can override the table styles by using the option shiny.table.class.
2012-07-24 18:36:02 -07:00
Joe Cheng
0fe8bacf73 Integrate slider, more efficient input event handling 2012-07-24 18:20:11 -07:00
Joe Cheng
ef1afb482f common.R => global.R 2012-07-24 14:12:44 -07:00
JJ Allaire
134a3de256 hello sample app 2012-07-24 13:32:04 -07:00
JJ Allaire
71975546cb add support for select multiple attribute 2012-07-24 13:26:49 -07:00
Joe Cheng
b4c02f42f7 Add support for progress indication
The CSS class 'recalculating' will be added to any output elements whose content might be affected by a change to one or more of the inputs.
2012-07-24 10:45:00 -07:00
JJ Allaire
da7210f43f rename server function to shinyServer 2012-07-24 02:53:48 -07:00
Joe Cheng
8b4d62e374 Error handling support (very basic) 2012-07-23 17:10:19 -07:00
JJ Allaire
b68da2c3d3 add jslider component 2012-07-23 13:54:01 -07:00
JJ Allaire
b2db41c7f4 remove submit button from example 3 2012-07-23 12:08:54 -07:00
JJ Allaire
c4922d1655 insert name/value handling for selectList (now has same behavior as manipulate) 2012-07-23 12:08:26 -07:00
JJ Allaire
94ca77e697 remove html tags from example 3 2012-07-23 11:43:43 -07:00
JJ Allaire
c1d076ef79 change name of selectListInput to selectInput 2012-07-23 11:43:09 -07:00
JJ Allaire
39c69a4aff bind directly to shiny css class names 2012-07-23 11:39:16 -07:00
JJ Allaire
5a0921ed74 flesh out Readme.md 2012-07-23 11:23:44 -07:00
JJ Allaire
68c668615f add runExample function for easily running examples from within the tutorial 2012-07-23 09:01:36 -07:00
JJ Allaire
e1d5876ae6 move doc and examples to inst directory 2012-07-23 08:59:29 -07:00
JJ Allaire
741910407f change applicationPage to pageWithSidebar 2012-07-23 08:50:21 -07:00
JJ Allaire
39d4befc54 make tags module responsible for the export of the HTML function 2012-07-23 08:49:56 -07:00
JJ Allaire
d13505ce91 eliminate withTags construct 2012-07-23 08:42:45 -07:00
JJ Allaire
8c6d586fb0 fix HTML function 2012-07-23 06:04:31 -07:00
JJ Allaire
f66c2967dd use panel suffix for tab components 2012-07-23 05:53:20 -07:00
JJ Allaire
ef44a2295f export html tags from shinyui module rather than tags module 2012-07-23 05:09:50 -07:00
JJ Allaire
6186231041 unify naming convention for tags module 2012-07-23 04:59:17 -07:00
JJ Allaire
25ec5550b5 remove jslider component 2012-07-23 03:15:54 -07:00
JJ Allaire
1f93610a95 add COPYING and NOTICE files 2012-07-23 02:10:04 -07:00
JJ Allaire
01cde51a71 liveText should not include a label (should be done at a higher level in the system) 2012-07-23 01:53:32 -07:00
JJ Allaire
7d054c11de submit button: change default caption and ensure it is wrapped in a block element 2012-07-22 09:21:15 -07:00
JJ Allaire
98f717d5b4 add comment on slider control source/dependencies 2012-07-22 07:52:20 -07:00
JJ Allaire
6f315144cc factor slider into core slider function and sliderInput wrapper for integration into bootstrap forms 2012-07-22 07:47:36 -07:00
JJ Allaire
9ba8f569db merge 2012-07-22 10:15:20 -04:00
JJ Allaire
51f169571f add primary style to submit button 2012-07-22 10:14:14 -04:00
JJ Allaire
b6a9ffb4c7 initial implementaiton of slider control 2012-07-22 10:13:58 -04:00
JJ Allaire
346612aac1 initial implementaiton of slider control 2012-07-22 10:11:47 -04:00
JJ Allaire
205144d92d add support for form submit button 2012-07-21 18:40:33 -07:00
JJ Allaire
af2e321f45 add helpText widget 2012-07-21 18:31:32 -07:00
JJ Allaire
e22a20701b automatically generate ids for tabsets 2012-07-21 13:28:05 -07:00
JJ Allaire
f3edde8f81 add HTML function for including raw html 2012-07-21 13:12:50 -07:00
JJ Allaire
f405a0c905 perform html escaping of attribs and text 2012-07-21 12:50:51 -07:00
JJ Allaire
4907df497f use more natural attribute names now that we need to use withTags less often 2012-07-21 12:31:51 -07:00
JJ Allaire
e551c42f32 export some additional commonly used html tags 2012-07-21 12:18:12 -07:00
JJ Allaire
0c1a235cc1 ensure that attribute names don't conflict with tag names 2012-07-21 12:03:44 -07:00
JJ Allaire
5384b3a8c0 explicitliy specify h1 element in headers 2012-07-21 12:03:00 -07:00
JJ Allaire
0acb5f5857 export a small set of text and heading oriented html tags 2012-07-21 11:45:34 -07:00
JJ Allaire
cee124a4d6 use padding rather than br for header panel 2012-07-21 11:44:42 -07:00
JJ Allaire
084b983b44 change name of application to applicationPage so as to be less likely to conflict with future apis 2012-07-21 11:21:58 -07:00
JJ Allaire
bf15948275 add some padding to the top of the header panel 2012-07-21 11:21:23 -07:00
JJ Allaire
8796875128 change name of createTag function to tag 2012-07-21 10:55:48 -07:00
JJ Allaire
af9c2b1449 rename shiny core output functions with live prefix 2012-07-20 18:50:22 -07:00
Joe Cheng
f0d6b6f558 Hot-reload of server.R 2012-07-20 15:59:56 -07:00
Joe Cheng
3778e01d7c Hot-reload of ui.R 2012-07-20 15:16:05 -07:00
JJ Allaire
70ebad0410 rename client.R to ui.R 2012-07-20 14:01:57 -07:00
JJ Allaire
7cf58bd864 improve names of functions in tags.R 2012-07-20 13:29:53 -07:00
JJ Allaire
5858483fca use withTags where appropriate 2012-07-20 13:20:38 -07:00
JJ Allaire
fb94d2a99c Merge branch 'master' of github.com:rstudio/shiny 2012-07-20 12:42:15 -07:00
JJ Allaire
55b5441f00 implement withTags 2012-07-20 12:42:08 -07:00
Joe Cheng
bf397e496c Add option for printing websocket traffic 2012-07-20 12:06:42 -07:00
Joe Cheng
a78ae8ca4a Don't plot unless width and height are positive 2012-07-20 12:06:42 -07:00
JJ Allaire
c635b92991 re-organize bootstrap.R 2012-07-20 12:06:31 -07:00
JJ Allaire
53d406f640 eliminate labelOnTop option 2012-07-20 11:59:38 -07:00
JJ Allaire
701f4b743b rename functions to clarify shiny core vs. bootstrap 2012-07-20 11:52:10 -07:00
JJ Allaire
7466baf1b2 support for otuput tabsets 2012-07-20 11:31:58 -07:00
JJ Allaire
13ecf8ef21 use bootstrap for example 3 2012-07-20 08:53:04 -07:00
JJ Allaire
c946a3973a use standard html attribute names for components whenever possible 2012-07-20 08:04:31 -07:00
JJ Allaire
615f265c00 bootstrap for examples 1 and 2 2012-07-20 07:52:35 -07:00
JJ Allaire
4177ba7840 eliminate withTags (couldn't get it to work properly) 2012-07-20 05:11:33 -07:00
JJ Allaire
393593b2d2 roxygenize 2012-07-19 19:01:22 -07:00
Joe Cheng
e736c3949a Use new client.R/server.R scheme 2012-07-19 14:26:01 -07:00
JJ Allaire
e1509e7db3 recursively include lists of lists of tags 2012-07-19 13:12:37 -07:00
JJ Allaire
49150b07fd correctly handle lists of tags 2012-07-19 13:03:25 -07:00
JJ Allaire
1d8f1b4c6a unpack var args before calling createTag 2012-07-19 12:57:09 -07:00
JJ Allaire
833f0c67cf Merge branch 'master' of github.com:rstudio/shiny 2012-07-19 12:48:20 -07:00
JJ Allaire
4b559b5a94 break tags into their own namespace 2012-07-19 12:48:15 -07:00
Joe Cheng
55c8d60cfb Add Bootstrap 2.0.4 to shared resources 2012-07-19 11:14:40 -07:00
JJ Allaire
0e129379e9 use html builder for example 2 2012-07-19 08:44:35 -07:00
JJ Allaire
7e3f704285 add textInput function 2012-07-19 08:10:04 -07:00
JJ Allaire
d8a595ac70 change clientUI -> clientPage 2012-07-19 06:57:00 -07:00
JJ Allaire
c13cb9b723 remove example code from ui.R 2012-07-19 06:41:43 -07:00
JJ Allaire
8cc83855b9 replace defineUI and page functions with single clientUI function (page conflicted with base::page and having a single function seemed simpler) 2012-07-19 06:41:16 -07:00
JJ Allaire
faebbf5753 Merge branch 'master' of github.com:rstudio/shiny 2012-07-19 06:38:03 -07:00
JJ Allaire
3e297bad1f use withHeadTags function rather than head directly (since it conflicted with base::head) 2012-07-19 06:33:07 -07:00
JJ Allaire
f56949dd0b use withHeadTags function rather than head directly (since it conflicted with base::head) 2012-07-19 06:08:07 -07:00
Joe Cheng
04081ec2d3 Integrate UI builder into Shiny
Replace example #1 HTML with builder
2012-07-18 15:27:27 -07:00
JJ Allaire
442f3d93c6 Merge branch 'master' of github.com:rstudio/shiny 2012-07-18 17:53:08 -04:00
Joe Cheng
b41d9bff51 HTML escaping utility function 2012-07-18 14:44:44 -07:00
JJ Allaire
7e1cd68cb4 comment out example/demo code 2012-07-18 17:06:16 -04:00
JJ Allaire
47675633d2 only self-close void elements 2012-07-18 16:54:02 -04:00
JJ Allaire
8e59834989 Merge branch 'master' of github.com:rstudio/shiny 2012-07-18 16:36:25 -04:00
JJ Allaire
a87c3cdb88 add doctype and charset to html header 2012-07-18 16:36:17 -04:00
Joe Cheng
b2f9903e18 Allow dynamic rendering of front-ends 2012-07-18 13:04:35 -07:00
JJ Allaire
a48c8056f2 allow attributes without values via NA 2012-07-18 15:56:01 -04:00
JJ Allaire
dfd6b85296 defer varargs processing until tag function 2012-07-18 15:43:58 -04:00
JJ Allaire
f3aed1bd53 more work on html builder 2012-07-18 15:41:36 -04:00
Joe Cheng
41716d160b Change startApp/runApp signature
Also change example apps to launch directly using runApp
2012-07-18 09:00:32 -07:00
Joe Cheng
bd87be2f7e Tweak docs 2012-07-18 09:00:32 -07:00
JJ Allaire
9bd4ad6e47 first stab at html generation syntax 2012-07-18 08:56:08 -07:00
JJ Allaire
9bd0c01bdd more scaffolding for ui module 2012-07-18 03:32:04 -07:00
JJ Allaire
7dc6b4035a add initial scaffolding for ui module 2012-07-18 03:25:40 -07:00
JJ Allaire
3a65b9e0e5 update reactivePlot docs 2012-07-18 03:25:14 -07:00
Joe Cheng
569b98c724 Update Example 3 to use auto-sized plot 2012-07-17 23:00:48 -07:00
Joe Cheng
3de022ba05 Add autosizing to reactive plots
Autosizing meaning the plot's HTML tag's clientside width and height will automatically be used by the renderer
2012-07-17 23:00:32 -07:00
Joe Cheng
b697718826 Add client throttle/debounce support
Also add 500ms debounce to input keyup/change
2012-07-17 22:58:57 -07:00
Joe Cheng
a16f7b34ab Allow output functions to access shinyapp and their name 2012-07-17 22:57:47 -07:00
Joe Cheng
0660ddbfbf Values keys that start with . were not reactive 2012-07-17 22:28:22 -07:00
Joe Cheng
f1a4bf4dd7 Allow deferred submission of input 2012-07-16 16:28:53 -07:00
Joe Cheng
06c319d1aa remove launchApp 2012-07-16 11:24:39 -07:00
Joe Cheng
2d89749c9b Include docs for launch.browser param 2012-07-16 09:37:12 -07:00
Joe Cheng
696bee13af Better interactive app lifecycle management
- When runApp returns, close the server socket
- Optionally launch browser when runApp is called
2012-07-16 09:30:35 -07:00
Joe Cheng
c5b835186c Keep R responsive when running interactively 2012-07-16 09:12:49 -07:00
Joe Cheng
ea3c1dacea Take version number out of jquery filename 2012-07-16 09:12:49 -07:00
JJ Allaire
7de29090db add launchApp function to run an app asynchronously and open it in a web browser 2012-07-16 06:18:57 -07:00
JJ Allaire
d982d15fbc sleep for 100ms around calls to serviceApp (makes huge improvement in IDE responsiveness while apps are running) 2012-07-16 05:30:48 -07:00
Joe Cheng
4455810b5b Ignore Mac build directories 2012-07-13 12:23:00 -07:00
Joe Cheng
00a8372a74 Fix some Rd formatting issues 2012-07-13 02:29:19 -07:00
Joe Cheng
108dd4ff24 Add invalidateLater API call
Provides a simpler mechanism for doing time-based invalidation of reactive functions.
2012-07-13 02:29:03 -07:00
Joe Cheng
8a687851f2 Allow multiple clients to connect; doc improvements
- Multiple clients can now connect on a single port, and each one gets a unique shinyapp instance
- Improve docs for reactiveXXX functions
- Simplify interface for running an app
2012-07-13 00:29:17 -07:00
Joe Cheng
52394d61bf Add time infrastructure, reactiveTimer 2012-07-12 16:36:32 -07:00
Joe Cheng
270d97f3db Merge remote-tracking branch 'upstream/master'
Conflicts:
	shiny.Rproj
2012-07-12 10:56:20 -07:00
Joe Cheng
c5b7e549ec Pass input/output args to app func 2012-07-12 01:59:50 -07:00
Joe Cheng
891a93a7a3 Update gitignore 2012-07-12 01:49:00 -07:00
Joe Cheng
13c7800c8c Add function for getting sys time in millis 2012-07-12 01:46:02 -07:00
JJ Allaire
e89f5de680 add build type to project file 2012-07-10 12:45:30 -04:00
Joe Cheng
c4fdd04fb4 Allow running in package form 2012-07-04 14:24:58 -07:00
Joe Cheng
500501497f Roxygenize 2012-07-04 14:11:35 -07:00
Joe Cheng
4106161753 Initial pass at packaging 2012-07-02 12:15:44 -07:00
Joe Cheng
8ce5a23c4b Rename flush.react to flushReact 2012-07-02 12:03:15 -07:00
Joe Cheng
5c524af472 Use camel case for all functions and fields 2012-06-29 17:09:07 -07:00
Joe Cheng
4b1123c4e4 Simplify output API 2012-06-29 15:53:10 -07:00
Joe Cheng
c3268d0362 Simplify API 2012-06-29 09:34:15 -07:00
Joe Cheng
f3fa9883aa Don't crash on errors in callbacks 2012-06-28 22:33:21 -07:00
Joe Cheng
8cf7ec9738 Drop 'shiny' from func names; layout changes 2012-06-28 22:27:22 -07:00
Joe Cheng
7c3a92662f Make websocket URL port independent 2012-06-27 14:50:18 -07:00
Joe Cheng
e05358db1d Add summary to example 3 2012-06-27 14:43:56 -07:00
Joe Cheng
74d450703c Add script for windows 2012-06-27 11:38:33 -07:00
Joe Cheng
aee4f3780c Stop using private functions from websockets 2012-06-27 11:19:32 -07:00
Joe Cheng
3aa0702ff8 Plots can now use <img> 2012-06-27 11:10:22 -07:00
Joe Cheng
cc51dbd4e6 Improve default table styles 2012-06-27 10:44:49 -07:00
Joe Cheng
228a83e0a7 Ignore .Rhistory 2012-06-27 00:01:25 -07:00
Joe Cheng
ee1ed1e9e5 Update README, add proj file 2012-06-27 00:00:55 -07:00
Joe Cheng
6a394cc30e Remove Ruby implementation... *sniff* 2012-06-26 21:56:13 -07:00
227 changed files with 33024 additions and 1084 deletions

11
.Rbuildignore Normal file
View File

@@ -0,0 +1,11 @@
^\.Rproj\.user$
^\.git$
^examples$
^README\.md$
^shiny\.Rproj$
^shiny\.sh$
^shiny\.cmd$
^run\.R$
^\.gitignore$
^res$
^tools$

11
.gitignore vendored
View File

@@ -1,4 +1,9 @@
vendor/ruby
\.bundle/
\.DS_Store
.DS_Store
.Rproj.user
.Rhistory
.Rprofile
*.o
*.so
/src-i386/
/src-x86_64/
README.html

51
DESCRIPTION Normal file
View File

@@ -0,0 +1,51 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 0.6.0.99
Date: 2013-01-23
Author: RStudio, Inc.
Maintainer: Winston Chang <winston@rstudio.com>
Description: Shiny makes it incredibly easy to build interactive web
applications with R. Automatic "reactive" binding between inputs and
outputs and extensive pre-built widgets make it possible to build
beautiful, responsive, and powerful applications with minimal effort.
License: GPL-3
Depends:
R (>= 2.14.1)
Imports:
stats,
tools,
utils,
datasets,
methods,
httpuv (>= 1.0.6.2),
caTools,
RJSONIO,
xtable,
digest
Suggests:
markdown,
Cairo,
testthat
URL: http://www.rstudio.com/shiny/
BugReports: https://github.com/rstudio/shiny/issues
Collate:
'map.R'
'priorityqueue.R'
'utils.R'
'tar.R'
'timer.R'
'tags.R'
'cache.R'
'graph.R'
'react.R'
'reactives.R'
'fileupload.R'
'shiny.R'
'shinywrappers.R'
'shinyui.R'
'slider.R'
'bootstrap.R'
'run-url.R'
'imageutils.R'
'update-input.R'

View File

@@ -1,5 +0,0 @@
source 'https://rubygems.org'
gem 'em-websocket'
gem 'eventmachine_httpserver'
gem 'json'

View File

@@ -1,18 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.2.8)
em-websocket (0.3.6)
addressable (>= 2.1.1)
eventmachine (>= 0.12.9)
eventmachine (0.12.10)
eventmachine_httpserver (0.2.1)
json (1.7.3)
PLATFORMS
ruby
DEPENDENCIES
em-websocket
eventmachine_httpserver
json

135
NAMESPACE Normal file
View File

@@ -0,0 +1,135 @@
S3method("$",reactivevalues)
S3method("$",shinyoutput)
S3method("$<-",reactivevalues)
S3method("$<-",shinyoutput)
S3method("[",reactivevalues)
S3method("[",shinyoutput)
S3method("[<-",reactivevalues)
S3method("[<-",shinyoutput)
S3method("[[",reactivevalues)
S3method("[[",shinyoutput)
S3method("[[<-",reactivevalues)
S3method("[[<-",shinyoutput)
S3method("names<-",reactivevalues)
S3method(as.character,shiny.tag)
S3method(as.character,shiny.tag.list)
S3method(as.list,reactivevalues)
S3method(format,shiny.tag)
S3method(format,shiny.tag.list)
S3method(names,reactivevalues)
S3method(print,shiny.tag)
S3method(print,shiny.tag.list)
export(HTML)
export(a)
export(actionButton)
export(addResourcePath)
export(animationOptions)
export(basicPage)
export(bootstrapPage)
export(br)
export(checkboxGroupInput)
export(checkboxInput)
export(code)
export(conditionalPanel)
export(dateInput)
export(dateRangeInput)
export(div)
export(downloadButton)
export(downloadHandler)
export(downloadLink)
export(em)
export(exprToFunction)
export(fileInput)
export(h1)
export(h2)
export(h3)
export(h4)
export(h5)
export(h6)
export(headerPanel)
export(helpText)
export(htmlOutput)
export(imageOutput)
export(img)
export(includeCSS)
export(includeHTML)
export(includeMarkdown)
export(includeScript)
export(includeText)
export(invalidateLater)
export(isolate)
export(mainPanel)
export(numericInput)
export(observe)
export(outputOptions)
export(p)
export(pageWithSidebar)
export(parseQueryString)
export(plotOutput)
export(plotPNG)
export(pre)
export(radioButtons)
export(reactive)
export(reactivePlot)
export(reactivePrint)
export(reactiveTable)
export(reactiveText)
export(reactiveTimer)
export(reactiveUI)
export(reactiveValues)
export(reactiveValuesToList)
export(renderImage)
export(renderPlot)
export(renderPrint)
export(renderTable)
export(renderText)
export(renderUI)
export(repeatable)
export(runApp)
export(runExample)
export(runGist)
export(runGitHub)
export(runUrl)
export(selectInput)
export(shinyServer)
export(shinyUI)
export(showReactLog)
export(sidebarPanel)
export(singleton)
export(sliderInput)
export(span)
export(stopApp)
export(strong)
export(submitButton)
export(tabPanel)
export(tableOutput)
export(tabsetPanel)
export(tag)
export(tagAppendChild)
export(tagAppendChildren)
export(tagList)
export(tagSetChildren)
export(tags)
export(textInput)
export(textOutput)
export(uiOutput)
export(updateCheckboxGroupInput)
export(updateCheckboxInput)
export(updateDateInput)
export(updateDateRangeInput)
export(updateNumericInput)
export(updateRadioButtons)
export(updateSelectInput)
export(updateSliderInput)
export(updateTabsetPanel)
export(updateTextInput)
export(validateCssUnit)
export(verbatimTextOutput)
export(wellPanel)
export(withTags)
export(writeReactLog)
import(RJSONIO)
import(caTools)
import(digest)
import(httpuv)
import(xtable)

298
NEWS Normal file
View File

@@ -0,0 +1,298 @@
shiny 0.6.0.99
--------------------------------------------------------------------------------
shiny 0.6.0
--------------------------------------------------------------------------------
* `tabsetPanel()` can be directed to start with a specific tab selected.
* Fix bug where multiple file uploads with 3 or more files result in incorrect
data.
* Add `withTags()` function.
* Add dateInput and dateRangeInput.
* `shinyServer()` now takes an optional `session` argument, which is used for
communication with the session object.
* Add functions to update values of existing inputs on a page, instead of
replacing them entirely.
* Allow listening on domain sockets.
* Added `actionButton()` to Shiny.
* The server can now send custom JSON messages to the client. On the client
side, functions can be registered to handle these messages.
* Callbacks can be registered to be called at the end of a client session.
* Add ability to set priority of observers and outputs. Each priority level
gets its own queue.
* Fix bug where the presence of a submit button would prevent sending of
metadata until the button was clicked.
* `reactiveTimer()` and `invalidateLater()` by default no longer invalidate
reactive objects after the client session has closed.
* Shiny apps can be run without a server.r and ui.r file.
shiny 0.5.0
--------------------------------------------------------------------------------
* Switch from websockets package for handling websocket connections to httpuv.
* New method for detecting hidden output objects. Instead of checking that
height and width are 0, it checks that the object or any ancestor in the DOM
has style display:none.
* Add `clientData` reactive values object, which carries information about the
client. This includes the hidden status of output objects, height/width plot
output objects, and the URL of the browser.
* Add `parseQueryString()` function.
* Add `renderImage()` function for sending arbitrary image files to the client,
and its counterpart, `imageOutput()`.
* Add support for high-resolution (Retina) displays.
* Fix bug #55, where `renderTable()` would throw error with an empty data frame.
shiny 0.4.1
--------------------------------------------------------------------------------
* Fix bug where width and height weren't passed along properly from
`reactivePlot` to `renderPlot`.
* Fix bug where infinite recursion would happen when `reactivePlot` was passed
a function for width or height.
shiny 0.4.0
--------------------------------------------------------------------------------
* Added suspend/resume capability to observers.
* Output objects are automatically suspended when they are hidden on the user's
web browser.
* `runGist()` accepts GitHub's new URL format, which includes the username.
* `reactive()` and `observe()` now take expressions instead of functions.
* `reactiveText()`, `reactivePlot()`, and so on, have been renamed to
`renderText()`, `renderPlot()`, etc. They also now take expressions instead
of functions.
* Fixed a bug where empty values in a numericInput were sent to the R process
as 0. They are now sent as NA.
shiny 0.3.1
--------------------------------------------------------------------------------
* Fix issue #91: bug where downloading files did not work.
* Add [[<- operator for shinyoutput object, making it possible to assign values
with `output[['plot1']] <- ...`.
* Reactive functions now preserve the visible/invisible state of their returned
values.
shiny 0.3.0
--------------------------------------------------------------------------------
* Reactive functions are now evaluated lazily.
* Add `reactiveValues()`.
* Using `as.list()` to convert a reactivevalues object (like `input`) to a list
is deprecated. The new function `reactiveValuesToList()` should be used
instead.
* Add `isolate()`. This function is used for accessing reactive functions,
without them invalidating their parent contexts.
* Fix issue #58: bug where reactive functions are not re-run when all items in
a checkboxGroup are unchecked.
* Fix issue #71, where `reactiveTable()` would return blank if the first
element of a data frame was NA.
* In `plotOutput`, better validation for CSS units when specifying width and
height.
* `reactivePrint()` no longer displays invisible output.
* `reactiveText()` no longer displays printed output, only the return value
from a function.
* The `runGitHub()` and `runUrl()` functions have been added, for running
Shiny apps from GitHub repositories and zip/tar files at remote URLs.
* Fix issue #64, where pressing Enter in a textbox would cause a form to
submit.
shiny 0.2.4
--------------------------------------------------------------------------------
* `runGist` has been updated to use the new download URLs from
https://gist.github.com.
* Shiny now uses `CairoPNG()` for output, when the Cairo package is available.
This provides better-looking output on Linux and Windows.
shiny 0.2.3
--------------------------------------------------------------------------------
* Ignore request variables for routing purposes
shiny 0.2.2
--------------------------------------------------------------------------------
* Fix CRAN warning (assigning to global environment)
shiny 0.2.1
--------------------------------------------------------------------------------
* [BREAKING] Modify API of `downloadHandler`: The `content` function now takes
a file path, not writable connection, as an argument. This makes it much
easier to work with APIs that only write to file paths, not connections.
shiny 0.2.0
--------------------------------------------------------------------------------
* Fix subtle name resolution bug--the usual symptom being S4 methods not being
invoked correctly when called from inside of ui.R or server.R
shiny 0.1.14
--------------------------------------------------------------------------------
* Fix slider animator, which broke in 0.1.10
shiny 0.1.13
--------------------------------------------------------------------------------
* Fix temp file leak in reactivePlot
shiny 0.1.12
--------------------------------------------------------------------------------
* Fix problems with runGist on Windows
* Add feature for on-the-fly file downloads (e.g. CSV data, PDFs)
* Add CSS hooks for app-wide busy indicators
shiny 0.1.11
--------------------------------------------------------------------------------
* Fix input binding with IE8 on Shiny Server
* Fix issue #41: reactiveTable should allow print options too
* Allow dynamic sizing of reactivePlot (i.e. using a function instead of a fixed
value)
shiny 0.1.10
--------------------------------------------------------------------------------
* Support more MIME types when serving out of www
* Fix issue #35: Allow modification of untar args
* headerPanel can take an explicit window title parameter
* checkboxInput uses correct attribute `checked` instead of `selected`
* Fix plot rendering with IE8 on Shiny Server
shiny 0.1.9
--------------------------------------------------------------------------------
* Much less flicker when updating plots
* More customizable error display
* Add `includeText`, `includeHTML`, and `includeMarkdown` functions for putting
text, HTML, and Markdown content from external files in the application's UI.
shiny 0.1.8
--------------------------------------------------------------------------------
* Add `runGist` function for conveniently running a Shiny app that is published
on gist.github.com.
* Fix issue #27: Warnings cause reactive functions to stop executing.
* The server.R and ui.R filenames are now case insensitive.
* Add `wellPanel` function for creating inset areas on the page.
* Add `bootstrapPage` function for creating new Twitter Bootstrap based
layouts from scratch.
shiny 0.1.7
--------------------------------------------------------------------------------
* Fix issue #26: Shiny.OutputBindings not correctly exported.
* Add `repeatable` function for making easily repeatable versions of random
number generating functions.
* Transcode JSON into UTF-8 (prevents non-ASCII reactivePrint values from
causing errors on Windows).
shiny 0.1.6
--------------------------------------------------------------------------------
* Import package dependencies, instead of attaching them (with the exception of
websockets, which doesn't currently work unless attached).
* conditionalPanel was animated, now it is not.
* bindAll was not correctly sending initial values to the server; fixed.
shiny 0.1.5
--------------------------------------------------------------------------------
* BREAKING CHANGE: JS APIs Shiny.bindInput and Shiny.bindOutput removed and
replaced with Shiny.bindAll; Shiny.unbindInput and Shiny.unbindOutput removed
and replaced with Shiny.unbindAll.
* Add file upload support (currently only works with Chrome and Firefox). Use
a normal HTML file input, or call the `fileInput` UI function.
* Shiny.unbindOutputs did not work, now it does.
* Generally improved robustness of dynamic input/output bindings.
* Add conditionalPanel UI function to allow showing/hiding UI based on a JS
expression; for example, whether an input is a particular value. Also works in
raw HTML (add the `data-display-if` attribute to the element that should be
shown/hidden).
* htmlOutput (CSS class `shiny-html-output`) can contain inputs and outputs.
shiny 0.1.4
--------------------------------------------------------------------------------
* Allow Bootstrap tabsets to act as reactive inputs; their value indicates which
tab is active
* Upgrade to Bootstrap 2.1
* Add `checkboxGroupInput` control, which presents a list of checkboxes and
returns a vector of the selected values
* Add `addResourcePath`, intended for reusable component authors to access CSS,
JavaScript, image files, etc. from their package directories
* Add Shiny.bindInputs(scope), .unbindInputs(scope), .bindOutputs(scope), and
.unbindOutputs(scope) JS API calls to allow dynamic binding/unbinding of HTML
elements
shiny 0.1.3
--------------------------------------------------------------------------------
* Introduce Shiny.inputBindings.register JS API and InputBinding class, for
creating custom input controls
* Add `step` parameter to numericInput
* Read names of input using `names(input)`
* Access snapshot of input as a list using `as.list(input)`
* Fix issue #10: Plots in tabsets not rendered
shiny 0.1.2
--------------------------------------------------------------------------------
Initial private beta release!

1215
R/bootstrap.R Normal file

File diff suppressed because it is too large Load Diff

80
R/cache.R Normal file
View File

@@ -0,0 +1,80 @@
# A context object for tracking a cache that needs to be dirtied when a set of
# files changes on disk. Each time the cache is dirtied, the set of files is
# cleared. Therefore, the set of files needs to be re-built each time the cached
# code executes. This approach allows for dynamic dependency graphs.
CacheContext <- setRefClass(
'CacheContext',
fields = list(
.dirty = 'logical',
.tests = 'list'
),
methods = list(
initialize = function() {
.dirty <<- TRUE
# List of functions that return TRUE if dirty
.tests <<- list()
},
addDependencyFile = function(file) {
if (.dirty)
return()
file <- normalizePath(file)
mtime <- file.info(file)$mtime
.tests <<- c(.tests, function() {
newMtime <- try(file.info(file)$mtime, silent=TRUE)
if (is(newMtime, 'try-error'))
return(TRUE)
return(!identical(mtime, newMtime))
})
invisible()
},
forceDirty = function() {
.dirty <<- TRUE
.tests <<- list()
invisible()
},
isDirty = function() {
if (.dirty)
return(TRUE)
for (test in .tests) {
if (test()) {
forceDirty()
return(TRUE)
}
}
return(FALSE)
},
reset = function() {
.dirty <<- FALSE
.tests <<- list()
},
with = function(func) {
oldCC <- .currentCacheContext$cc
.currentCacheContext$cc <- .self
on.exit(.currentCacheContext$cc <- oldCC)
return(func())
}
)
)
.currentCacheContext <- new.env()
# Indicates to Shiny that the given file path is part of the dependency graph
# for whatever is currently executing (so far, only ui.R). By default, ui.R only
# gets re-executed when it is detected to have changed; this function allows the
# caller to indicate that it should also re-execute if the given file changes.
#
# If NULL or NA is given as the argument, then ui.R will re-execute next time.
dependsOnFile <- function(filepath) {
if (is.null(.currentCacheContext$cc))
return()
if (is.null(filepath) || is.na(filepath))
.currentCacheContext$cc$forceDirty()
else
.currentCacheContext$cc$addDependencyFile(filepath)
}

109
R/fileupload.R Normal file
View File

@@ -0,0 +1,109 @@
# For HTML5-capable browsers, file uploads happen through a series of requests.
#
# 1. Client tells server that one or more files are about to be uploaded; the
# server responds with a "job ID" that the client should use for the rest of
# the upload.
#
# 2. For each file (sequentially):
# a. Client tells server the name, size, and type of the file.
# b. Client sends server a small-ish blob of data.
# c. Repeat 2b until the entire file has been uploaded.
# d. Client tells server that the current file is done.
#
# 3. Repeat 2 until all files have been uploaded.
#
# 4. Client tells server that all files have been uploaded, along with the
# input ID that this data should be associated with.
#
# Unfortunately this approach will not work for browsers that don't support
# HTML5 File API, but the fallback approach we would like to use (multipart
# form upload, i.e. traditional HTTP POST-based file upload) doesn't work with
# the websockets package's HTTP server at the moment.
FileUploadOperation <- setRefClass(
'FileUploadOperation',
fields = list(
.parent = 'ANY',
.id = 'character',
.files = 'data.frame',
.dir = 'character',
.currentFileInfo = 'list',
.currentFileData = 'ANY',
.pendingFileInfos = 'list'
),
methods = list(
initialize = function(parent, id, dir, fileInfos) {
.parent <<- parent
.id <<- id
.files <<- data.frame(name=character(),
size=numeric(),
type=character(),
datapath=character(),
stringsAsFactors=FALSE)
.dir <<- dir
.pendingFileInfos <<- fileInfos
},
fileBegin = function() {
if (length(.pendingFileInfos) < 1)
stop("fileBegin called too many times")
file <- .pendingFileInfos[[1]]
.currentFileInfo <<- file
.pendingFileInfos <<- tail(.pendingFileInfos, -1)
filename <- file.path(.dir, as.character(length(.files$name)))
row <- data.frame(name=file$name, size=file$size, type=file$type,
datapath=filename, stringsAsFactors=FALSE)
if (length(.files$name) == 0)
.files <<- row
else
.files <<- rbind(.files, row)
.currentFileData <<- file(filename, open='wb')
},
fileChunk = function(rawdata) {
writeBin(rawdata, .currentFileData)
},
fileEnd = function() {
close(.currentFileData)
},
finish = function() {
if (length(.pendingFileInfos) > 0)
stop("File upload job was stopped prematurely")
.parent$onJobFinished(.id)
return(.files)
}
)
)
FileUploadContext <- setRefClass(
'FileUploadContext',
fields = list(
.basedir = 'character',
.operations = 'Map'
),
methods = list(
initialize = function(dir=tempdir()) {
.basedir <<- dir
},
createUploadOperation = function(fileInfos) {
while (TRUE) {
id <- paste(as.raw(runif(12, min=0, max=0xFF)), collapse='')
dir <- file.path(.basedir, id)
if (!dir.create(dir))
next
op <- FileUploadOperation$new(.self, id, dir, fileInfos)
.operations$set(id, op)
return(id)
}
},
getUploadOperation = function(jobId) {
.operations$get(jobId)
},
onJobFinished = function(jobId) {
.operations$remove(jobId)
}
)
)

73
R/graph.R Normal file
View File

@@ -0,0 +1,73 @@
#' @export
writeReactLog <- function(file=stdout()) {
cat(RJSONIO::toJSON(.graphEnv$log, pretty=TRUE), file=file)
}
#' @export
showReactLog <- function() {
browseURL(renderReactLog())
}
renderReactLog <- function() {
templateFile <- system.file('www/reactive-graph.html', package='shiny')
html <- paste(readLines(templateFile, warn=FALSE), collapse='\r\n')
tc <- textConnection(NULL, 'w')
on.exit(close(tc))
writeReactLog(tc)
cat('\n', file=tc)
flush(tc)
html <- sub('__DATA__', paste(textConnectionValue(tc), collapse='\r\n'), html, fixed=TRUE)
file <- tempfile(fileext = '.html')
writeLines(html, file)
return(file)
}
.graphAppend <- function(logEntry) {
if (isTRUE(getOption('shiny.reactlog', FALSE)))
.graphEnv$log <- c(.graphEnv$log, list(logEntry))
}
.graphDependsOn <- function(id, label) {
if (isTRUE(getOption('shiny.reactlog', FALSE)))
.graphAppend(list(action='dep', id=id, dependsOn=label))
}
.graphDependsOnId <- function(id, dependee) {
if (isTRUE(getOption('shiny.reactlog', FALSE)))
.graphAppend(list(action='depId', id=id, dependsOn=dependee))
}
.graphCreateContext <- function(id, label, type, prevId) {
if (isTRUE(getOption('shiny.reactlog', FALSE)))
.graphAppend(list(
action='ctx', id=id, label=paste(label, collapse='\n'), type=type, prevId=prevId
))
}
.graphEnterContext <- function(id) {
if (isTRUE(getOption('shiny.reactlog', FALSE)))
.graphAppend(list(action='enter', id=id))
}
.graphExitContext <- function(id) {
if (isTRUE(getOption('shiny.reactlog', FALSE)))
.graphAppend(list(action='exit', id=id))
}
.graphValueChange <- function(label, value) {
if (isTRUE(getOption('shiny.reactlog', FALSE))) {
.graphAppend(list(
action = 'valueChange',
id = label,
value = paste(capture.output(str(value)), collapse='\n')
))
}
}
.graphInvalidate <- function(id) {
if (isTRUE(getOption('shiny.reactlog', FALSE)))
.graphAppend(list(action='invalidate', id=id))
}
.graphEnv <- new.env()
.graphEnv$log <- list()

56
R/imageutils.R Normal file
View File

@@ -0,0 +1,56 @@
#' Run a plotting function and save the output as a PNG
#'
#' This function returns the name of the PNG file that it generates. In
#' essence, it calls \code{png()}, then \code{func()}, then \code{dev.off()}.
#' So \code{func} must be a function that will generate a plot when used this
#' way.
#'
#' For output, it will try to use the following devices, in this order:
#' quartz (via \code{\link[grDevices]{png}}), then \code{\link[Cairo]{CairoPNG}},
#' and finally \code{\link[grDevices]{png}}. This is in order of quality of
#' output. Notably, plain \code{png} output on Linux and Windows may not
#' antialias some point shapes, resulting in poor quality output.
#'
#' In some cases, \code{Cairo()} provides output that looks worse than
#' \code{png()}. To disable Cairo output for an app, use
#' \code{options(shiny.usecairo=FALSE)}.
#'
#' @param func A function that generates a plot.
#' @param filename The name of the output file. Defaults to a temp file with
#' extension \code{.png}.
#' @param width Width in pixels.
#' @param height Height in pixels.
#' @param res Resolution in pixels per inch. This value is passed to
#' \code{\link{png}}. Note that this affects the resolution of PNG rendering in
#' R; it won't change the actual ppi of the browser.
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
#' These can be used to set the width, height, background color, etc.
#'
#' @export
plotPNG <- function(func, filename=tempfile(fileext='.png'),
width=400, height=400, res=72, ...) {
# If quartz is available, use png() (which will default to quartz).
# Otherwise, if the Cairo package is installed, use CairoPNG().
# Finally, if neither quartz nor Cairo, use png().
if (capabilities("aqua")) {
pngfun <- png
} else if (getOption('shiny.usecairo', TRUE) &&
nchar(system.file(package = "Cairo"))) {
# Workaround for issue #140: Cairo ignores res and dpi settings. Need to
# use regular png function.
if (res == 72) {
pngfun <- Cairo::CairoPNG
} else {
pngfun <- png
}
} else {
pngfun <- png
}
do.call(pngfun, c(filename=filename, width=width, height=height, res=res, list(...)))
tryCatch(
func(),
finally=dev.off())
filename
}

71
R/map.R Normal file
View File

@@ -0,0 +1,71 @@
# TESTS
# Simple set/get
# Simple remove
# Simple containsKey
# Simple keys
# Simple values
# Simple clear
# Get of unknown key returns NULL
# Remove of unknown key does nothing
# Setting a key twice always results in last-one-wins
# /TESTS
Map <- setRefClass(
'Map',
fields = list(
.env = 'environment'
),
methods = list(
initialize = function() {
.env <<- new.env(parent=emptyenv())
},
get = function(key) {
if (.self$containsKey(key))
return(base::get(key, pos=.env, inherits=FALSE))
else
return(NULL)
},
set = function(key, value) {
assign(key, value, pos=.env, inherits=FALSE)
return(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)
}
return(NULL)
},
containsKey = function(key) {
exists(key, where=.env, inherits=FALSE)
},
keys = function() {
ls(envir=.env, all.names=TRUE)
},
values = function() {
mget(.self$keys(), envir=.env, inherits=FALSE)
},
clear = function() {
.env <<- new.env(parent=emptyenv())
invisible(NULL)
},
size = function() {
length(.env)
}
)
)
as.list.Map <- function(map) {
sapply(map$keys(),
map$get,
simplify=FALSE)
}
length.Map <- function(map) {
map$size()
}

110
R/priorityqueue.R Normal file
View File

@@ -0,0 +1,110 @@
# "...like a regular queue or stack data structure, but where additionally each
# element has a "priority" associated with it. In a priority queue, an element
# with high priority is served before an element with low priority. If two
# elements have the same priority, they are served according to their order in
# the queue." (http://en.wikipedia.org/wiki/Priority_queue)
PriorityQueue <- setRefClass(
'PriorityQueue',
fields = list(
# Keys are priorities, values are subqueues (implemented as list)
.itemsByPriority = 'Map',
# Sorted vector (largest first)
.priorities = 'numeric'
),
methods = list(
# Enqueue an item, with the given priority level (must be integer). Higher
# priority numbers are dequeued earlier than lower.
enqueue = function(item, priority) {
priority <- normalizePriority(priority)
if (!(priority %in% .priorities)) {
.priorities <<- c(.priorities, priority)
.priorities <<- sort(.priorities, decreasing=TRUE)
.itemsByPriority$set(.key(priority), list(item))
} else {
.itemsByPriority$set(
.key(priority),
c(.itemsByPriority$get(.key(priority)), item)
)
}
return(invisible())
},
# Retrieve a single item by 1) priority number (highest first) and then 2)
# insertion order (first in, first out). If there are no items to be
# dequeued, then NULL is returned. If it is necessary to distinguish between
# a NULL value and the empty case, call isEmpty() before dequeue().
dequeue = function() {
if (length(.priorities) == 0)
return(NULL)
maxPriority <- .priorities[[1]]
items <- .itemsByPriority$get(.key(maxPriority))
firstItem <- items[[1]]
if (length(items) == 1) {
# This is the last item at this priority. Remove both the list and the
# priority level.
.itemsByPriority$remove(.key(maxPriority))
.priorities <<- .priorities[-1]
} else {
# There are still items at this priority. Remove the current item from
# the list, and save it.
items <- items[-1]
.itemsByPriority$set(.key(maxPriority), items)
}
return(firstItem)
},
# Returns TRUE if no items are in the queue, otherwise FALSE.
isEmpty = function() {
length(.priorities) == 0
},
# Translates a priority integer to a character that is suitable for using as
# a key.
.key = function(priority) {
sprintf('%a', priority)
}
)
)
normalizePriority <- function(priority) {
if (is.null(priority))
priority <- 0
# Cast integers to numeric to prevent any inconsistencies
if (is.integer(priority))
priority <- as.numeric(priority)
if (!is.numeric(priority))
stop('priority must be an integer or numeric')
# Check length
if (length(priority) == 0) {
warning('Zero-length priority vector was passed; using 0')
priority <- 0
} else if (length(priority) > 1) {
warning('Priority has length > 1 and only the first element will be used')
priority <- priority[1]
}
# NA == 0
if (is.na(priority))
priority <- 0
return(priority)
}
# pq <- PriorityQueue$new()
# pq$enqueue('a', 1)
# pq$enqueue('b', 1L)
# pq$enqueue('c', 1)
# pq$enqueue('A', 2)
# pq$enqueue('B', 2L)
# pq$enqueue('C', 2)
# pq$enqueue('d', 1)
# pq$enqueue('e', 1L)
# pq$enqueue('f', 1)
# pq$enqueue('D', 2)
# pq$enqueue('E', 2L)
# pq$enqueue('F', 2)
# # Expect ABCDEFabcdef

350
R/react.R
View File

@@ -1,106 +1,70 @@
# TESTS
# Simple set/get
# Simple remove
# Simple contains.key
# Simple keys
# Simple values
# Simple clear
# Get of unknown key returns NULL
# Remove of unknown key does nothing
# Setting a key twice always results in last-one-wins
# /TESTS
Map <- setRefClass(
'Map',
fields = list(
.env = 'environment'
),
methods = list(
initialize = function() {
.env <<- new.env(parent=emptyenv())
},
get = function(key) {
if (.self$contains.key(key))
return(base::get(key, pos=.env, inherits=F))
else
return(NULL)
},
set = function(key, value) {
assign(key, value, pos=.env, inherits=F)
return(value)
},
remove = function(key) {
if (.self$contains.key(key)) {
result <- .self$get(key)
rm(list = key, pos=.env, inherits=F)
return(result)
}
return(NULL)
},
contains.key = function(key) {
exists(key, where=.env, inherits=F)
},
keys = function() {
ls(envir=.env, all.names=T)
},
values = function() {
mget(.self$keys(), envir=.env, inherits=F)
},
clear = function() {
.env <<- new.env(parent=emptyenv())
invisible(NULL)
},
size = function() {
length(.env)
}
)
)
as.list.Map <- function(map) {
sapply(map$keys(),
map$get,
simplify=F)
}
length.Map <- function(map) {
map$size()
}
Context <- setRefClass(
'Context',
fields = list(
id = 'character',
.label = 'character', # For debug purposes
.invalidated = 'logical',
.callbacks = 'list'
.invalidateCallbacks = 'list',
.flushCallbacks = 'list'
),
methods = list(
initialize = function() {
id <<- .get.reactive.environment()$next.id()
.invalidated <<- F
.callbacks <<- list()
initialize = function(label='', type='other', prevId='') {
id <<- .getReactiveEnvironment()$nextId()
.invalidated <<- FALSE
.invalidateCallbacks <<- list()
.flushCallbacks <<- list()
.label <<- label
.graphCreateContext(id, label, type, prevId)
},
run = function(func) {
env <- .get.reactive.environment()
old.ctx <- env$current.context(warn=F)
env$set.current.context(.self)
on.exit(env$set.current.context(old.ctx))
func()
"Run the provided function under this context."
env <- .getReactiveEnvironment()
.graphEnterContext(id)
on.exit(.graphExitContext(id))
env$runWith(.self, func)
},
invalidate = function() {
"Invalidate this context. It will immediately call the callbacks
that have been registered with onInvalidate()."
if (.invalidated)
return()
.invalidated <<- T
.get.reactive.environment()$add.pending.invalidate(.self)
.invalidated <<- TRUE
.graphInvalidate(id)
lapply(.invalidateCallbacks, function(func) {
func()
})
NULL
},
on.invalidate = function(func) {
onInvalidate = function(func) {
"Register a function to be called when this context is invalidated.
If this context is already invalidated, the function is called
immediately."
if (.invalidated)
func()
else
.callbacks <<- c(.callbacks, func)
.invalidateCallbacks <<- c(.invalidateCallbacks, func)
NULL
},
execute.callbacks = function() {
lapply(.callbacks, function(func) {
func()
addPendingFlush = function(priority) {
"Tell the reactive environment that this context should be flushed the
next time flushReact() called."
.getReactiveEnvironment()$addPendingFlush(.self, priority)
},
onFlush = function(func) {
"Register a function to be called when this context is flushed."
.flushCallbacks <<- c(.flushCallbacks, func)
},
executeFlushCallbacks = function() {
"For internal use only."
lapply(.flushCallbacks, function(func) {
withCallingHandlers({
func()
}, warning = function(e) {
# TODO: Callbacks in app
}, error = function(e) {
# TODO: Callbacks in app
})
})
}
)
@@ -108,196 +72,66 @@ Context <- setRefClass(
ReactiveEnvironment <- setRefClass(
'ReactiveEnvironment',
fields = c('.current.context', '.next.id', '.pending.invalidate'),
fields = list(
.currentContext = 'ANY',
.nextId = 'integer',
.pendingFlush = 'PriorityQueue',
.inFlush = 'logical'
),
methods = list(
initialize = function() {
.current.context <<- NULL
.next.id <<- 0L
.pending.invalidate <<- list()
.currentContext <<- NULL
.nextId <<- 0L
.pendingFlush <<- PriorityQueue$new()
.inFlush <<- FALSE
},
next.id = function() {
.next.id <<- .next.id + 1L
return(as.character(.next.id))
nextId = function() {
.nextId <<- .nextId + 1L
return(as.character(.nextId))
},
current.context = function(warn=T) {
if (warn && is.null(.current.context))
warning('No reactive context is active')
return(.current.context)
currentContext = function() {
if (is.null(.currentContext)) {
stop('Operation not allowed without an active reactive context. ',
'(You tried to do something that can only be done from inside a ',
'reactive function.)')
}
return(.currentContext)
},
set.current.context = function(ctx) {
.current.context <<- ctx
runWith = function(ctx, func) {
old.ctx <- .currentContext
.currentContext <<- ctx
on.exit(.currentContext <<- old.ctx)
func()
},
add.pending.invalidate = function(ctx) {
.pending.invalidate <<- c(.pending.invalidate, ctx)
addPendingFlush = function(ctx, priority) {
.pendingFlush$enqueue(ctx, priority)
},
flush = function() {
while (length(.pending.invalidate) > 0) {
contexts <- .pending.invalidate
.pending.invalidate <<- list()
lapply(contexts, function(ctx) {
ctx$execute.callbacks()
NULL
})
# If already in a flush, don't start another one
if (.inFlush) return()
.inFlush <<- TRUE
on.exit(.inFlush <<- FALSE)
while (!.pendingFlush$isEmpty()) {
ctx <- .pendingFlush$dequeue()
ctx$executeFlushCallbacks()
}
}
)
)
Values <- setRefClass(
'Values',
fields = list(
.values = 'environment',
.dependencies = 'environment'
),
methods = list(
initialize = function() {
.values <<- new.env(parent=emptyenv())
.dependencies <<- new.env(parent=emptyenv())
},
get = function(key) {
ctx <- .get.reactive.environment()$current.context()
dep.key <- paste(key, ':', ctx$id, sep='')
if (!exists(dep.key, where=.dependencies, inherits=F)) {
assign(dep.key, ctx, pos=.dependencies, inherits=F)
ctx$on.invalidate(function() {
rm(list=dep.key, pos=.dependencies, inherits=F)
})
}
if (!exists(key, where=.values, inherits=F))
NULL
else
base::get(key, pos=.values, inherits=F)
},
set = function(key, value) {
if (exists(key, where=.values, inherits=F)) {
if (identical(base::get(key, pos=.values, inherits=F), value)) {
return(invisible())
}
}
assign(key, value, pos=.values, inherits=F)
dep.keys <- objects(
pos=.dependencies,
pattern=paste('^\\Q', key, ':', '\\E', '\\d+$', sep='')
)
lapply(
mget(dep.keys, envir=.dependencies),
function(ctx) {
ctx$invalidate()
NULL
}
)
invisible()
},
mset = function(lst) {
lapply(names(lst),
function(name) {
.self$set(name, lst[[name]])
})
}
)
)
Observable <- setRefClass(
'Observable',
fields = c(
'.func', # function
'.dependencies', # Map
'.initialized', # logical
'.value' # any
),
methods = list(
initialize = function(func) {
.func <<- func
.dependencies <<- Map$new()
.initialized <<- F
},
get.value = function() {
if (!.initialized) {
.initialized <<- T
.self$.update.value()
}
ctx <- .get.reactive.environment()$current.context()
if (!.dependencies$contains.key(ctx$id)) {
.dependencies$set(ctx$id, ctx)
ctx$on.invalidate(function() {
.dependencies$remove(ctx$id)
})
}
return(.value)
},
.update.value = function() {
old.value <- .value
ctx <- Context$new()
ctx$on.invalidate(function() {
.self$.update.value()
})
ctx$run(function() {
.value <<- .func()
})
if (!identical(old.value, .value)) {
lapply(
.dependencies$values(),
function(dep.ctx) {
dep.ctx$invalidate()
NULL
}
)
}
}
)
)
Observer <- setRefClass(
'Observer',
fields = list(
.func = 'function'
),
methods = list(
initialize = function(func) {
.func <<- func
.self$run()
},
run = function() {
ctx <- Context$new()
ctx$on.invalidate(function() {
run()
})
ctx$run(.func)
}
)
)
.get.reactive.environment <- function() {
if (!exists('.ReactiveEnvironment', envir=.GlobalEnv, inherits=F)) {
assign('.ReactiveEnvironment', ReactiveEnvironment$new(), envir=.GlobalEnv)
}
get('.ReactiveEnvironment', envir=.GlobalEnv, inherits=F)
.reactiveEnvironment <- ReactiveEnvironment$new()
.getReactiveEnvironment <- function() {
.reactiveEnvironment
}
flush.react <- function() {
.get.reactive.environment()$flush()
# Causes any pending invalidations to run.
flushReact <- function() {
.getReactiveEnvironment()$flush()
}
test <- function () {
values <- Values$new()
obs <- Observer$new(function() {print(values$get('foo'))})
flush.react()
values$set('foo', 'bar')
flush.react()
values$set('a', 100)
values$set('b', 250)
observable <- Observable$new(function() {
values$get('a') + values$get('b')
})
obs2 <- Observer$new(function() {print(paste0('a+b: ', observable$get.value()))})
flush.react()
values$set('b', 300)
flush.react()
values$mset(list(a = 10, b = 20))
flush.react()
# Retrieves the current reactive context, or errors if there is no reactive
# context active at the moment.
getCurrentContext <- function() {
.getReactiveEnvironment()$currentContext()
}

823
R/reactives.R Normal file
View File

@@ -0,0 +1,823 @@
Dependents <- setRefClass(
'Dependents',
fields = list(
.dependents = 'Map'
),
methods = list(
register = function(depId=NULL, depLabel=NULL) {
ctx <- .getReactiveEnvironment()$currentContext()
if (!.dependents$containsKey(ctx$id)) {
.dependents$set(ctx$id, ctx)
ctx$onInvalidate(function() {
.dependents$remove(ctx$id)
})
if (!is.null(depId) && nchar(depId) > 0)
.graphDependsOnId(ctx$id, depId)
if (!is.null(depLabel))
.graphDependsOn(ctx$id, depLabel)
}
},
invalidate = function() {
lapply(
.dependents$values(),
function(ctx) {
ctx$invalidate()
NULL
}
)
}
)
)
# ReactiveValues ------------------------------------------------------------
ReactiveValues <- setRefClass(
'ReactiveValues',
fields = list(
# For debug purposes
.label = 'character',
.values = 'environment',
.dependents = 'environment',
# Dependents for the list of all names, including hidden
.namesDeps = 'Dependents',
# Dependents for all values, including hidden
.allValuesDeps = 'Dependents',
# Dependents for all values
.valuesDeps = 'Dependents'
),
methods = list(
initialize = function() {
.label <<- paste('reactiveValues', runif(1, min=1000, max=9999),
sep="")
.values <<- new.env(parent=emptyenv())
.dependents <<- new.env(parent=emptyenv())
},
get = function(key) {
ctx <- .getReactiveEnvironment()$currentContext()
dep.key <- paste(key, ':', ctx$id, sep='')
if (!exists(dep.key, where=.dependents, inherits=FALSE)) {
.graphDependsOn(ctx$id, sprintf('%s$%s', .label, key))
assign(dep.key, ctx, pos=.dependents, inherits=FALSE)
ctx$onInvalidate(function() {
rm(list=dep.key, pos=.dependents, inherits=FALSE)
})
}
if (!exists(key, where=.values, inherits=FALSE))
NULL
else
base::get(key, pos=.values, inherits=FALSE)
},
set = function(key, value) {
hidden <- substr(key, 1, 1) == "."
if (exists(key, where=.values, inherits=FALSE)) {
if (identical(base::get(key, pos=.values, inherits=FALSE), value)) {
return(invisible())
}
}
else {
.namesDeps$invalidate()
}
if (hidden)
.allValuesDeps$invalidate()
else
.valuesDeps$invalidate()
assign(key, value, pos=.values, inherits=FALSE)
.graphValueChange(sprintf('names(%s)', .label), ls(.values, all.names=TRUE))
.graphValueChange(sprintf('%s (all)', .label), as.list(.values))
.graphValueChange(sprintf('%s$%s', .label, key), value)
dep.keys <- objects(
pos=.dependents,
pattern=paste('^\\Q', key, ':', '\\E', '\\d+$', sep=''),
all.names=TRUE
)
lapply(
mget(dep.keys, envir=.dependents),
function(ctx) {
ctx$invalidate()
NULL
}
)
invisible()
},
mset = function(lst) {
lapply(base::names(lst),
function(name) {
.self$set(name, lst[[name]])
})
},
names = function() {
.graphDependsOn(.getReactiveEnvironment()$currentContext()$id,
sprintf('names(%s)', .label))
.namesDeps$register()
return(ls(.values, all.names=TRUE))
},
toList = function(all.names=FALSE) {
.graphDependsOn(.getReactiveEnvironment()$currentContext()$id,
sprintf('%s (all)', .label))
if (all.names)
.allValuesDeps$register()
.valuesDeps$register()
return(as.list(.values, all.names=all.names))
},
.setLabel = function(label) {
.label <<- label
}
)
)
# reactivevalues ------------------------------------------------------------
# S3 wrapper class for ReactiveValues reference class
#' Create an object for storing reactive values
#'
#' This function returns an object for storing reactive values. It is similar
#' to a list, but with special capabilities for reactive programming. When you
#' read a value from it, the calling reactive expression takes a reactive
#' dependency on that value, and when you write to it, it notifies any reactive
#' functions that depend on that value.
#'
#' @examples
#' # Create the object with no values
#' values <- reactiveValues()
#'
#' # Assign values to 'a' and 'b'
#' values$a <- 3
#' values[['b']] <- 4
#'
#' \dontrun{
#' # From within a reactive context, you can access values with:
#' values$a
#' values[['a']]
#' }
#'
#' # If not in a reactive context (e.g., at the console), you can use isolate()
#' # to retrieve the value:
#' isolate(values$a)
#' isolate(values[['a']])
#'
#' # Set values upon creation
#' values <- reactiveValues(a = 1, b = 2)
#' isolate(values$a)
#'
#' @param ... Objects that will be added to the reactivevalues object. All of
#' these objects must be named.
#'
#' @seealso \code{\link{isolate}}.
#'
#' @export
reactiveValues <- function(...) {
args <- list(...)
if ((length(args) > 0) && (is.null(names(args)) || any(names(args) == "")))
stop("All arguments passed to reactiveValues() must be named.")
values <- .createReactiveValues(ReactiveValues$new())
# Use .subset2() instead of [[, to avoid method dispatch
.subset2(values, 'impl')$mset(args)
values
}
# Register the S3 class so that it can be used for a field in a Reference Class
setOldClass("reactivevalues")
# Create a reactivevalues object
#
# @param values A ReactiveValues object
# @param readonly Should this object be read-only?
.createReactiveValues <- function(values = NULL, readonly = FALSE) {
structure(list(impl=values), class='reactivevalues', readonly=readonly)
}
#' @S3method $ reactivevalues
`$.reactivevalues` <- function(x, name) {
.subset2(x, 'impl')$get(name)
}
#' @S3method [[ reactivevalues
`[[.reactivevalues` <- `$.reactivevalues`
#' @S3method $<- reactivevalues
`$<-.reactivevalues` <- function(x, name, value) {
if (attr(x, 'readonly')) {
stop("Attempted to assign value to a read-only reactivevalues object")
} else if (length(name) != 1 || !is.character(name)) {
stop("Must use single string to index into reactivevalues")
} else {
.subset2(x, 'impl')$set(name, value)
x
}
}
#' @S3method [[<- reactivevalues
`[[<-.reactivevalues` <- `$<-.reactivevalues`
#' @S3method [ reactivevalues
`[.reactivevalues` <- function(values, name) {
stop("Single-bracket indexing of reactivevalues object is not allowed.")
}
#' @S3method [<- reactivevalues
`[<-.reactivevalues` <- function(values, name, value) {
stop("Single-bracket indexing of reactivevalues object is not allowed.")
}
#' @S3method names reactivevalues
names.reactivevalues <- function(x) {
.subset2(x, 'impl')$names()
}
#' @S3method names<- reactivevalues
`names<-.reactivevalues` <- function(x, value) {
stop("Can't assign names to reactivevalues object")
}
#' @S3method as.list reactivevalues
as.list.reactivevalues <- function(x, all.names=FALSE, ...) {
shinyDeprecated("reactiveValuesToList",
msg = paste("'as.list.reactivevalues' is deprecated. ",
"Use reactiveValuesToList instead.",
"\nPlease see ?reactiveValuesToList for more information.",
sep = ""))
reactiveValuesToList(x, all.names)
}
# For debug purposes
.setLabel <- function(x, label) {
.subset2(x, 'impl')$.setLabel(label)
}
#' Convert a reactivevalues object to a list
#'
#' This function does something similar to what you might \code{\link{as.list}}
#' to do. The difference is that the calling context will take dependencies on
#' every object in the reactivevalues object. To avoid taking dependencies on
#' all the objects, you can wrap the call with \code{\link{isolate}()}.
#'
#' @param x A reactivevalues object.
#' @param all.names If \code{TRUE}, include objects with a leading dot. If
#' \code{FALSE} (the default) don't include those objects.
#' @examples
#' values <- reactiveValues(a = 1)
#' \dontrun{
#' reactiveValuesToList(values)
#' }
#'
#' # To get the objects without taking dependencies on them, use isolate().
#' # isolate() can also be used when calling from outside a reactive context (e.g.
#' # at the console)
#' isolate(reactiveValuesToList(values))
#'
#' @export
reactiveValuesToList <- function(x, all.names=FALSE) {
.subset2(x, 'impl')$toList(all.names)
}
# Observable ----------------------------------------------------------------
Observable <- setRefClass(
'Observable',
fields = list(
.func = 'function',
.label = 'character',
.dependents = 'Dependents',
.invalidated = 'logical',
.running = 'logical',
.value = 'ANY',
.visible = 'logical',
.execCount = 'integer',
.mostRecentCtxId = 'character'
),
methods = list(
initialize = function(func, label=deparse(substitute(func))) {
if (length(formals(func)) > 0)
stop("Can't make a reactive expression from a function that takes one ",
"or more parameters; only functions without parameters can be ",
"reactive.")
.func <<- func
.invalidated <<- TRUE
.running <<- FALSE
.label <<- label
.execCount <<- 0L
.mostRecentCtxId <<- ""
},
getValue = function() {
.dependents$register()
if (.invalidated || .running) {
.self$.updateValue()
}
.graphDependsOnId(getCurrentContext()$id, .mostRecentCtxId)
if (identical(class(.value), 'try-error'))
stop(attr(.value, 'condition'))
if (.visible)
.value
else
invisible(.value)
},
.updateValue = function() {
ctx <- Context$new(.label, type='observable', prevId=.mostRecentCtxId)
.mostRecentCtxId <<- ctx$id
ctx$onInvalidate(function() {
.invalidated <<- TRUE
.dependents$invalidate()
})
.execCount <<- .execCount + 1L
.invalidated <<- FALSE
wasRunning <- .running
.running <<- TRUE
on.exit(.running <<- wasRunning)
ctx$run(function() {
result <- withVisible(try(.func(), silent=FALSE))
.visible <<- result$visible
.value <<- result$value
})
}
)
)
#' Create a reactive expression
#'
#' Wraps a normal expression to create a reactive expression. Conceptually, a
#' reactive expression is a expression whose result will change over time.
#'
#' Reactive expressions are expressions that can read reactive values and call other
#' reactive expressions. Whenever a reactive value changes, any reactive expressions
#' that depended on it are marked as "invalidated" and will automatically
#' re-execute if necessary. If a reactive expression is marked as invalidated, any
#' other reactive expressions that recently called it are also marked as
#' invalidated. In this way, invalidations ripple through the expressions that
#' depend on each other.
#'
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{Shiny tutorial} for
#' more information about reactive expressions.
#'
#' @param x An expression (quoted or unquoted).
#' @param env The parent environment for the reactive expression. By default, this
#' is the calling environment, the same as when defining an ordinary
#' non-reactive expression.
#' @param quoted Is the expression quoted? By default, this is \code{FALSE}.
#' This is useful when you want to use an expression that is stored in a
#' variable; to do so, it must be quoted with `quote()`.
#' @param label A label for the reactive expression, useful for debugging.
#'
#' @examples
#' values <- reactiveValues(A=1)
#'
#' reactiveB <- reactive({
#' values$A + 1
#' })
#'
#' # Can use quoted expressions
#' reactiveC <- reactive(quote({ values$A + 2 }), quoted = TRUE)
#'
#' # To store expressions for later conversion to reactive, use quote()
#' expr_q <- quote({ values$A + 3 })
#' reactiveD <- reactive(expr_q, quoted = TRUE)
#'
#' # View the values from the R console with isolate()
#' isolate(reactiveB())
#' isolate(reactiveC())
#' isolate(reactiveD())
#'
#' @export
reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL) {
fun <- exprToFunction(x, env, quoted)
if (is.null(label))
label <- sprintf('reactive(%s)', paste(deparse(body(fun)), collapse='\n'))
Observable$new(fun, label)$getValue
}
# Return the number of times that a reactive expression or observer has been run
execCount <- function(x) {
if (is.function(x))
return(environment(x)$.execCount)
else if (is(x, 'Observer'))
return(x$.execCount)
else
stop('Unexpected argument to execCount')
}
# Observer ------------------------------------------------------------------
Observer <- setRefClass(
'Observer',
fields = list(
.func = 'function',
.label = 'character',
.priority = 'numeric',
.invalidateCallbacks = 'list',
.execCount = 'integer',
.onResume = 'function',
.suspended = 'logical',
.prevId = 'character'
),
methods = list(
initialize = function(func, label, suspended = FALSE, priority = 0) {
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
.priority <<- normalizePriority(priority)
.execCount <<- 0L
.suspended <<- suspended
.onResume <<- function() NULL
.prevId <<- ''
# Defer the first running of this until flushReact is called
.createContext()$invalidate()
},
.createContext = function() {
ctx <- Context$new(.label, type='observer', prevId=.prevId)
.prevId <<- ctx$id
ctx$onInvalidate(function() {
lapply(.invalidateCallbacks, function(func) {
func()
NULL
})
continue <- function() {
ctx$addPendingFlush(.priority)
}
if (.suspended == FALSE)
continue()
else
.onResume <<- continue
})
ctx$onFlush(function() {
run()
})
return(ctx)
},
run = function() {
ctx <- .createContext()
.execCount <<- .execCount + 1L
ctx$run(.func)
},
onInvalidate = function(callback) {
"Register a callback function to run when this observer is invalidated.
No arguments will be provided to the callback function when it is
invoked."
.invalidateCallbacks <<- c(.invalidateCallbacks, callback)
},
setPriority = function(priority = 0) {
"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
which case the priority change will be effective upon resume."
.priority <<- normalizePriority(priority)
},
suspend = function() {
"Causes this observer to stop scheduling flushes (re-executions) in
response to invalidations. If the observer was invalidated prior to this
call but it has not re-executed yet (because it waits until onFlush is
called) then that re-execution will still occur, because the flush is
already scheduled."
.suspended <<- TRUE
},
resume = function() {
"Causes this observer to start re-executing in response to invalidations.
If the observer was invalidated while suspended, then it will schedule
itself for re-execution (pending flush)."
if (.suspended) {
.suspended <<- FALSE
.onResume()
.onResume <<- function() NULL
}
invisible()
}
)
)
#' 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
#' non-reactive expression.
#' @param quoted Is the expression quoted? By default, this is \code{FALSE}.
#' This is useful when you want to use an expression that is stored in a
#' variable; to do so, it must be quoted with `quote()`.
#' @param label A label for the observer, useful for debugging.
#' @param suspended If \code{TRUE}, start the observer in a suspended state.
#' If \code{FALSE} (the default), start in a non-suspended state.
#' @param priority An integer or numeric that controls the priority with which
#' this observer should be executed. An observer with a given priority level
#' will always execute sooner than all observers with a lower priority level.
#' Positive, negative, and zero values are allowed.
#' @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
#' this call but it has not re-executed yet then that re-execution will
#' still occur, because the flush is already scheduled.
#' }
#' \item{\code{resume()}}{
#' Causes this observer to start re-executing in response to
#' invalidations. If the observer was invalidated while suspended, then it
#' will schedule itself for re-execution.
#' }
#' \item{\code{setPriority(priority = 0)}}{
#' 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
#' which case the priority change will be effective upon resume.
#' }
#' \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
#' invoked.
#' }
#' }
#'
#' @examples
#' values <- reactiveValues(A=1)
#'
#' obsB <- observe({
#' print(values$A + 1)
#' })
#'
#' # Can use quoted expressions
#' obsC <- observe(quote({ print(values$A + 2) }), quoted = TRUE)
#'
#' # To store expressions for later conversion to observe, use quote()
#' expr_q <- quote({ print(values$A + 3) })
#' obsD <- observe(expr_q, quoted = TRUE)
#'
#' # In a normal Shiny app, the web client will trigger flush events. If you
#' # are at the console, you can force a flush with flushReact()
#' shiny:::flushReact()
#'
#' @export
observe <- function(x, env=parent.frame(), quoted=FALSE, label=NULL,
suspended=FALSE, priority=0) {
fun <- exprToFunction(x, env, quoted)
if (is.null(label))
label <- sprintf('observe(%s)', paste(deparse(body(fun)), collapse='\n'))
invisible(Observer$new(
fun, label=label, suspended=suspended, priority=priority))
}
# ---------------------------------------------------------------------------
#' Timer
#'
#' 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
#' 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
#' current time (as in \code{\link{Sys.time}}).
#' @seealso invalidateLater
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' # Anything that calls autoInvalidate will automatically invalidate
#' # every 2 seconds.
#' autoInvalidate <- reactiveTimer(2000, session)
#'
#' observe({
#' # Invalidate and re-execute this reactive expression every time the
#' # timer fires.
#' autoInvalidate()
#'
#' # Do something each time this is invalidated.
#' # The isolate() makes this observer _not_ get invalidated and re-executed
#' # when input$n changes.
#' print(paste("The value of input$n is", isolate(input$n)))
#' })
#'
#' # Generate a new histogram each time the timer fires, but not when
#' # input$n changes.
#' output$plot <- renderPlot({
#' autoInvalidate()
#' hist(isolate(input$n))
#' })
#' })
#' }
#'
#' @export
reactiveTimer <- function(intervalMs=1000, session) {
if (missing(session)) {
warning("reactiveTimer should be passed a session object or NULL")
session <- NULL
}
dependents <- Map$new()
timerCallbacks$schedule(intervalMs, function() {
# Quit if the session is closed
if (!is.null(session) && session$isClosed()) {
return(invisible())
}
timerCallbacks$schedule(intervalMs, sys.function())
lapply(
dependents$values(),
function(dep.ctx) {
dep.ctx$invalidate()
NULL
})
})
return(function() {
ctx <- .getReactiveEnvironment()$currentContext()
if (!dependents$containsKey(ctx$id)) {
dependents$set(ctx$id, ctx)
ctx$onInvalidate(function() {
dependents$remove(ctx$id)
})
}
return(Sys.time())
})
}
#' Scheduled Invalidation
#'
#' Schedules the current reactive context to be invalidated in the given number
#' of milliseconds.
#' @param millis Approximate milliseconds to wait before invalidating the
#' current reactive context.
#' @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.
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # Re-execute this reactive expression after 1000 milliseconds
#' invalidateLater(1000, session)
#'
#' # Do something each time this is invalidated.
#' # The isolate() makes this observer _not_ get invalidated and re-executed
#' # when input$n changes.
#' print(paste("The value of input$n is", isolate(input$n)))
#' })
#'
#' # Generate a new histogram at timed intervals, but not when
#' # input$n changes.
#' output$plot <- renderPlot({
#' # Re-execute this reactive expression after 2000 milliseconds
#' invalidateLater(2000, session)
#' hist(isolate(input$n))
#' })
#' })
#' }
#'
#' @export
invalidateLater <- function(millis, session) {
if (missing(session)) {
warning("invalidateLater should be passed a session object or NULL")
session <- NULL
}
ctx <- .getReactiveEnvironment()$currentContext()
timerCallbacks$schedule(millis, function() {
# Quit if the session is closed
if (!is.null(session) && session$isClosed()) {
return(invisible())
}
ctx$invalidate()
})
invisible()
}
#' 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
#' 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}
#' 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()}.
#' If you want to avoid this, you can use \code{\link{local}()} inside the
#' \code{isolate()}.
#'
#' This function can also be useful for calling reactive expression at the
#' console, which can be useful for debugging. To do so, simply wrap the
#' 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
#' b <- input$valueB
#' c(a=a, b=b)
#' })
#' writeToDatabase(data)
#' })
#'
#' observe({
#' x <- 1
#' # x outside of isolate() is affected
#' isolate(x <- 2)
#' print(x) # 2
#'
#' y <- 1
#' # Use local() to avoid affecting calling environment
#' isolate(local(y <- 2))
#' print(y) # 1
#' })
#'
#' }
#'
#' # Can also use isolate to call reactive expressions from the R console
#' values <- reactiveValues(A=1)
#' fun <- reactive({ as.character(values$A) })
#' isolate(fun())
#' # "1"
#'
#' # isolate also works if the reactive expression accesses values from the
#' # input object, like input$x
#'
#' @export
isolate <- function(expr) {
ctx <- Context$new('[isolate]', type='isolate')
ctx$run(function() {
expr
})
ctx$invalidate()
}

164
R/run-url.R Normal file
View File

@@ -0,0 +1,164 @@
#' Run a Shiny application from https://gist.github.com
#'
#' Download and launch a Shiny application that is hosted on GitHub as a gist.
#'
#' @param gist The identifier of the gist. For example, if the gist is
#' https://gist.github.com/jcheng5/3239667, then \code{3239667},
#' \code{'3239667'}, and \code{'https://gist.github.com/jcheng5/3239667'}
#' are all valid values.
#' @param port The TCP port that the application should listen on. Defaults to
#' port 8100.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
#'
#' @examples
#' \dontrun{
#' runGist(3239667)
#' runGist("https://gist.github.com/jcheng5/3239667")
#'
#' # Old URL format without username
#' runGist("https://gist.github.com/3239667")
#' }
#'
#' @export
runGist <- function(gist,
port=8100L,
launch.browser=getOption('shiny.launch.browser',
interactive())) {
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
sprintf('https://gist.github.com/%s/download', gist)
} else if(grepl('^https://gist.github.com/([^/]+/)?([0-9a-f]+)$', gist)) {
paste(gist, '/download', sep='')
} else {
stop('Unrecognized gist identifier format')
}
runUrl(gistUrl, filetype=".tar.gz", subdir=NULL, port=port,
launch.browser=launch.browser)
}
#' Run a Shiny application from a GitHub repository
#'
#' Download and launch a Shiny application that is hosted in a GitHub repository.
#'
#' @param repo Name of the repository
#' @param username GitHub username
#' @param ref Desired git reference. Could be a commit, tag, or branch
#' name. Defaults to \code{"master"}.
#' @param subdir A subdirectory in the repository that contains the app. By
#' default, this function will run an app from the top level of the repo, but
#' you can use a path such as `\code{"inst/shinyapp"}.
#' @param port The TCP port that the application should listen on. Defaults to
#' port 8100.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
#'
#' @examples
#' \dontrun{
#' runGitHub("shiny_example", "rstudio")
#'
#' # Can run an app from a subdirectory in the repo
#' runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
#' }
#'
#' @export
runGitHub <- function(repo, username = getOption("github.user"),
ref = "master", subdir = NULL, port = 8100,
launch.browser = getOption('shiny.launch.browser', interactive())) {
if (is.null(ref)) {
stop("Must specify either a ref. ")
}
message("Downloading github repo(s) ",
paste(repo, ref, sep = "/", collapse = ", "),
" from ",
paste(username, collapse = ", "))
name <- paste(username, "-", repo, sep = "")
url <- paste("https://github.com/", username, "/", repo, "/archive/",
ref, ".tar.gz", sep = "")
runUrl(url, subdir=subdir, port=port, launch.browser=launch.browser)
}
#' Run a Shiny application from a URL
#'
#' Download and launch a Shiny application that is hosted at a downloadable
#' URL. The Shiny application must be saved in a .zip, .tar, or .tar.gz file.
#' The Shiny application files must be contained in a subdirectory in the
#' archive. For example, the files might be \code{myapp/server.r} and
#' \code{myapp/ui.r}.
#'
#' @param url URL of the application.
#' @param filetype The file type (\code{".zip"}, \code{".tar"}, or
#' \code{".tar.gz"}. Defaults to the file extension taken from the url.
#' @param subdir A subdirectory in the repository that contains the app. By
#' default, this function will run an app from the top level of the repo, but
#' you can use a path such as `\code{"inst/shinyapp"}.
#' @param port The TCP port that the application should listen on. Defaults to
#' port 8100.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
#'
#' @examples
#' \dontrun{
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
#'
#' # Can run an app from a subdirectory in the archive
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
#' subdir = "inst/shinyapp/")
#' }
#'
#' @export
runUrl <- function(url, filetype = NULL, subdir = NULL, port = 8100,
launch.browser = getOption('shiny.launch.browser', interactive())) {
if (!is.null(subdir) && ".." %in% strsplit(subdir, '/')[[1]])
stop("'..' not allowed in subdir")
if (is.null(filetype))
filetype <- basename(url)
if (grepl("\\.tar\\.gz$", filetype))
fileext <- ".tar.gz"
else if (grepl("\\.tar$", filetype))
fileext <- ".tar"
else if (grepl("\\.zip$", filetype))
fileext <- ".zip"
else
stop("Unknown file extension.")
message("Downloading ", url)
filePath <- tempfile('shinyapp', fileext=fileext)
if (download(url, filePath, mode = "wb", quiet = TRUE) != 0)
stop("Failed to download URL ", url)
on.exit(unlink(filePath))
if (fileext %in% c(".tar", ".tar.gz")) {
# Regular untar commonly causes two problems on Windows with github tarballs:
# 1) If RTools' tar.exe is in the path, you get cygwin path warnings which
# throw list=TRUE off;
# 2) If the internal untar implementation is used, it chokes on the 'g'
# type flag that github uses (to stash their commit hash info).
# By using our own forked/modified untar2 we sidestep both issues.
dirname <- untar2(filePath, list=TRUE)[1]
untar2(filePath, exdir = dirname(filePath))
} else if (fileext == ".zip") {
dirname <- as.character(unzip(filePath, list=TRUE)$Name[1])
unzip(filePath, exdir = dirname(filePath))
}
appdir <- file.path(dirname(filePath), dirname)
on.exit(unlink(appdir, recursive = TRUE), add = TRUE)
appsubdir <- ifelse(is.null(subdir), appdir, file.path(appdir, subdir))
runApp(appsubdir, port=port, launch.browser=launch.browser)
}

1510
R/shiny.R

File diff suppressed because it is too large Load Diff

252
R/shinyui.R Normal file
View File

@@ -0,0 +1,252 @@
#' @export
p <- function(...) tags$p(...)
#' @export
h1 <- function(...) tags$h1(...)
#' @export
h2 <- function(...) tags$h2(...)
#' @export
h3 <- function(...) tags$h3(...)
#' @export
h4 <- function(...) tags$h4(...)
#' @export
h5 <- function(...) tags$h5(...)
#' @export
h6 <- function(...) tags$h6(...)
#' @export
a <- function(...) tags$a(...)
#' @export
br <- function(...) tags$br(...)
#' @export
div <- function(...) tags$div(...)
#' @export
span <- function(...) tags$span(...)
#' @export
pre <- function(...) tags$pre(...)
#' @export
code <- function(...) tags$code(...)
#' @export
img <- function(...) tags$img(...)
#' @export
strong <- function(...) tags$strong(...)
#' @export
em <- function(...) tags$em(...)
#' Include Content 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.
#'
#' @export
singleton <- function(x) {
class(x) <- c(class(x), 'shiny.singleton')
return(x)
}
renderPage <- function(ui, connection) {
# provide a filter so we can intercept head tag requests
context <- new.env()
context$head <- character()
context$singletons <- character()
context$filter <- function(content) {
if (inherits(content, 'shiny.singleton')) {
sig <- digest(content, algo='sha1')
if (sig %in% context$singletons)
return(FALSE)
context$singletons <- c(sig, context$singletons)
}
if (isTag(content) && identical(content$name, "head")) {
textConn <- textConnection(NULL, "w")
textConnWriter <- function(text) cat(text, file = textConn)
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 <- textConnectionValue(textConn)
close(textConn)
# write preamble
writeLines(c('<!DOCTYPE html>',
'<html>',
'<head>',
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>',
' <script src="shared/jquery.js" type="text/javascript"></script>',
' <script src="shared/shiny.js" type="text/javascript"></script>',
' <link rel="stylesheet" type="text/css" href="shared/shiny.css"/>',
context$head,
'</head>',
'<body>',
recursive=TRUE),
con = connection)
# write UI html to connection
writeLines(uiHTML, con = connection)
# write end document
writeLines(c('</body>',
'</html>'),
con = connection)
}
#' Create a Shiny UI handler
#'
#' Register a UI handler by providing a UI definition (created with e.g.
#' \link{pageWithSidebar}) and web server path (typically "/", the default
#' value).
#'
#' @param ui A user-interace definition
#' @param path The web server path to server the UI from
#' @return Called for its side-effect of registering a UI handler
#'
#' @examples
#' el <- div(HTML("I like <u>turtles</u>"))
#' cat(as.character(el))
#'
#' @examples
#' # Define UI
#' shinyUI(pageWithSidebar(
#'
#' # Application title
#' headerPanel("Hello Shiny!"),
#'
#' # Sidebar with a slider input
#' sidebarPanel(
#' sliderInput("obs",
#' "Number of observations:",
#' min = 0,
#' max = 1000,
#' value = 500)
#' ),
#'
#' # Show a plot of the generated distribution
#' mainPanel(
#' plotOutput("distPlot")
#' )
#' ))
#'
#' @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))
renderPage(ui, textConn)
html <- paste(textConnectionValue(textConn), collapse='\n')
return(httpResponse(200, content=html))
}
})
}

489
R/shinywrappers.R Normal file
View File

@@ -0,0 +1,489 @@
suppressPackageStartupMessages({
library(caTools)
library(xtable)
})
#' Plot Output
#'
#' Renders a reactive plot that is suitable for assigning to an \code{output}
#' slot.
#'
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
#' the CSS class name \code{shiny-plot-output}.
#'
#' @seealso For more details on how the plots are generated, and how to control
#' the output, see \code{\link{plotPNG}}.
#'
#' @param expr An expression that generates a plot.
#' @param width The width of the rendered plot, in pixels; or \code{'auto'} to
#' use the \code{offsetWidth} of the HTML element that is bound to this plot.
#' You can also pass in a function that returns the width in pixels or
#' \code{'auto'}; in the body of the function you may reference reactive
#' values and functions.
#' @param height The height of the rendered plot, in pixels; or \code{'auto'} to
#' use the \code{offsetHeight} of the HTML element that is bound to this plot.
#' You can also pass in a function that returns the width in pixels or
#' \code{'auto'}; in the body of the function you may reference reactive
#' values and functions.
#' @param res Resolution of resulting plot, in pixels per inch. This value is
#' passed to \code{\link{png}}. Note that this affects the resolution of PNG
#' rendering in R; it won't change the actual ppi of the browser.
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
#' These can be used to set the width, height, background color, etc.
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' is useful if you want to save an expression in a variable.
#' @param func A function that generates a plot (deprecated; use \code{expr}
#' instead).
#'
#' @export
renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
env=parent.frame(), quoted=FALSE, func=NULL) {
if (!is.null(func)) {
shinyDeprecated(msg="renderPlot: argument 'func' is deprecated. Please use 'expr' instead.")
} else {
func <- exprToFunction(expr, env, quoted)
}
args <- list(...)
if (is.function(width))
widthWrapper <- reactive({ width() })
else
widthWrapper <- NULL
if (is.function(height))
heightWrapper <- reactive({ height() })
else
heightWrapper <- NULL
return(function(shinysession, name, ...) {
if (!is.null(widthWrapper))
width <- widthWrapper()
if (!is.null(heightWrapper))
height <- heightWrapper()
# Note that these are reactive calls. A change to the width and height
# will inherently cause a reactive plot to redraw (unless width and
# height were explicitly specified).
prefix <- 'output_'
if (width == 'auto')
width <- shinysession$clientData[[paste(prefix, name, '_width', sep='')]];
if (height == 'auto')
height <- shinysession$clientData[[paste(prefix, name, '_height', sep='')]];
if (is.null(width) || is.null(height) || width <= 0 || height <= 0)
return(NULL)
# Resolution multiplier
pixelratio <- shinysession$clientData$pixelratio
if (is.null(pixelratio))
pixelratio <- 1
outfile <- do.call(plotPNG, c(func, 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))
})
}
#' Image file output
#'
#' Renders a reactive image that is suitable for assigning to an \code{output}
#' slot.
#'
#' The expression \code{expr} must return a list containing the attributes for
#' the \code{img} object on the client web page. For the image to display,
#' properly, the list must have at least one entry, \code{src}, which is the
#' path to the image file. It may also useful to have a \code{contentType}
#' entry specifying the MIME type of the image. If one is not provided,
#' \code{renderImage} will try to autodetect the type, based on the file
#' extension.
#'
#' Other elements such as \code{width}, \code{height}, \code{class}, and
#' \code{alt}, can also be added to the list, and they will be used as
#' attributes in the \code{img} object.
#'
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
#' the CSS class name \code{shiny-image-output}.
#'
#' @seealso For more details on how the images are generated, and how to control
#' the output, see \code{\link{plotPNG}}.
#'
#' @param expr An expression that returns a list.
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' is useful if you want to save an expression in a variable.
#' @param deleteFile Should the file in \code{func()$src} be deleted after
#' it is sent to the client browser? Genrrally speaking, if the image is a
#' temp file generated within \code{func}, then this should be \code{TRUE};
#' if the image is not a temp file, this should be \code{FALSE}.
#'
#' @export
#'
#' @examples
#' \dontrun{
#'
#' shinyServer(function(input, output, clientData) {
#'
#' # A plot of fixed size
#' output$plot1 <- renderImage({
#' # A temp file to save the output. It will be deleted after renderImage
#' # sends it, because deleteFile=TRUE.
#' outfile <- tempfile(fileext='.png')
#'
#' # Generate a png
#' png(outfile, width=400, height=400)
#' hist(rnorm(input$n))
#' dev.off()
#'
#' # Return a list
#' list(src = outfile,
#' alt = "This is alternate text")
#' }, deleteFile = TRUE)
#'
#' # A dynamically-sized plot
#' output$plot2 <- renderImage({
#' # Read plot2's width and height. These are reactive values, so this
#' # expression will re-run whenever these values change.
#' width <- clientData$output_plot2_width
#' height <- clientData$output_plot2_height
#'
#' # A temp file to save the output.
#' outfile <- tempfile(fileext='.png')
#'
#' png(outfile, width=width, height=height)
#' hist(rnorm(input$obs))
#' dev.off()
#'
#' # Return a list containing the filename
#' list(src = outfile,
#' width = width,
#' height = height,
#' alt = "This is alternate text")
#' }, deleteFile = TRUE)
#'
#' # Send a pre-rendered image, and don't delete the image after sending it
#' output$plot3 <- renderImage({
#' # When input$n is 1, filename is ./images/image1.jpeg
#' filename <- normalizePath(file.path('./images',
#' paste('image', input$n, '.jpeg', sep='')))
#'
#' # Return a list containing the filename
#' list(src = filename)
#' }, deleteFile = FALSE)
#' })
#'
#' }
renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
deleteFile=TRUE) {
func <- exprToFunction(expr, env, quoted)
return(function(shinysession, name, ...) {
imageinfo <- func()
# Should the file be deleted after being sent? If .deleteFile not set or if
# TRUE, then delete; otherwise don't delete.
if (deleteFile) {
on.exit(unlink(imageinfo$src))
}
# If contentType not specified, autodetect based on extension
if (is.null(imageinfo$contentType)) {
contentType <- getContentType(sub('^.*\\.', '', basename(imageinfo$src)))
} else {
contentType <- imageinfo$contentType
}
# Extra values are everything in imageinfo except 'src' and 'contentType'
extra_attr <- imageinfo[!names(imageinfo) %in% c('src', 'contentType')]
# Return a list with src, and other img attributes
c(src = shinysession$fileUrl(name, file=imageinfo$src, contentType=contentType),
extra_attr)
})
}
#' Table Output
#'
#' Creates a reactive table that is suitable for assigning to an \code{output}
#' slot.
#'
#' The corresponding HTML output tag should be \code{div} and have the CSS class
#' name \code{shiny-html-output}.
#'
#' @param expr An expression that returns an R object that can be used with
#' \code{\link[xtable]{xtable}}.
#' @param ... Arguments to be passed through to \code{\link[xtable]{xtable}} and
#' \code{\link[xtable]{print.xtable}}.
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' is useful if you want to save an expression in a variable.
#' @param func A function that returns an R object that can be used with
#' \code{\link[xtable]{xtable}} (deprecated; use \code{expr} instead).
#'
#' @export
renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL) {
if (!is.null(func)) {
shinyDeprecated(msg="renderTable: argument 'func' is deprecated. Please use 'expr' instead.")
} else {
func <- exprToFunction(expr, env, quoted)
}
function() {
classNames <- getOption('shiny.table.class', 'data table table-bordered table-condensed')
data <- func()
if (is.null(data) || identical(data, data.frame()))
return("")
return(paste(
capture.output(
print(xtable(data, ...),
type='html',
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
#' for assigning to an \code{output} slot.
#'
#' The corresponding HTML output tag can be anything (though \code{pre} is
#' recommended if you need a monospace font and whitespace preserved) and should
#' have the CSS class name \code{shiny-text-output}.
#'
#' The result of executing \code{func} will be printed inside a
#' \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.
#' To display nothing, make your function return \code{\link{invisible}()}.
#'
#' @param expr An expression that may print output and/or return a printable R
#' object.
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' @param func A function that may print output and/or return a printable R
#' object (deprecated; use \code{expr} instead).
#'
#' @seealso \code{\link{renderText}} for displaying the value returned from a
#' function, instead of the printed output.
#'
#' @example res/text-example.R
#'
#' @export
renderPrint <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
if (!is.null(func)) {
shinyDeprecated(msg="renderPrint: argument 'func' is deprecated. Please use 'expr' instead.")
} else {
func <- exprToFunction(expr, env, quoted)
}
function() {
return(paste(capture.output({
result <- withVisible(func())
if (result$visible)
print(result$value)
}), collapse="\n"))
}
}
#' Text Output
#'
#' Makes a reactive version of the given function that also uses
#' \code{\link[base]{cat}} to turn its result into a single-element character
#' vector.
#'
#' 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
#' \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}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' is useful if you want to save an expression in a variable.
#' @param func A function that returns an R object that can be used as an
#' argument to \code{cat}.(deprecated; use \code{expr} instead).
#'
#' @seealso \code{\link{renderPrint}} for capturing the print output of a
#' function, rather than the returned text value.
#'
#' @example res/text-example.R
#'
#' @export
renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
if (!is.null(func)) {
shinyDeprecated(msg="renderText: argument 'func' is deprecated. Please use 'expr' instead.")
} else {
func <- exprToFunction(expr, env, quoted)
}
function() {
value <- func()
return(paste(capture.output(cat(value)), collapse="\n"))
}
}
#' UI Output
#'
#' \bold{Experimental feature.} Makes a reactive version of a function that
#' generates HTML using the Shiny UI library.
#'
#' The corresponding HTML output tag should be \code{div} and have the CSS class
#' name \code{shiny-html-output} (or use \code{\link{uiOutput}}).
#'
#' @param expr An expression that returns a Shiny tag object, \code{\link{HTML}},
#' or a list of such objects.
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' is useful if you want to save an expression in a variable.
#' @param func A function that returns a Shiny tag object, \code{\link{HTML}},
#' or a list of such objects (deprecated; use \code{expr} instead).
#'
#' @seealso conditionalPanel
#'
#' @export
#' @examples
#' \dontrun{
#' output$moreControls <- renderUI({
#' list(
#'
#' )
#' })
#' }
renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
if (!is.null(func)) {
shinyDeprecated(msg="renderUI: argument 'func' is deprecated. Please use 'expr' instead.")
} else {
func <- exprToFunction(expr, env, quoted)
}
function() {
result <- func()
if (is.null(result) || length(result) == 0)
return(NULL)
# Wrap result in tagList in case it is an ordinary list
return(as.character(tagList(result)))
}
}
#' File Downloads
#'
#' Allows content from the Shiny application to be made available to the user as
#' file downloads (for example, downloading the currently visible data as a CSV
#' file). Both filename and contents can be calculated dynamically at the time
#' the user initiates the download. Assign the return value to a slot on
#' \code{output} in your server function, and in the UI use
#' \code{\link{downloadButton}} or \code{\link{downloadLink}} to make the
#' download available.
#'
#' @param filename A string of the filename, including extension, that the
#' user's web browser should default to when downloading the file; or a
#' function that returns such a string. (Reactive values and functions may be
#' used from this function.)
#' @param content A function that takes a single argument \code{file} that is a
#' file path (string) of a nonexistent temp file, and writes the content to
#' that file path. (Reactive values and functions may be used from this
#' function.)
#' @param contentType A string of the download's
#' \href{http://en.wikipedia.org/wiki/Internet_media_type}{content type}, for
#' example \code{"text/csv"} or \code{"image/png"}. If \code{NULL} or
#' \code{NA}, the content type will be guessed based on the filename
#' extension, or \code{application/octet-stream} if the extension is unknown.
#'
#' @examples
#' \dontrun{
#' # In server.R:
#' output$downloadData <- downloadHandler(
#' filename = function() {
#' paste('data-', Sys.Date(), '.csv', sep='')
#' },
#' content = function(file) {
#' write.csv(data, file)
#' }
#' )
#'
#' # In ui.R:
#' downloadLink('downloadData', 'Download')
#' }
#'
#' @export
downloadHandler <- function(filename, content, contentType=NA) {
return(function(shinysession, name, ...) {
shinysession$registerDownload(name, filename, contentType, content)
})
}
# Deprecated functions ------------------------------------------------------
#' Plot output (deprecated)
#'
#' See \code{\link{renderPlot}}.
#' @param func A function.
#' @param width Width.
#' @param height Height.
#' @param ... Other arguments to pass on.
#' @export
reactivePlot <- function(func, width='auto', height='auto', ...) {
shinyDeprecated(new="renderPlot")
renderPlot({ func() }, width=width, height=height, ...)
}
#' Table output (deprecated)
#'
#' See \code{\link{renderTable}}.
#' @param func A function.
#' @param ... Other arguments to pass on.
#' @export
reactiveTable <- function(func, ...) {
shinyDeprecated(new="renderTable")
renderTable({ func() })
}
#' Print output (deprecated)
#'
#' See \code{\link{renderPrint}}.
#' @param func A function.
#' @export
reactivePrint <- function(func) {
shinyDeprecated(new="renderPrint")
renderPrint({ func() })
}
#' UI output (deprecated)
#'
#' See \code{\link{renderUI}}.
#' @param func A function.
#' @export
reactiveUI <- function(func) {
shinyDeprecated(new="renderUI")
renderUI({ func() })
}
#' Text output (deprecated)
#'
#' See \code{\link{renderText}}.
#' @param func A function.
#' @export
reactiveText <- function(func) {
shinyDeprecated(new="renderText")
renderText({ func() })
}

133
R/slider.R Normal file
View File

@@ -0,0 +1,133 @@
hasDecimals <- function(value) {
truncatedValue <- round(value)
return (!identical(value, truncatedValue))
}
#' Animation Options
#'
#' Creates an options object for customizing animations for \link{sliderInput}.
#'
#' @param interval The interval, in milliseconds, between each animation step.
#' @param loop \code{TRUE} to automatically restart the animation when it
#' reaches the end.
#' @param playButton Specifies the appearance of the play button. Valid values
#' are a one-element character vector (for a simple text label), an HTML tag
#' or list of tags (using \code{\link{tag}} and friends), or raw HTML (using
#' \code{\link{HTML}}).
#' @param pauseButton Similar to \code{playButton}, but for the pause button.
#'
#' @export
animationOptions <- function(interval=1000,
loop=FALSE,
playButton=NULL,
pauseButton=NULL) {
list(interval=interval,
loop=loop,
playButton=playButton,
pauseButton=pauseButton)
}
# Create a new slider control (list of slider input element and the script
# tag used to configure it). This is a lower level control that should
# be wrapped in an "input" construct (e.g. sliderInput in bootstrap.R)
#
# this is a wrapper for: https://github.com/egorkhmelev/jslider
# (www/shared/slider contains js, css, and img dependencies)
slider <- function(inputId, min, max, value, step = NULL, ...,
round=FALSE, format='#,##0.#####', locale='us',
ticks=TRUE, animate=FALSE) {
# validate inputId
inputId <- as.character(inputId)
if (!is.character(inputId))
stop("inputId not specified")
# validate numeric inputs
if (!is.numeric(value) || !is.numeric(min) || !is.numeric(max))
stop("min, max, and value must all be numeric values")
else if (min(value) < min)
stop(paste("slider initial value", value,
"is less than the specified minimum"))
else if (max(value) > max)
stop(paste("slider initial value", value,
"is greater than the specified maximum"))
else if (min > max)
stop(paste("slider maximum is greater than minimum"))
else if (!is.null(step)) {
if (!is.numeric(step))
stop("step is not a numeric value")
if (step > (max - min))
stop("step is greater than range")
}
# step
range <- max - min
if (is.null(step)) {
# short range or decimals means continuous decimal
if (range < 2 || hasDecimals(min) || hasDecimals(max))
step <- range / 250 # ~ one step per pixel
else
step = 1
}
# Default state is to not have ticks
if (identical(ticks, TRUE)) {
# Automatic ticks
tickCount <- (range / step) + 1
if (tickCount <= 26)
ticks <- paste(rep('|', floor(tickCount)), collapse=';')
else {
ticks <- NULL
# # This is a smarter auto-tick algorithm, but to be truly useful
# # we need jslider to be able to space ticks irregularly
# tickSize <- 10^(floor(log10(range/0.39)))
# if ((range / tickSize) == floor(range / tickSize)) {
# ticks <- paste(rep('|', (range / tickSize) + 1), collapse=';')
# }
# else {
# ticks <- NULL
# }
}
}
else if (is.numeric(ticks) && length(ticks) == 1) {
# Use n ticks
ticks <- paste(rep('|', ticks), collapse=';')
}
else if (length(ticks) > 1 && (is.numeric(ticks) || is.character(ticks))) {
# Explicit ticks
ticks <- paste(ticks, collapse=';')
}
else {
ticks <- NULL
}
# build slider
sliderFragment <- list(tags$input(
id=inputId, type="slider",
name=inputId, value=paste(value, collapse=';'), class="jslider",
'data-from'=min, 'data-to'=max, 'data-step'=step,
'data-skin'='plastic', 'data-round'=round, 'data-locale'=locale,
'data-format'=format, 'data-scale'=ticks,
'data-smooth'=FALSE))
if (identical(animate, TRUE))
animate <- animationOptions()
if (!is.null(animate) && !identical(animate, FALSE)) {
if (is.null(animate$playButton))
animate$playButton <- 'Play'
if (is.null(animate$pauseButton))
animate$pauseButton <- 'Pause'
sliderFragment[[length(sliderFragment)+1]] <-
tags$div(class='slider-animate-container',
tags$a(href='#',
class='slider-animate-button',
'data-target-id'=inputId,
'data-interval'=animate$interval,
'data-loop'=animate$loop,
tags$span(class='play', animate$playButton),
tags$span(class='pause', animate$pauseButton)))
}
return(sliderFragment)
}

402
R/tags.R Normal file
View File

@@ -0,0 +1,402 @@
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), ...)
}
#' @S3method format shiny.tag
format.shiny.tag <- function(x, ...) {
as.character.shiny.tag(x)
}
#' @S3method as.character shiny.tag
as.character.shiny.tag <- function(x, ...) {
f = file()
on.exit(close(f))
textWriter <- function(text) {
cat(text, file=f)
}
tagWrite(x, textWriter)
return(HTML(paste(readLines(f, warn=FALSE), collapse='\n')))
}
#' @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
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=""))
# write attributes
for (attrib in names(tag$attribs)) {
attribValue <- tag$attribs[[attrib]]
if (!is.na(attribValue)) {
if (is.logical(attribValue))
attribValue <- tolower(attribValue)
text <- htmlEscape(attribValue, attribute=TRUE)
textWriter(paste(" ", attrib,"=\"", text, "\"", sep=""))
}
else {
textWriter(paste(" ", attrib, sep=""))
}
}
# write any children
if (length(tag$children) > 0) {
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=""))
}
}
}
# 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
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))
}
}

191
R/tar.R Normal file
View File

@@ -0,0 +1,191 @@
# This file was pulled from the R code base as of
# Thursday, November 22, 2012 at 6:24:55 AM UTC
# and edited to remove everything but the copyright
# header and untar2, and to make untar2 more tolerant
# of the 'x' and 'g' extended block indicators, the
# latter of which is used in tar files generated by
# GitHub.
# File src/library/utils/R/tar.R
# Part of the R package, http://www.R-project.org
#
# Copyright (C) 1995-2012 The R Core Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is available at
# http://www.r-project.org/Licenses/
untar2 <- function(tarfile, files = NULL, list = FALSE, exdir = ".")
{
getOct <- function(x, offset, len)
{
x <- 0L
for(i in offset + seq_len(len)) {
z <- block[i]
if(!as.integer(z)) break; # terminate on nul
switch(rawToChar(z),
" " = {},
"0"=,"1"=,"2"=,"3"=,"4"=,"5"=,"6"=,"7"=
{x <- 8*x + (as.integer(z)-48)},
stop("invalid octal digit")
)
}
x
}
mydir.create <- function(path, ...) {
## for Windows' sake
path <- sub("[\\/]$", "", path)
if(file_test("-d", path)) return()
if(!dir.create(path, showWarnings = TRUE, recursive = TRUE, ...))
stop(gettextf("failed to create directory %s", sQuote(path)),
domain = NA)
}
warn1 <- character()
## A tar file is a set of 512 byte records,
## a header record followed by file contents (zero-padded).
## See http://en.wikipedia.org/wiki/Tar_%28file_format%29
if(is.character(tarfile) && length(tarfile) == 1L) {
con <- gzfile(path.expand(tarfile), "rb") # reads compressed formats
on.exit(close(con))
} else if(inherits(tarfile, "connection")) con <- tarfile
else stop("'tarfile' must be a character string or a connection")
if (!missing(exdir)) {
mydir.create(exdir)
od <- setwd(exdir)
on.exit(setwd(od), add = TRUE)
}
contents <- character()
llink <- lname <- NULL
repeat{
block <- readBin(con, "raw", n = 512L)
if(!length(block)) break
if(length(block) < 512L) stop("incomplete block on file")
if(all(block == 0)) break
ns <- max(which(block[1:100] > 0))
name <- rawToChar(block[seq_len(ns)])
magic <- rawToChar(block[258:262])
if ((magic == "ustar") && block[346] > 0) {
ns <- max(which(block[346:500] > 0))
prefix <- rawToChar(block[345+seq_len(ns)])
name <- file.path(prefix, name)
}
## mode zero-padded 8 bytes (including nul) at 101
## Aargh: bsdtar has this one incorrectly with 6 bytes+space
mode <- as.octmode(getOct(block, 100, 8))
size <- getOct(block, 124, 12)
ts <- getOct(block, 136, 12)
ft <- as.POSIXct(as.numeric(ts), origin="1970-01-01", tz="UTC")
csum <- getOct(block, 148, 8)
block[149:156] <- charToRaw(" ")
xx <- as.integer(block)
checksum <- sum(xx) %% 2^24 # 6 bytes
if(csum != checksum) {
## try it with signed bytes.
checksum <- sum(ifelse(xx > 127, xx - 128, xx)) %% 2^24 # 6 bytes
if(csum != checksum)
warning(gettextf("checksum error for entry '%s'", name),
domain = NA)
}
type <- block[157L]
ctype <- rawToChar(type)
if(type == 0L || ctype == "0") {
if(!is.null(lname)) {name <- lname; lname <- NULL}
contents <- c(contents, name)
remain <- size
dothis <- !list
if(dothis && length(files)) dothis <- name %in% files
if(dothis) {
mydir.create(dirname(name))
out <- file(name, "wb")
}
for(i in seq_len(ceiling(size/512L))) {
block <- readBin(con, "raw", n = 512L)
if(length(block) < 512L)
stop("incomplete block on file")
if (dothis) {
writeBin(block[seq_len(min(512L, remain))], out)
remain <- remain - 512L
}
}
if(dothis) {
close(out)
Sys.chmod(name, mode, FALSE) # override umask
Sys.setFileTime(name, ft)
}
} else if(ctype %in% c("1", "2")) { # hard and symbolic links
contents <- c(contents, name)
ns <- max(which(block[158:257] > 0))
name2 <- rawToChar(block[157L + seq_len(ns)])
if(!is.null(lname)) {name <- lname; lname <- NULL}
if(!is.null(llink)) {name2 <- llink; llink <- NULL}
if(!list) {
if(ctype == "1") {
if (!file.link(name2, name)) { # will give a warning
## link failed, so try a file copy
if(file.copy(name2, name))
warn1 <- c(warn1, "restoring hard link as a file copy")
else
warning(gettextf("failed to copy %s to %s", sQuote(name2), sQuote(name)), domain = NA)
}
} else {
if(.Platform$OS.type == "windows") {
## this will not work for links to dirs
from <- file.path(dirname(name), name2)
if (!file.copy(from, name))
warning(gettextf("failed to copy %s to %s", sQuote(from), sQuote(name)), domain = NA)
else
warn1 <- c(warn1, "restoring symbolic link as a file copy")
} else {
if(!file.symlink(name2, name)) { # will give a warning
## so try a file copy: will not work for links to dirs
from <- file.path(dirname(name), name2)
if (file.copy(from, name))
warn1 <- c(warn1, "restoring symbolic link as a file copy")
else
warning(gettextf("failed to copy %s to %s", sQuote(from), sQuote(name)), domain = NA)
}
}
}
}
} else if(ctype == "5") {
contents <- c(contents, name)
if(!list) {
mydir.create(name)
Sys.chmod(name, mode, TRUE) # FIXME: check result
## no point is setting time, as dir will be populated later.
}
} else if(ctype %in% c("L", "K")) {
## This is a GNU extension that should no longer be
## in use, but it is.
name_size <- 512L * ceiling(size/512L)
block <- readBin(con, "raw", n = name_size)
if(length(block) < name_size)
stop("incomplete block on file")
ns <- max(which(block > 0)) # size on file may or may not include final nul
if(ctype == "L")
lname <- rawToChar(block[seq_len(ns)])
else
llink <- rawToChar(block[seq_len(ns)])
} else if(ctype %in% c("x", "g")) {
readBin(con, "raw", n = 512L*ceiling(size/512L))
} else stop("unsupported entry type ", sQuote(ctype))
}
if(length(warn1)) {
warn1 <- unique(warn1)
for (w in warn1) warning(w, domain = NA)
}
if(list) contents else invisible(0L)
}

72
R/timer.R Normal file
View File

@@ -0,0 +1,72 @@
# Return the current time, in milliseconds from epoch, with
# unspecified time zone.
now <- function() {
as.numeric(Sys.time()) * 1000
}
TimerCallbacks <- setRefClass(
'TimerCallbacks',
fields = list(
.nextId = 'integer',
.funcs = 'Map',
.times = 'data.frame'
),
methods = list(
initialize = function() {
.nextId <<- 0L
},
clear = function() {
.nextId <<- 0L
.funcs$clear()
.times <<- data.frame()
},
schedule = function(millis, func) {
id <- .nextId
.nextId <<- .nextId + 1L
t <- now()
# TODO: Horribly inefficient, use a heap instead
.times <<- rbind(.times, data.frame(time=t+millis,
scheduled=t,
id=id))
.times <<- .times[order(.times$time),]
.funcs$set(as.character(id), func)
return(id)
},
timeToNextEvent = function() {
if (dim(.times)[1] == 0)
return(Inf)
return(.times[1, 'time'] - now())
},
takeElapsed = function() {
t <- now()
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...?
# TODO: Detect NULL, and...?
thisFunc()
}
return(TRUE)
}
)
)
timerCallbacks <- TimerCallbacks$new()

405
R/update-input.R Normal file
View File

@@ -0,0 +1,405 @@
#' Change the value of a text input on the client
#'
#' @template update-input
#' @param value The value to set for the input object.
#'
#' @seealso \code{\link{textInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#'
#' # This will change the value of input$inText, based on x
#' updateTextInput(session, "inText", value = paste("New text", x))
#'
#' # Can also set the label, this time for input$inText2
#' updateTextInput(session, "inText2",
#' label = paste("New label", x),
#' value = paste("New text", x))
#' })
#' })
#' }
#' @export
updateTextInput <- function(session, inputId, label = NULL, value = NULL) {
message <- dropNulls(list(label=label, value=value))
session$sendInputMessage(inputId, message)
}
#' Change the value of a checkbox input on the client
#'
#' @template update-input
#' @param value The value to set for the input object.
#'
#' @seealso \code{\link{checkboxInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # TRUE if input$controller is even, FALSE otherwise.
#' x_even <- input$controller %% 2 == 0
#'
#' updateCheckboxInput(session, "inCheckbox", value = x_even)
#' })
#' })
#' }
#' @export
updateCheckboxInput <- updateTextInput
#' Change the value of a slider input on the client
#'
#' @template update-input
#' @param value The value to set for the input object.
#'
#' @seealso \code{\link{sliderInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#'
#' # Similar to number and text. only label and value can be set for slider
#' updateSliderInput(session, "inSlider",
#' label = paste("Slider label", x),
#' value = x)
#'
#' # For sliders that pick out a range, pass in a vector of 2 values.
#' updateSliderInput(session, "inSlider2", value = c(x-1, x+1))
#'
#' # An NA means to not change that value (the low or high one)
#' updateSliderInput(session, "inSlider3", value = c(NA, x+2))
#' })
#' })
#' }
#' @export
updateSliderInput <- updateTextInput
#' Change the value of a date input on the client
#'
#' @template update-input
#' @param value The desired date value. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param min The minimum allowed date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param max The maximum allowed date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#'
#' @seealso \code{\link{dateInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#'
#' updateDateInput(session, "inDate",
#' label = paste("Date label", x),
#' value = paste("2013-04-", x, sep=""),
#' min = paste("2013-04-", x-1, sep=""),
#' max = paste("2013-04-", x+1, sep="")
#' )
#' })
#' })
#' }
#' @export
updateDateInput <- function(session, inputId, label = NULL, value = NULL,
min = NULL, max = NULL) {
# If value is a date object, convert it to a string with yyyy-mm-dd format
# Same for min and max
if (inherits(value, "Date")) value <- format(value, "%Y-%m-%d")
if (inherits(min, "Date")) min <- format(min, "%Y-%m-%d")
if (inherits(max, "Date")) max <- format(max, "%Y-%m-%d")
message <- dropNulls(list(label=label, value=value, min=min, max=max))
session$sendInputMessage(inputId, message)
}
#' Change the start and end values of a date range input on the client
#'
#' @template update-input
#' @param start The start date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param end The end date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param min The minimum allowed date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param max The maximum allowed date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#'
#' @seealso \code{\link{dateRangeInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#'
#' updateDateRangeInput(session, "inDateRange",
#' label = paste("Date range label", x),
#' start = paste("2013-01-", x, sep=""))
#' end = paste("2013-12-", x, sep=""))
#' })
#' })
#' }
#' @export
updateDateRangeInput <- function(session, inputId, label = NULL,
start = NULL, end = NULL, min = NULL, max = NULL) {
# Make sure start and end are strings, not date objects. This is for
# consistency across different locales.
if (inherits(start, "Date")) start <- format(start, '%Y-%m-%d')
if (inherits(end, "Date")) end <- format(end, '%Y-%m-%d')
if (inherits(min, "Date")) min <- format(min, '%Y-%m-%d')
if (inherits(max, "Date")) max <- format(max, '%Y-%m-%d')
message <- dropNulls(list(
label = label,
value = c(start, end),
min = min,
max = max
))
session$sendInputMessage(inputId, message)
}
#' Change the selected tab on the client
#'
#' @param session The \code{session} object passed to function given to
#' \code{shinyServer}.
#' @param inputId The id of the tabset panel object.
#' @param selected The name of the tab to make active.
#'
#' @seealso \code{\link{tabsetPanel}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # TRUE if input$controller is even, FALSE otherwise.
#' x_even <- input$controller %% 2 == 0
#'
#' # Change the selected tab.
#' # Note that the tabsetPanel must have been created with an 'id' argument
#' if (x_even) {
#' updateTabsetPanel(session, "inTabset", selected = "panel2")
#' } else {
#' updateTabsetPanel(session, "inTabset", selected = "panel1")
#' }
#' })
#' })
#' }
#' @export
updateTabsetPanel <- function(session, inputId, selected = NULL) {
message <- dropNulls(list(value = selected))
session$sendInputMessage(inputId, message)
}
#' Change the value of a number input on the client
#'
#' @template update-input
#' @param value The value to set for the input object.
#' @param min Minimum value.
#' @param max Maximum value.
#' @param step Step size.
#'
#' @seealso \code{\link{numericInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#'
#' updateNumericInput(session, "inNumber", value = x)
#'
#' updateNumericInput(session, "inNumber2",
#' label = paste("Number label ", x),
#' value = x, min = x-10, max = x+10, step = 5)
#' })
#' })
#' }
#' @export
updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
min = NULL, max = NULL, step = NULL) {
message <- dropNulls(list(label=label, value=value, min=min, max=max, step=step))
session$sendInputMessage(inputId, message)
}
#' Change the value of a checkbox group input on the client
#'
#' @template update-input
#' @param choices A named vector or named list of options. For each item, the
#' name will be used as the label, and the value will be used as the value.
#' @param selected A vector or list of options which will be selected.
#'
#' @seealso \code{\link{checkboxGroupInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#'
#' # Create a list of new options, where the name of the items is something
#' # like 'option label x 1', and the values are 'option-x-1'.
#' cb_options <- list()
#' cb_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
#' cb_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
#'
#' # Change values for input$inCheckboxGroup
#' updateCheckboxGroupInput(session, "inCheckboxGroup", choices = cb_options)
#'
#' # Can also set the label and select items
#' updateCheckboxGroupInput(session, "inCheckboxGroup2",
#' label = paste("checkboxgroup label", x),
#' choices = cb_options,
#' selected = sprintf("option label %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)
}
#' 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.
#'
#' @seealso \code{\link{radioButtons}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#'
#' r_options <- list()
#' r_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
#' r_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
#'
#' # Change values for input$inRadio
#' updateRadioButtons(session, "inRadio", choices = r_options)
#'
#' # Can also set the label and select an item
#' updateRadioButtons(session, "inRadio2",
#' label = paste("Radio label", x),
#' choices = r_options,
#' selected = sprintf("option label %d 2", x)
#' )
#' })
#' })
#' }
#' @export
updateRadioButtons <- updateCheckboxGroupInput
#' Change the value of a select input on the client
#'
#' @template update-input
#' @param choices A named vector or named list of options. For each item, the
#' name will be used as the label, and the value will be used as the value.
#' @param selected A vector or list of options which will be selected.
#'
#' @seealso \code{\link{selectInput}}
#'
#' @examples
#' \dontrun{
#' shinyServer(function(input, output, session) {
#'
#' observe({
#' # We'll use the input$controller variable multiple times, so save it as x
#' # for convenience.
#' x <- input$controller
#'
#' # Create a list of new options, where the name of the items is something
#' # like 'option label x 1', and the values are 'option-x-1'.
#' s_options <- list()
#' s_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
#' s_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
#'
#' # Change values for input$inSelect
#' updateSelectInput(session, "inSelect", choices = s_options)
#'
#' # Can also set the label and select an item (or more than one if it's a
#' # multi-select)
#' updateSelectInput(session, "inSelect2",
#' label = paste("Select label", x),
#' choices = s_options,
#' selected = sprintf("option label %d 2", x)
#' )
#' })
#' })
#' }
#' @export
updateSelectInput <- 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,
selected = name %in% selected)
}
)
message <- dropNulls(list(label = label, options = options))
session$sendInputMessage(inputId, message)
}

310
R/utils.R Normal file
View File

@@ -0,0 +1,310 @@
#' Make a random number generator repeatable
#'
#' Given a function that generates random data, returns a wrapped version of
#' that function that always uses the same seed when called. The seed to use can
#' be passed in explicitly if desired; otherwise, a random number is used.
#'
#' @param rngfunc The function that is affected by the R session's seed.
#' @param seed The seed to set every time the resulting function is called.
#' @return A repeatable version of the function that was passed in.
#'
#' @note When called, the returned function attempts to preserve the R session's
#' current seed by snapshotting and restoring
#' \code{\link[base]{.Random.seed}}.
#'
#' @examples
#' rnormA <- repeatable(rnorm)
#' rnormB <- repeatable(rnorm)
#' rnormA(3) # [1] 1.8285879 -0.7468041 -0.4639111
#' rnormA(3) # [1] 1.8285879 -0.7468041 -0.4639111
#' rnormA(5) # [1] 1.8285879 -0.7468041 -0.4639111 -1.6510126 -1.4686924
#' rnormB(5) # [1] -0.7946034 0.2568374 -0.6567597 1.2451387 -0.8375699
#'
#' @export
repeatable <- function(rngfunc, seed = runif(1, 0, .Machine$integer.max)) {
force(seed)
function(...) {
# When we exit, restore the seed to its original state
if (exists('.Random.seed', where=globalenv())) {
currentSeed <- get('.Random.seed', pos=globalenv())
on.exit(assign('.Random.seed', currentSeed, pos=globalenv()))
}
else {
on.exit(rm('.Random.seed', pos=globalenv()))
}
set.seed(seed)
do.call(rngfunc, list(...))
}
}
`%OR%` <- function(x, y) {
ifelse(is.null(x) || is.na(x), y, x)
}
`%AND%` <- function(x, y) {
if (!is.null(x) && !is.na(x))
if (!is.null(y) && !is.na(y))
return(y)
return(NULL)
}
`%.%` <- function(x, y) {
paste(x, y, sep='')
}
# Given a vector or list, drop all the NULL items in it
dropNulls <- function(x) {
x[!vapply(x, is.null, FUN.VALUE=logical(1))]
}
knownContentTypes <- Map$new()
knownContentTypes$mset(
html='text/html; charset=UTF-8',
htm='text/html; charset=UTF-8',
js='text/javascript',
css='text/css',
png='image/png',
jpg='image/jpeg',
jpeg='image/jpeg',
gif='image/gif',
svg='image/svg+xml',
txt='text/plain',
pdf='application/pdf',
ps='application/postscript',
xml='application/xml',
m3u='audio/x-mpegurl',
m4a='audio/mp4a-latm',
m4b='audio/mp4a-latm',
m4p='audio/mp4a-latm',
mp3='audio/mpeg',
wav='audio/x-wav',
m4u='video/vnd.mpegurl',
m4v='video/x-m4v',
mp4='video/mp4',
mpeg='video/mpeg',
mpg='video/mpeg',
avi='video/x-msvideo',
mov='video/quicktime',
ogg='application/ogg',
swf='application/x-shockwave-flash',
doc='application/msword',
xls='application/vnd.ms-excel',
ppt='application/vnd.ms-powerpoint',
xlsx='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
xltx='application/vnd.openxmlformats-officedocument.spreadsheetml.template',
potx='application/vnd.openxmlformats-officedocument.presentationml.template',
ppsx='application/vnd.openxmlformats-officedocument.presentationml.slideshow',
pptx='application/vnd.openxmlformats-officedocument.presentationml.presentation',
sldx='application/vnd.openxmlformats-officedocument.presentationml.slide',
docx='application/vnd.openxmlformats-officedocument.wordprocessingml.document',
dotx='application/vnd.openxmlformats-officedocument.wordprocessingml.template',
xlam='application/vnd.ms-excel.addin.macroEnabled.12',
xlsb='application/vnd.ms-excel.sheet.binary.macroEnabled.12')
getContentType <- function(ext, defaultType='application/octet-stream') {
knownContentTypes$get(tolower(ext)) %OR% defaultType
}
# Create a zero-arg function from a quoted expression and environment
# @examples
# makeFunction(body=quote(print(3)))
makeFunction <- function(args = pairlist(), body, env = parent.frame()) {
eval(call("function", args, body), env)
}
#' Convert an expression or quoted expression to a function
#'
#' This is to be called from another function, because it will attempt to get
#' an unquoted expression from two calls back.
#'
#' If expr is a quoted expression, then this just converts it to a function.
#' If expr is a function, then this simply returns expr (and prints a
#' deprecation message.
#' If expr was a non-quoted expression from two calls back, then this will
#' quote the original expression and convert it to a function.
#
#' @param expr A quoted or unquoted expression, or a function.
#' @param env The desired environment for the function. Defaults to the
#' calling environment two steps back.
#' @param quoted Is the expression quoted?
#'
#' @examples
#' # Example of a new renderer, similar to renderText
#' # This is something that toolkit authors will do
#' renderTriple <- function(expr, env=parent.frame(), quoted=FALSE) {
#' # Convert expr to a function
#' func <- shiny::exprToFunction(expr, env, quoted)
#'
#' function() {
#' value <- func()
#' paste(rep(value, 3), collapse=", ")
#' }
#' }
#'
#'
#' # Example of using the renderer.
#' # This is something that app authors will do.
#' values <- reactiveValues(A="text")
#'
#' \dontrun{
#' # Create an output object
#' output$tripleA <- renderTriple({
#' values$A
#' })
#' }
#'
#' # At the R console, you can experiment with the renderer using isolate()
#' tripleA <- renderTriple({
#' values$A
#' })
#'
#' isolate(tripleA())
#' # "text, text, text"
#'
#' @export
exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE) {
# Get the quoted expr from two calls back
expr_sub <- eval(substitute(substitute(expr)), parent.frame())
# Check if expr is a function, making sure not to evaluate expr, in case it
# is actually an unquoted expression.
# If expr is a single token, then indexing with [[ will error; if it has multiple
# tokens, then [[ works. In the former case it will be a name object; in the
# latter, it will be a language object.
if (!is.name(expr_sub) && expr_sub[[1]] == as.name('function')) {
# Get name of function that called this function
called_fun <- sys.call(-1)[[1]]
shinyDeprecated(msg = paste("Passing functions to '", called_fun,
"' is deprecated. Please use expressions instead. See ?", called_fun,
" for more information.", sep=""))
return(expr)
}
if (quoted) {
# expr is a quoted expression
makeFunction(body=expr, env=env)
} else {
# expr is an unquoted expression
makeFunction(body=expr_sub, env=env)
}
}
#' Parse a GET query string from a URL
#'
#' Returns a named character vector of key-value pairs.
#'
#' @param str The query string. It can have a leading \code{"?"} or not.
#' @export
#' @examples
#' parseQueryString("?foo=1&bar=b%20a%20r")
#'
#' \dontrun{
#' # Example of usage within a Shiny app
#' shinyServer(function(input, output, clientData) {
#'
#' output$queryText <- renderText({
#' query <- parseQueryString(clientData$url_search)
#'
#' # Ways of accessing the values
#' if (as.numeric(query$foo) == 1) {
#' # Do something
#' }
#' if (query[["bar"]] == "targetstring") {
#' # Do something else
#' }
#'
#' # Return a string with key-value pairs
#' paste(names(query), query, sep = "=", collapse=", ")
#' })
#' })
#' }
#'
parseQueryString <- function(str) {
if (is.null(str) || nchar(str) == 0)
return(list())
# Remove leading ?
if (substr(str, 1, 1) == '?')
str <- substr(str, 2, nchar(str))
pairs <- strsplit(str, '&', fixed = TRUE)[[1]]
pairs <- strsplit(pairs, '=', fixed = TRUE)
keys <- vapply(pairs, function(x) x[1], FUN.VALUE = character(1))
values <- vapply(pairs, function(x) x[2], FUN.VALUE = character(1))
# Replace NA with '', so they don't get converted to 'NA' by URLdecode
values[is.na(values)] <- ''
# Convert "+" to " ", since URLdecode doesn't do it
keys <- gsub('+', ' ', keys, fixed = TRUE)
values <- gsub('+', ' ', values, fixed = TRUE)
keys <- vapply(keys, function(x) URLdecode(x), FUN.VALUE = character(1))
values <- vapply(values, function(x) URLdecode(x), FUN.VALUE = character(1))
setNames(as.list(values), keys)
}
#' Print message for deprecated functions in Shiny
#'
#' To disable these messages, use \code{options(shiny.deprecation.messages=FALSE)}.
#'
#' @param new Name of replacement function.
#' @param msg Message to print. If used, this will override the default message.
#' @param old Name of deprecated function.
shinyDeprecated <- function(new=NULL, msg=NULL,
old=as.character(sys.call(sys.parent()))[1L]) {
if (getOption("shiny.deprecation.messages", default=TRUE) == FALSE)
return(invisible())
if (is.null(msg)) {
msg <- paste(old, "is deprecated.")
if (!is.null(new))
msg <- paste(msg, "Please use", new, "instead.",
"To disable this message, run options(shiny.deprecation.messages=FALSE)")
}
# Similar to .Deprecated(), but print a message instead of warning
message(msg)
}
Callbacks <- setRefClass(
'Callbacks',
fields = list(
.nextId = 'integer',
.callbacks = 'Map'
),
methods = list(
initialize = function() {
.nextId <<- as.integer(.Machine$integer.max)
},
register = function(callback) {
id <- as.character(.nextId)
.nextId <<- .nextId - 1L
.callbacks$set(id, callback)
return(function() {
.callbacks$remove(id)
})
},
invoke = function(..., onError=NULL) {
for (callback in .callbacks$values()) {
tryCatch(
do.call(callback, list(...)),
error = function(e) {
if (is.null(onError))
stop(e)
else
onError(e)
}
)
}
},
count = function() {
.callbacks$size()
}
)
)

View File

@@ -1,10 +1,39 @@
# Shiny
### A web framework for R (eventually--Ruby for now)
# Shiny
```sh
sudo apt-get install ruby1.9.1 ruby1.9.1-dev
sudo gem install bundler
cd shiny
bundle install --path vendor
./run.sh
```
Shiny is a new package from RStudio that makes it incredibly easy to build interactive web applications with R.
For an introduction and examples, visit the [Shiny homepage](http://www.rstudio.com/shiny/).
## Features
* Build useful web applications with only a few lines of code&mdash;no JavaScript required.
* Shiny applications are automatically "live" in the same way that spreadsheets are live. Outputs change instantly as users modify inputs, without requiring a reload of the browser.
* Shiny user interfaces can be built entirely using R, or can be written directly in HTML, CSS, and JavaScript for more flexibility.
* Works in any R environment (Console R, Rgui for Windows or Mac, ESS, StatET, RStudio, etc.)
* Attractive default UI theme based on [Twitter Bootstrap](http://twitter.github.com/bootstrap).
* A highly customizable slider widget with built-in support for animation.
* Pre-built output widgets for displaying plots, tables, and printed output of R objects.
* Fast bidirectional communication between the web browser and R using the [websockets](http://illposed.net/websockets.html) package.
* Uses a [reactive](http://en.wikipedia.org/wiki/Reactive_programming) programming model that eliminates messy event handling code, so you can focus on the code that really matters.
* Develop and redistribute your own Shiny widgets that other developers can easily drop into their own applications (coming soon!).
## Installation
From an R console:
```r
install.packages("shiny")
```
## Getting Started
To learn more we highly recommend you check out the [Shiny Tutorial](http://rstudio.github.com/shiny/tutorial). The tutorial explains the framework in-depth, walks you through building a simple application, and includes extensive annotated examples.
We hope you enjoy using Shiny. As you learn more and work with the package please [let us know](https://github.com/rstudio/shiny/issues) what problems you encounter and how you'd like to see Shiny evolve.
## License
The shiny package is licensed under the GPLv3. See these files in the inst directory for additional details:
- COPYING - shiny package license (GPLv3)
- NOTICE - Copyright notices for additional included software

View File

@@ -1,15 +0,0 @@
library(digest)
input <- Observable$new(function() {
str <- get.shiny.input('input1')
if (get.shiny.input('addnewline'))
str <- paste(str, "\n", sep='')
return(str)
})
define.shiny.output('md5_hash', function() {
digest(input$get.value(), algo='md5', serialize=F)
})
define.shiny.output('sha1_hash', function() {
digest(input$get.value(), algo='sha1', serialize=F)
})

View File

@@ -1,26 +0,0 @@
<html>
<head>
<script src="shared/jquery-1.7.2.js" type="text/javascript"></script>
<script src="shared/shiny.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="shared/shiny.css"/>
</head>
<body>
<h1>Example 2: Hash Calculation</h1>
<p>
<label>Input:</label><br />
<input name="input1" value="Hello World!"/>
<input type="checkbox" name="addnewline" checked="checked"/> Append newline
</p>
<p>
<label>MD5:</label><br />
<pre id="md5_hash" class="live-text"></pre>
</p>
<p>
<label>SHA-1:</label><br />
<pre id="sha1_hash" class="live-text"></pre>
</p>
</body>
</html>

View File

@@ -1,24 +0,0 @@
data <- Observable$new(function() {
# Choose a distribution function
dist <- switch(get.shiny.input('dist'),
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
# Generate n values from the distribution function
dist(max(1, get.shiny.input('n')))
})
define.shiny.plot('plot1', function() {
dist <- get.shiny.input('dist')
n <- get.shiny.input('n')
hist(data$get.value(),
main=paste('r', dist, '(', n, ')', sep=''))
}, width=600, height=300)
define.shiny.table('table1', function() {
data.frame(x=data$get.value())
})

19
hash.rb
View File

@@ -1,19 +0,0 @@
require 'shiny'
require 'digest/sha1'
require 'digest/md5'
shinyapp = ShinyApp.new
input1 = React::ObservableValue.new {
shinyapp.session.get('input1') + (shinyapp.session.get('addnewline') ? "\n" : '')
}
shinyapp.define_output('md5_hash') do
Digest::MD5.hexdigest(input1.value)
end
shinyapp.define_output('sha1_hash') do
Digest::SHA1.hexdigest(input1.value)
end
shinyapp.run

678
inst/COPYING Normal file
View File

@@ -0,0 +1,678 @@
The shiny package is licensed to you under the GPLv3, the terms of
which are included below. The markdown pacakge includes other open
source software whose license terms can be found in the file NOTICE.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

265
inst/NOTICE Normal file
View File

@@ -0,0 +1,265 @@
The shiny package inludes other open source software components. The following
is a list of these components (full copies of the license agreements used by
these components are included below):
- jQuery
- Bootstrap
- bootstrap-datepicker, from https://github.com/eternicode/bootstrap-datepicker
- jslider
jQuery License
----------------------------------------------------------------------
Copyright (c) 2012 jQuery Foundation and other contributors,
http://jquery.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Bootstrap and bootstrap-datepicker License
----------------------------------------------------------------------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
jslider License
----------------------------------------------------------------------
The MIT License (MIT)
Copyright (c) 2012 Egor Khmelev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,20 @@
library(shiny)
# Define server logic required to generate and plot a random distribution
shinyServer(function(input, output) {
# Expression that generates a plot of the distribution. The expression
# is wrapped in a call to renderPlot to indicate that:
#
# 1) It is "reactive" and therefore should be automatically
# re-executed when inputs change
# 2) Its output type is a plot
#
output$distPlot <- renderPlot({
# generate an rnorm distribution and plot it
dist <- rnorm(input$obs)
hist(dist)
})
})

View File

@@ -0,0 +1,22 @@
library(shiny)
# Define UI for application that plots random distributions
shinyUI(pageWithSidebar(
# Application title
headerPanel("Hello Shiny!"),
# Sidebar with a slider input for number of observations
sidebarPanel(
sliderInput("obs",
"Number of observations:",
min = 0,
max = 1000,
value = 500)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
))

View File

@@ -0,0 +1,25 @@
library(shiny)
library(datasets)
# Define server logic required to summarize and view the selected dataset
shinyServer(function(input, output) {
# Return the requested dataset
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# Generate a summary of the dataset
output$summary <- renderPrint({
dataset <- datasetInput()
summary(dataset)
})
# Show the first "n" observations
output$view <- renderTable({
head(datasetInput(), n = input$obs)
})
})

View File

@@ -0,0 +1,25 @@
library(shiny)
# Define UI for dataset viewer application
shinyUI(pageWithSidebar(
# Application title
headerPanel("Shiny Text"),
# Sidebar with controls to select a dataset and specify the number
# of observations to view
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Number of observations to view:", 10)
),
# Show a summary of the dataset and an HTML table with the requested
# number of observations
mainPanel(
verbatimTextOutput("summary"),
tableOutput("view")
)
))

View File

@@ -0,0 +1,50 @@
library(shiny)
library(datasets)
# Define server logic required to summarize and view the selected dataset
shinyServer(function(input, output) {
# By declaring databaseInput as a reactive expression we ensure that:
#
# 1) It is only called when the inputs it depends on changes
# 2) The computation and result are shared by all the callers (it
# only executes a single time)
# 3) When the inputs change and the expression is re-executed, the
# new result is compared to the previous result; if the two are
# identical, then the callers are not notified
#
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# The output$caption is computed based on a reactive expression that
# returns input$caption. When the user changes the "caption" field:
#
# 1) This function is automatically called to recompute the output
# 2) The new caption is pushed back to the browser for re-display
#
# Note that because the data-oriented reactive expressions below don't
# depend on input$caption, those expressions are NOT called when
# input$caption changes.
output$caption <- renderText({
input$caption
})
# The output$summary depends on the datasetInput reactive expression,
# so will be re-executed whenever datasetInput is re-executed
# (i.e. whenever the input$dataset changes)
output$summary <- renderPrint({
dataset <- datasetInput()
summary(dataset)
})
# The output$view depends on both the databaseInput reactive expression
# and input$obs, so will be re-executed whenever input$dataset or
# input$obs is changed.
output$view <- renderTable({
head(datasetInput(), n = input$obs)
})
})

View File

@@ -0,0 +1,32 @@
library(shiny)
# Define UI for dataset viewer application
shinyUI(pageWithSidebar(
# Application title
headerPanel("Reactivity"),
# Sidebar with controls to provide a caption, select a dataset, and
# specify the number of observations to view. Note that changes made
# to the caption in the textInput control are updated in the output
# area immediately as you type
sidebarPanel(
textInput("caption", "Caption:", "Data Summary"),
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Number of observations to view:", 10)
),
# Show the caption, a summary of the dataset and an HTML table with
# the requested number of observations
mainPanel(
h3(textOutput("caption")),
verbatimTextOutput("summary"),
tableOutput("view")
)
))

View File

@@ -0,0 +1,32 @@
library(shiny)
library(datasets)
# We tweak the "am" field to have nicer factor labels. Since this doesn't
# rely on any user inputs we can do this once at startup and then use the
# value throughout the lifetime of the application
mpgData <- mtcars
mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
# Define server logic required to plot various variables against mpg
shinyServer(function(input, output) {
# Compute the forumla text in a reactive expression since it is
# shared by the output$caption and output$mpgPlot functions
formulaText <- reactive({
paste("mpg ~", input$variable)
})
# Return the formula text for printing as a caption
output$caption <- renderText({
formulaText()
})
# Generate a plot of the requested variable against mpg and only
# include outliers if requested
output$mpgPlot <- renderPlot({
boxplot(as.formula(formulaText()),
data = mpgData,
outline = input$outliers)
})
})

26
inst/examples/04_mpg/ui.R Normal file
View File

@@ -0,0 +1,26 @@
library(shiny)
# Define UI for miles per gallon application
shinyUI(pageWithSidebar(
# Application title
headerPanel("Miles Per Gallon"),
# Sidebar with controls to select the variable to plot against mpg
# and to specify whether outliers should be included
sidebarPanel(
selectInput("variable", "Variable:",
c("Cylinders" = "cyl",
"Transmission" = "am",
"Gears" = "gear")),
checkboxInput("outliers", "Show outliers", FALSE)
),
# Show the caption and plot of the requested variable against mpg
mainPanel(
h3(textOutput("caption")),
plotOutput("mpgPlot")
)
))

View File

@@ -0,0 +1,28 @@
library(shiny)
# Define server logic for slider examples
shinyServer(function(input, output) {
# Reactive expression to compose a data frame containing all of the values
sliderValues <- reactive({
# Compose data frame
data.frame(
Name = c("Integer",
"Decimal",
"Range",
"Custom Format",
"Animation"),
Value = as.character(c(input$integer,
input$decimal,
paste(input$range, collapse=' '),
input$format,
input$animation)),
stringsAsFactors=FALSE)
})
# Show the values using an HTML table
output$values <- renderTable({
sliderValues()
})
})

View File

@@ -0,0 +1,37 @@
library(shiny)
# Define UI for slider demo application
shinyUI(pageWithSidebar(
# Application title
headerPanel("Sliders"),
# Sidebar with sliders that demonstrate various available options
sidebarPanel(
# Simple integer interval
sliderInput("integer", "Integer:",
min=0, max=1000, value=500),
# Decimal interval with step value
sliderInput("decimal", "Decimal:",
min = 0, max = 1, value = 0.5, step= 0.1),
# Specification of range within an interval
sliderInput("range", "Range:",
min = 1, max = 1000, value = c(200,500)),
# Provide a custom currency format for value display, with basic animation
sliderInput("format", "Custom Format:",
min = 0, max = 10000, value = 0, step = 2500,
format="$#,##0", locale="us", animate=TRUE),
# Animation with custom interval (in ms) to control speed, plus looping
sliderInput("animation", "Looping Animation:", 1, 2000, 1, step = 10,
animate=animationOptions(interval=300, loop=TRUE))
),
# Show a table summarizing the values entered
mainPanel(
tableOutput("values")
)
))

View File

@@ -0,0 +1,42 @@
library(shiny)
# Define server logic for random distribution application
shinyServer(function(input, output) {
# Reactive expression to generate the requested distribution. This is
# called whenever the inputs change. The output functions defined
# below then all use the value computed from this expression
data <- reactive({
dist <- switch(input$dist,
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
dist(input$n)
})
# Generate a plot of the data. Also uses the inputs to build the
# plot label. Note that the dependencies on both the inputs and
# the data reactive expression are both tracked, and all expressions
# are called in the sequence implied by the dependency graph
output$plot <- renderPlot({
dist <- input$dist
n <- input$n
hist(data(),
main=paste('r', dist, '(', n, ')', sep=''))
})
# Generate a summary of the data
output$summary <- renderPrint({
summary(data())
})
# Generate an HTML table view of the data
output$table <- renderTable({
data.frame(x=data())
})
})

View File

@@ -0,0 +1,36 @@
library(shiny)
# Define UI for random distribution application
shinyUI(pageWithSidebar(
# Application title
headerPanel("Tabsets"),
# Sidebar with controls to select the random distribution type
# and number of observations to generate. Note the use of the br()
# element to introduce extra vertical spacing
sidebarPanel(
radioButtons("dist", "Distribution type:",
c("Normal" = "norm",
"Uniform" = "unif",
"Log-normal" = "lnorm",
"Exponential" = "exp")),
br(),
sliderInput("n",
"Number of observations:",
value = 500,
min = 1,
max = 1000)
),
# Show a tabset that includes a plot, summary, and table view
# of the generated distribution
mainPanel(
tabsetPanel(
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Table", tableOutput("table"))
)
)
))

View File

@@ -0,0 +1,25 @@
library(shiny)
library(datasets)
# Define server logic required to summarize and view the selected dataset
shinyServer(function(input, output) {
# Return the requested dataset
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# Generate a summary of the dataset
output$summary <- renderPrint({
dataset <- datasetInput()
summary(dataset)
})
# Show the first "n" observations
output$view <- renderTable({
head(datasetInput(), n = input$obs)
})
})

View File

@@ -0,0 +1,39 @@
library(shiny)
# Define UI for dataset viewer application
shinyUI(pageWithSidebar(
# Application title.
headerPanel("More Widgets"),
# Sidebar with controls to select a dataset and specify the number
# of observations to view. The helpText function is also used to
# include clarifying text. Most notably, the inclusion of a
# submitButton defers the rendering of output until the user
# explicitly clicks the button (rather than doing it immediately
# when inputs change). This is useful if the computations required
# to render output are inordinately time-consuming.
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Number of observations to view:", 10),
helpText("Note: while the data view will show only the specified",
"number of observations, the summary will still be based",
"on the full dataset."),
submitButton("Update View")
),
# Show a summary of the dataset and an HTML table with the requested
# number of observations. Note the use of the h4 function to provide
# an additional header above each output section.
mainPanel(
h4("Summary"),
verbatimTextOutput("summary"),
h4("Observations"),
tableOutput("view")
)
))

View File

@@ -0,0 +1,42 @@
library(shiny)
# Define server logic for random distribution application
shinyServer(function(input, output) {
# Reactive expression to generate the requested distribution. This is
# called whenever the inputs change. The output expressions defined
# below then all used the value computed from this expression
data <- reactive({
dist <- switch(input$dist,
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
dist(input$n)
})
# Generate a plot of the data. Also uses the inputs to build the
# plot label. Note that the dependencies on both the inputs and
# the data reactive expression are both tracked, and all expressions
# are called in the sequence implied by the dependency graph
output$plot <- renderPlot({
dist <- input$dist
n <- input$n
hist(data(),
main=paste('r', dist, '(', n, ')', sep=''))
})
# Generate a summary of the data
output$summary <- renderPrint({
summary(data())
})
# Generate an HTML table view of the data
output$table <- renderTable({
data.frame(x=data())
})
})

View File

@@ -1,12 +1,15 @@
<html>
<head>
<script src="shared/jquery-1.7.2.js" type="text/javascript"></script>
<script src="shared/shiny.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="shared/shiny.css"/>
</head>
<body>
<h1>Example 3: Distributions</h1>
<head>
<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"/>
</head>
<body>
<h1>HTML UI</h1>
<p>
<label>Distribution type:</label><br />
<select name="dist">
@@ -14,17 +17,22 @@
<option value="unif">Uniform</option>
<option value="lnorm">Log-normal</option>
<option value="exp">Exponential</option>
</select>
</select>
</p>
<p>
<label>Number of observations:</label><br />
<input type="numeric" name="n" value="500" />
</p>
<label>Number of observations:</label><br />
<input type="number" name="n" value="500" min="1" max="1000" />
<div id="plot1" class="live-plot"></div>
</p>
<pre id="summary" class="shiny-text-output"></pre>
<div id="table1" class="live-html"></div>
<div id="plot" class="shiny-plot-output"
style="width: 100%; height: 400px"></div>
<div id="table" class="shiny-html-output"></div>
</body>
</html>
</html>

View File

@@ -0,0 +1,18 @@
library(shiny)
shinyServer(function(input, output) {
output$contents <- renderTable({
# input$file1 will be NULL initially. After the user selects and uploads a
# file, it will be a data frame with 'name', 'size', 'type', and 'datapath'
# columns. The 'datapath' column will contain the local filenames where the
# data can be found.
inFile <- input$file1
if (is.null(inFile))
return(NULL)
read.csv(inFile$datapath, header=input$header, sep=input$sep, quote=input$quote)
})
})

View File

@@ -0,0 +1,24 @@
library(shiny)
shinyUI(pageWithSidebar(
headerPanel("Uploading Files"),
sidebarPanel(
fileInput('file1', 'Choose CSV File',
accept=c('text/csv', 'text/comma-separated-values,text/plain')),
tags$hr(),
checkboxInput('header', 'Header', TRUE),
radioButtons('sep', 'Separator',
c(Comma=',',
Semicolon=';',
Tab='\t'),
'Comma'),
radioButtons('quote', 'Quote',
c(None='',
'Double Quote'='"',
'Single Quote'="'"),
'Double Quote')
),
mainPanel(
tableOutput('contents')
)
))

View File

@@ -0,0 +1,19 @@
shinyServer(function(input, output) {
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
output$table <- renderTable({
datasetInput()
})
output$downloadData <- downloadHandler(
filename = function() { paste(input$dataset, '.csv', sep='') },
content = function(file) {
write.csv(datasetInput(), file)
}
)
})

View File

@@ -0,0 +1,11 @@
shinyUI(pageWithSidebar(
headerPanel('Downloading Data'),
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
downloadButton('downloadData', 'Download')
),
mainPanel(
tableOutput('table')
)
))

View File

@@ -0,0 +1,6 @@
shinyServer(function(input, output) {
output$currentTime <- renderText({
invalidateLater(1000)
paste("The current time is", Sys.time())
})
})

View File

@@ -0,0 +1,3 @@
shinyUI(bootstrapPage(
textOutput("currentTime")
))

View File

@@ -0,0 +1,95 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Jasmine Spec Runner</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css">
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script>
<!-- include source files here... -->
<!-- All of these includes are copied out of the HTML file generated by
shinyUI() -->
<script src="../www/shared/jquery.js" type="text/javascript"></script>
<script src="../www/shared/shiny.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="../www/shared/shiny.css"/>
<link rel="stylesheet" type="text/css" href="../www/shared/slider/css/jquery.slider.min.css"/>
<script src="../www/shared/slider/js/jquery.slider.min.js"></script>
<link rel="stylesheet" type="text/css" href="../www/shared/bootstrap/css/bootstrap.min.css"/>
<script src="../www/shared/bootstrap/js/bootstrap.min.js"></script>
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"/> -->
<link rel="stylesheet" type="text/css" href="../www/shared/bootstrap/css/bootstrap-responsive.min.css"/>
<script src="../www/shared/datepicker/js/bootstrap-datepicker.min.js"></script>
<link rel="stylesheet" type="text/css" href="../www/shared/datepicker/css/datepicker.css"/>
<script src="../www/shared/bootstrap-daterangepicker/date.js"></script>
<script src="../www/shared/bootstrap-daterangepicker/daterangepicker.js"></script>
<link rel="stylesheet" type="text/css" href="../www/shared/bootstrap-daterangepicker/daterangepicker.css"/>
<!-- include spec files here... -->
<!-- <script type="text/javascript" src="spec/SpecHelper.js"></script>
<script type="text/javascript" src="spec/PlayerSpec.js"></script>
-->
<script type="text/javascript" src="spec/inputBindingSpec.js"></script>
<script type="text/javascript">
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
// var currentWindowOnload = window.onload;
// window.onload = function() {
// if (currentWindowOnload) {
// currentWindowOnload();
// }
// execJasmine();
// };
// Add a slight delay before running tests, so that Shiny has time to
// do setup stuff.
$(document).ready(function() {
setTimeout(function() {
execJasmine();
},
50
)
});
function execJasmine() {
jasmineEnv.execute();
}
})();
// Clear the Shiny disconnected gray screen shortly after loading
$(document).ready(function() {
setTimeout(function() {
$('body').removeClass('disconnected');
},
100
)
});
</script>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,2 @@
<label>Text input:</label>
<input id="in_text" type="text" value="starting value"/>

View File

@@ -0,0 +1,20 @@
Copyright (c) 2008-2011 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,681 @@
jasmine.HtmlReporterHelpers = {};
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
var results = child.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
return status;
};
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
var parentDiv = this.dom.summary;
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
var parent = child[parentSuite];
if (parent) {
if (typeof this.views.suites[parent.id] == 'undefined') {
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
}
parentDiv = this.views.suites[parent.id].element;
}
parentDiv.appendChild(childElement);
};
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
for(var fn in jasmine.HtmlReporterHelpers) {
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
}
};
jasmine.HtmlReporter = function(_doc) {
var self = this;
var doc = _doc || window.document;
var reporterView;
var dom = {};
// Jasmine Reporter Public Interface
self.logRunningSpecs = false;
self.reportRunnerStarting = function(runner) {
var specs = runner.specs() || [];
if (specs.length == 0) {
return;
}
createReporterDom(runner.env.versionString());
doc.body.appendChild(dom.reporter);
setExceptionHandling();
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
reporterView.addSpecs(specs, self.specFilter);
};
self.reportRunnerResults = function(runner) {
reporterView && reporterView.complete();
};
self.reportSuiteResults = function(suite) {
reporterView.suiteComplete(suite);
};
self.reportSpecStarting = function(spec) {
if (self.logRunningSpecs) {
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
self.reportSpecResults = function(spec) {
reporterView.specComplete(spec);
};
self.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
self.specFilter = function(spec) {
if (!focusedSpecName()) {
return true;
}
return spec.getFullName().indexOf(focusedSpecName()) === 0;
};
return self;
function focusedSpecName() {
var specName;
(function memoizeFocusedSpec() {
if (specName) {
return;
}
var paramMap = [];
var params = jasmine.HtmlReporter.parameters(doc);
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
specName = paramMap.spec;
})();
return specName;
}
function createReporterDom(version) {
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
dom.banner = self.createDom('div', { className: 'banner' },
self.createDom('span', { className: 'title' }, "Jasmine "),
self.createDom('span', { className: 'version' }, version)),
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
dom.alert = self.createDom('div', {className: 'alert'},
self.createDom('span', { className: 'exceptions' },
self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
dom.results = self.createDom('div', {className: 'results'},
dom.summary = self.createDom('div', { className: 'summary' }),
dom.details = self.createDom('div', { id: 'details' }))
);
}
function noTryCatch() {
return window.location.search.match(/catch=false/);
}
function searchWithCatch() {
var params = jasmine.HtmlReporter.parameters(window.document);
var removed = false;
var i = 0;
while (!removed && i < params.length) {
if (params[i].match(/catch=/)) {
params.splice(i, 1);
removed = true;
}
i++;
}
if (jasmine.CATCH_EXCEPTIONS) {
params.push("catch=false");
}
return params.join("&");
}
function setExceptionHandling() {
var chxCatch = document.getElementById('no_try_catch');
if (noTryCatch()) {
chxCatch.setAttribute('checked', true);
jasmine.CATCH_EXCEPTIONS = false;
}
chxCatch.onclick = function() {
window.location.search = searchWithCatch();
};
}
};
jasmine.HtmlReporter.parameters = function(doc) {
var paramStr = doc.location.search.substring(1);
var params = [];
if (paramStr.length > 0) {
params = paramStr.split('&');
}
return params;
}
jasmine.HtmlReporter.sectionLink = function(sectionName) {
var link = '?';
var params = [];
if (sectionName) {
params.push('spec=' + encodeURIComponent(sectionName));
}
if (!jasmine.CATCH_EXCEPTIONS) {
params.push("catch=false");
}
if (params.length > 0) {
link += params.join("&");
}
return link;
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
jasmine.HtmlReporter.ReporterView = function(dom) {
this.startedAt = new Date();
this.runningSpecCount = 0;
this.completeSpecCount = 0;
this.passedCount = 0;
this.failedCount = 0;
this.skippedCount = 0;
this.createResultsMenu = function() {
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
' | ',
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
this.summaryMenuItem.onclick = function() {
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
};
this.detailsMenuItem.onclick = function() {
showDetails();
};
};
this.addSpecs = function(specs, specFilter) {
this.totalSpecCount = specs.length;
this.views = {
specs: {},
suites: {}
};
for (var i = 0; i < specs.length; i++) {
var spec = specs[i];
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
if (specFilter(spec)) {
this.runningSpecCount++;
}
}
};
this.specComplete = function(spec) {
this.completeSpecCount++;
if (isUndefined(this.views.specs[spec.id])) {
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
}
var specView = this.views.specs[spec.id];
switch (specView.status()) {
case 'passed':
this.passedCount++;
break;
case 'failed':
this.failedCount++;
break;
case 'skipped':
this.skippedCount++;
break;
}
specView.refresh();
this.refresh();
};
this.suiteComplete = function(suite) {
var suiteView = this.views.suites[suite.id];
if (isUndefined(suiteView)) {
return;
}
suiteView.refresh();
};
this.refresh = function() {
if (isUndefined(this.resultsMenu)) {
this.createResultsMenu();
}
// currently running UI
if (isUndefined(this.runningAlert)) {
this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
dom.alert.appendChild(this.runningAlert);
}
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
// skipped specs UI
if (isUndefined(this.skippedAlert)) {
this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
}
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.skippedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.skippedAlert);
}
// passing specs UI
if (isUndefined(this.passedAlert)) {
this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
}
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
// failing specs UI
if (isUndefined(this.failedAlert)) {
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
}
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
if (this.failedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.failedAlert);
dom.alert.appendChild(this.resultsMenu);
}
// summary info
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
};
this.complete = function() {
dom.alert.removeChild(this.runningAlert);
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.failedCount === 0) {
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
} else {
showDetails();
}
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
};
return this;
function showDetails() {
if (dom.reporter.className.search(/showDetails/) === -1) {
dom.reporter.className += " showDetails";
}
}
function isUndefined(obj) {
return typeof obj === 'undefined';
}
function isDefined(obj) {
return !isUndefined(obj);
}
function specPluralizedFor(count) {
var str = count + " spec";
if (count > 1) {
str += "s"
}
return str;
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
this.spec = spec;
this.dom = dom;
this.views = views;
this.symbol = this.createDom('li', { className: 'pending' });
this.dom.symbolSummary.appendChild(this.symbol);
this.summary = this.createDom('div', { className: 'specSummary' },
this.createDom('a', {
className: 'description',
href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.description)
);
this.detail = this.createDom('div', { className: 'specDetail' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.getFullName())
);
};
jasmine.HtmlReporter.SpecView.prototype.status = function() {
return this.getSpecStatus(this.spec);
};
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
this.symbol.className = this.status();
switch (this.status()) {
case 'skipped':
break;
case 'passed':
this.appendSummaryToSuiteDiv();
break;
case 'failed':
this.appendSummaryToSuiteDiv();
this.appendFailureDetail();
break;
}
};
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
this.summary.className += ' ' + this.status();
this.appendToSummary(this.spec, this.summary);
};
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
this.detail.className += ' ' + this.status();
var resultItems = this.spec.results().getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
this.detail.appendChild(messagesDiv);
this.dom.details.appendChild(this.detail);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
this.suite = suite;
this.dom = dom;
this.views = views;
this.element = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
);
this.appendToSummary(this.suite, this.element);
};
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
return this.getSpecStatus(this.suite);
};
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
this.element.className += " " + this.status();
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
/* @deprecated Use jasmine.HtmlReporter instead
*/
jasmine.TrivialReporter = function(doc) {
this.document = doc || document;
this.suiteDivs = {};
this.logRunningSpecs = false;
};
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) { el.appendChild(child); }
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
var showPassed, showSkipped;
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
this.createDom('div', { className: 'banner' },
this.createDom('div', { className: 'logo' },
this.createDom('span', { className: 'title' }, "Jasmine"),
this.createDom('span', { className: 'version' }, runner.env.versionString())),
this.createDom('div', { className: 'options' },
"Show ",
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
)
),
this.runnerDiv = this.createDom('div', { className: 'runner running' },
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
);
this.document.body.appendChild(this.outerDiv);
var suites = runner.suites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
var suiteDiv = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
this.suiteDivs[suite.id] = suiteDiv;
var parentDiv = this.outerDiv;
if (suite.parentSuite) {
parentDiv = this.suiteDivs[suite.parentSuite.id];
}
parentDiv.appendChild(suiteDiv);
}
this.startedAt = new Date();
var self = this;
showPassed.onclick = function(evt) {
if (showPassed.checked) {
self.outerDiv.className += ' show-passed';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
}
};
showSkipped.onclick = function(evt) {
if (showSkipped.checked) {
self.outerDiv.className += ' show-skipped';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
}
};
};
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
var results = runner.results();
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
this.runnerDiv.setAttribute("class", className);
//do it twice for IE
this.runnerDiv.setAttribute("className", className);
var specs = runner.specs();
var specCount = 0;
for (var i = 0; i < specs.length; i++) {
if (this.specFilter(specs[i])) {
specCount++;
}
}
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
};
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
var results = suite.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.totalCount === 0) { // todo: change this to check results.skipped
status = 'skipped';
}
this.suiteDivs[suite.id].className += " " + status;
};
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
if (this.logRunningSpecs) {
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
var results = spec.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
var specDiv = this.createDom('div', { className: 'spec ' + status },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(spec.getFullName()),
title: spec.getFullName()
}, spec.description));
var resultItems = results.getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
specDiv.appendChild(messagesDiv);
}
this.suiteDivs[spec.suite.id].appendChild(specDiv);
};
jasmine.TrivialReporter.prototype.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
jasmine.TrivialReporter.prototype.getLocation = function() {
return this.document.location;
};
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
var paramMap = {};
var params = this.getLocation().search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
if (!paramMap.spec) {
return true;
}
return spec.getFullName().indexOf(paramMap.spec) === 0;
};

View File

@@ -0,0 +1,82 @@
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
#HTMLReporter a { text-decoration: none; }
#HTMLReporter a:hover { text-decoration: underline; }
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
#HTMLReporter .version { color: #aaaaaa; }
#HTMLReporter .banner { margin-top: 14px; }
#HTMLReporter .duration { color: #aaaaaa; float: right; }
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
#HTMLReporter .runningAlert { background-color: #666666; }
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
#HTMLReporter .passingAlert { background-color: #a6b779; }
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
#HTMLReporter .failingAlert { background-color: #cf867e; }
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
#HTMLReporter .results { margin-top: 14px; }
#HTMLReporter #details { display: none; }
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter.showDetails .summary { display: none; }
#HTMLReporter.showDetails #details { display: block; }
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter .summary { margin-top: 14px; }
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
#HTMLReporter .description + .suite { margin-top: 0; }
#HTMLReporter .suite { margin-top: 14px; }
#HTMLReporter .suite a { color: #333333; }
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
#HTMLReporter .resultMessage span.result { display: block; }
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
#TrivialReporter .runner.running { background-color: yellow; }
#TrivialReporter .options { text-align: right; font-size: .8em; }
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
#TrivialReporter .suite .suite { margin: 5px; }
#TrivialReporter .suite.passed { background-color: #dfd; }
#TrivialReporter .suite.failed { background-color: #fdd; }
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
#TrivialReporter .spec.skipped { background-color: #bbb; }
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
#TrivialReporter .passed { background-color: #cfc; display: none; }
#TrivialReporter .failed { background-color: #fbb; }
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
#TrivialReporter .resultMessage .mismatch { color: black; }
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }

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,60 @@
context("bootstrap")
test_that("CSS unit validation", {
# On error, return NA; on success, return result
validateCssUnit_wrap <- function(x) {
tryCatch(validateCssUnit(x), error = function(e) { NA_character_ })
}
# Test strings and expected results
strings <- c("100x", "10px", "10.4px", ".4px", "1px0", "px", "5", "%", "5%", "auto", "1auto", "")
expected <- c(NA, "10px", "10.4px", ".4px", NA, NA, NA, NA, "5%", "auto", NA, NA)
results <- vapply(strings, validateCssUnit_wrap, character(1), USE.NAMES = FALSE)
expect_equal(results, expected)
# Numbers should return string with "px"
expect_equal(validateCssUnit(100), "100px")
})
test_that("Repeated names for selectInput and radioButtons choices", {
# These test might be a bit too closely tied to the exact structure of the
# tag object, but they get the job done for now.
# Select input
x <- selectInput('id','label', choices = c(a='x1', a='x2', b='x3'))
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)
# 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[[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[[4]]$children[[2]]$children[[1]], 'b')
expect_equal(choices[[4]]$children[[1]]$attribs$value, 'x3')
expect_equal(choices[[4]]$children[[1]]$attribs$checked, NULL)
})

73
inst/tests/test-gc.r Normal file
View File

@@ -0,0 +1,73 @@
context("garbage collection")
test_that("unreferenced observers are garbage collected", {
vals_removed <- FALSE
obs_removed <- FALSE
vals <- reactiveValues(A=1)
obs <- observe({ vals$A })
# These are called when the objects are garbage-collected
reg.finalizer(attr(.subset2(vals,'impl'), ".xData"),
function(e) vals_removed <<- TRUE)
reg.finalizer(attr(obs, ".xData"),
function(e) obs_removed <<- TRUE)
flushReact()
# Removing this reference to obs doesn't delete it because vals still has a
# reference to it
rm(obs)
invisible(gc())
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
# Updating vals$A and flushing won't make obs go away because it creates a new
# context, and vals$A's context tracks obs's context as a dependent
vals$A <- 2
flushReact()
invisible(gc())
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
# Removing vals will result in vals and obs being garbage collected since
# there are no other references to them
rm(vals)
invisible(gc())
expect_equal(c(vals_removed, obs_removed), c(TRUE, TRUE))
})
test_that("suspended observers are garbage collected", {
vals_removed <- FALSE
obs_removed <- FALSE
vals <- reactiveValues(A=1)
obs <- observe({ vals$A })
# These are called when the objects are garbage-collected
reg.finalizer(attr(.subset2(vals,'impl'), ".xData"),
function(e) vals_removed <<- TRUE)
reg.finalizer(attr(obs, ".xData"),
function(e) obs_removed <<- TRUE)
flushReact()
vals$A <- 2
flushReact()
invisible(gc())
# Simply suspending and removing our reference to obs doesn't result in GC,
# because vals's context still has a reference to obs's context, as a dependent
obs$suspend()
rm(obs)
invisible(gc())
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
# Next time we update vals$A and flush, there's no more reference to obs
vals$A <- 3
flushReact()
invisible(gc())
expect_equal(c(vals_removed, obs_removed), c(FALSE, TRUE))
# Deleting vals should work immediately now
rm(vals)
invisible(gc()) # Removes vals object
expect_equal(c(vals_removed, obs_removed), c(TRUE, TRUE))
})

View File

@@ -0,0 +1,665 @@
context("reactivity")
# Test for correct behavior of ReactiveValues
test_that("ReactiveValues", {
# Creation and indexing into ReactiveValues -------------------------------
values <- reactiveValues()
# $ indexing
values$a <- 3
expect_equal(isolate(values$a), 3)
# [[ indexing
values[['a']] <- 4
expect_equal(isolate(values[['a']]), 4)
# Create with initialized values
values <- reactiveValues(a=1, b=2)
expect_equal(isolate(values$a), 1)
expect_equal(isolate(values[['b']]), 2)
# NULL values -------------------------------------------------------------
# Initializing with NULL value
values <- reactiveValues(a=NULL, b=2)
# a should exist and be NULL
expect_equal(isolate(names(values)), c("a", "b"))
expect_true(is.null(isolate(values$a)))
# Assigning NULL should keep object (not delete it), and set value to NULL
values$b <- NULL
expect_equal(isolate(names(values)), c("a", "b"))
expect_true(is.null(isolate(values$b)))
# Errors -----------------------------------------------------------------
# Error: indexing with non-string
expect_error(isolate(values[[1]]))
expect_error(isolate(values[[NULL]]))
expect_error(isolate(values[[list('a')]]))
# Error: [ indexing shouldn't work
expect_error(isolate(values['a']))
expect_error(isolate(values['a'] <- 1))
# Error: unnamed arguments
expect_error(reactiveValues(1))
expect_error(reactiveValues(1, b=2))
# Error: assignment to readonly values
values <- .createReactiveValues(ReactiveValues$new(), readonly = TRUE)
expect_error(values$a <- 1)
})
# Test for overreactivity. funcB has an indirect dependency on valueA (via
# funcA) and also a direct dependency on valueA. When valueA changes, funcB
# should only execute once.
test_that("Functions are not over-reactive", {
values <- reactiveValues(A=10)
funcA <- reactive({
values$A
})
funcB <- reactive({
funcA()
values$A
})
obsC <- observe({
funcB()
})
flushReact()
expect_equal(execCount(funcB), 1)
expect_equal(execCount(obsC), 1)
values$A <- 11
flushReact()
expect_equal(execCount(funcB), 2)
expect_equal(execCount(obsC), 2)
})
## "foo => bar" is defined as "foo is a dependency of bar"
##
## vA => fB
## (fB, vA) => obsE
## (fB, vA) => obsF
##
## obsE and obsF should each execute once when vA changes.
test_that("overreactivity2", {
# ----------------------------------------------
# Test 1
# B depends on A, and observer depends on A and B. The observer uses A and
# B, in that order.
# This is to store the value from observe()
observed_value1 <- NA
observed_value2 <- NA
values <- reactiveValues(A=1)
funcB <- reactive({
values$A + 5
})
obsC <- observe({
observed_value1 <<- funcB() * values$A
})
obsD <- observe({
observed_value2 <<- funcB() * values$A
})
flushReact()
expect_equal(observed_value1, 6) # Should be 1 * (1 + 5) = 6
expect_equal(observed_value2, 6) # Should be 1 * (1 + 5) = 6
expect_equal(execCount(funcB), 1)
expect_equal(execCount(obsC), 1)
expect_equal(execCount(obsD), 1)
values$A <- 2
flushReact()
expect_equal(observed_value1, 14) # Should be 2 * (2 + 5) = 14
expect_equal(observed_value2, 14) # Should be 2 * (2 + 5) = 14
expect_equal(execCount(funcB), 2)
expect_equal(execCount(obsC), 2)
expect_equal(execCount(obsD), 2)
})
## Test for isolation. funcB depends on funcA depends on valueA. When funcA
## is invalidated, if its new result is not different than its old result,
## then it doesn't invalidate its dependents. This is done by adding an observer
## (valueB) between obsA and funcC.
##
## valueA => obsB => valueC => funcD => obsE
test_that("isolation", {
values <- reactiveValues(A=10, C=NULL)
obsB <- observe({
values$C <- values$A > 0
})
funcD <- reactive({
values$C
})
obsE <- observe({
funcD()
})
flushReact()
countD <- execCount(funcD)
values$A <- 11
flushReact()
expect_equal(execCount(funcD), countD)
})
## Test for laziness. With lazy evaluation, the observers should "pull" values
## from their dependent functions. In contrast, eager evaluation would have
## reactive values and functions "push" their changes down to their descendents.
test_that("laziness", {
values <- reactiveValues(A=10)
funcA <- reactive({
values$A > 0
})
funcB <- reactive({
funcA()
})
obsC <- observe({
if (values$A > 10)
return()
funcB()
})
flushReact()
expect_equal(execCount(funcA), 1)
expect_equal(execCount(funcB), 1)
expect_equal(execCount(obsC), 1)
values$A <- 11
flushReact()
expect_equal(execCount(funcA), 1)
expect_equal(execCount(funcB), 1)
expect_equal(execCount(obsC), 2)
})
## Suppose B depends on A and C depends on A and B. Then when A is changed,
## the evaluation order should be A, B, C. Also, each time A is changed, B and
## C should be run once, if we want to be maximally efficient.
test_that("order of evaluation", {
# ----------------------------------------------
# Test 1
# B depends on A, and observer depends on A and B. The observer uses A and
# B, in that order.
# This is to store the value from observe()
observed_value <- NA
values <- reactiveValues(A=1)
funcB <- reactive({
values$A + 5
})
obsC <- observe({
observed_value <<- values$A * funcB()
})
flushReact()
expect_equal(observed_value, 6) # Should be 1 * (1 + 5) = 6
expect_equal(execCount(funcB), 1)
expect_equal(execCount(obsC), 1)
values$A <- 2
flushReact()
expect_equal(observed_value, 14) # Should be 2 * (2 + 5) = 14
expect_equal(execCount(funcB), 2)
expect_equal(execCount(obsC), 2)
# ----------------------------------------------
# Test 2:
# Same as Test 1, except the observer uses A and B in reversed order.
# Resulting values should be the same.
observed_value <- NA
values <- reactiveValues(A=1)
funcB <- reactive({
values$A + 5
})
obsC <- observe({
observed_value <<- funcB() * values$A
})
flushReact()
# Should be 1 * (1 + 5) = 6
expect_equal(observed_value, 6)
expect_equal(execCount(funcB), 1)
expect_equal(execCount(obsC), 1)
values$A <- 2
flushReact()
# Should be 2 * (2 + 5) = 14
expect_equal(observed_value, 14)
expect_equal(execCount(funcB), 2)
expect_equal(execCount(obsC), 2)
})
## Expressions in isolate() should not invalidate the parent context.
test_that("isolate() blocks invalidations from propagating", {
obsC_value <- NA
obsD_value <- NA
values <- reactiveValues(A=1, B=10)
funcB <- reactive({
values$B + 100
})
# References to valueB and funcB are isolated
obsC <- observe({
obsC_value <<-
values$A + isolate(values$B) + isolate(funcB())
})
# In contrast with obsC, this has a non-isolated reference to funcB
obsD <- observe({
obsD_value <<-
values$A + isolate(values$B) + funcB()
})
flushReact()
expect_equal(obsC_value, 121)
expect_equal(execCount(obsC), 1)
expect_equal(obsD_value, 121)
expect_equal(execCount(obsD), 1)
# Changing A should invalidate obsC and obsD
values$A <- 2
flushReact()
expect_equal(obsC_value, 122)
expect_equal(execCount(obsC), 2)
expect_equal(obsD_value, 122)
expect_equal(execCount(obsD), 2)
# Changing B shouldn't invalidate obsC becuause references to B are in isolate()
# But it should invalidate obsD.
values$B <- 20
flushReact()
expect_equal(obsC_value, 122)
expect_equal(execCount(obsC), 2)
expect_equal(obsD_value, 142)
expect_equal(execCount(obsD), 3)
# Changing A should invalidate obsC and obsD, and they should see updated
# values for valueA, valueB, and funcB
values$A <- 3
flushReact()
expect_equal(obsC_value, 143)
expect_equal(execCount(obsC), 3)
expect_equal(obsD_value, 143)
expect_equal(execCount(obsD), 4)
})
test_that("isolate() evaluates expressions in calling environment", {
outside <- 1
inside <- 1
loc <- 1
outside <- isolate(2) # Assignment outside isolate
isolate(inside <- 2) # Assignment inside isolate
# Should affect vars in the calling environment
expect_equal(outside, 2)
expect_equal(inside, 2)
isolate(local(loc <<- 2)) # <<- inside isolate(local)
isolate(local(loc <- 3)) # <- inside isolate(local) - should have no effect
expect_equal(loc, 2)
})
test_that("Circular refs/reentrancy in reactive functions work", {
values <- reactiveValues(A=3)
funcB <- reactive({
# Each time fB executes, it reads and then writes valueA,
# effectively invalidating itself--until valueA becomes 0.
if (values$A == 0)
return()
values$A <- values$A - 1
return(values$A)
})
obsC <- observe({
funcB()
})
flushReact()
expect_equal(execCount(obsC), 4)
values$A <- 3
flushReact()
expect_equal(execCount(obsC), 8)
})
test_that("Simple recursion", {
values <- reactiveValues(A=5)
funcB <- reactive({
if (values$A == 0)
return(0)
values$A <- values$A - 1
funcB()
})
obsC <- observe({
funcB()
})
flushReact()
expect_equal(execCount(obsC), 2)
expect_equal(execCount(funcB), 6)
})
test_that("Non-reactive recursion", {
nonreactiveA <- 3
outputD <- NULL
funcB <- reactive({
if (nonreactiveA == 0)
return(0)
nonreactiveA <<- nonreactiveA - 1
return(funcB())
})
obsC <- observe({
outputD <<- funcB()
})
flushReact()
expect_equal(execCount(funcB), 4)
expect_equal(outputD, 0)
})
test_that("Circular dep with observer only", {
values <- reactiveValues(A=3)
obsB <- observe({
if (values$A == 0)
return()
values$A <- values$A - 1
})
flushReact()
expect_equal(execCount(obsB), 4)
})
test_that("Writing then reading value is not circular", {
values <- reactiveValues(A=3)
funcB <- reactive({
values$A <- isolate(values$A) - 1
values$A
})
obsC <- observe({
funcB()
})
flushReact()
expect_equal(execCount(obsC), 1)
values$A <- 10
flushReact()
expect_equal(execCount(obsC), 2)
})
test_that("names() and reactiveValuesToList()", {
values <- reactiveValues(A=1, .B=2)
# Dependent on names
depNames <- observe({
names(values)
})
# Dependent on all non-hidden objects
depValues <- observe({
reactiveValuesToList(values)
})
# Dependent on all objects, including hidden
depAllValues <- observe({
reactiveValuesToList(values, all.names = TRUE)
})
# names() returns all names
expect_equal(sort(isolate(names(values))), sort(c(".B", "A")))
# Assigning names fails
expect_error(isolate(names(v) <- c('x', 'y')))
expect_equal(isolate(reactiveValuesToList(values)), list(A=1))
expect_equal(isolate(reactiveValuesToList(values, all.names=TRUE)), list(A=1, .B=2))
flushReact()
expect_equal(execCount(depNames), 1)
expect_equal(execCount(depValues), 1)
expect_equal(execCount(depAllValues), 1)
# Update existing variable
values$A <- 2
flushReact()
expect_equal(execCount(depNames), 1)
expect_equal(execCount(depValues), 2)
expect_equal(execCount(depAllValues), 2)
# Update existing hidden variable
values$.B <- 3
flushReact()
expect_equal(execCount(depNames), 1)
expect_equal(execCount(depValues), 2)
expect_equal(execCount(depAllValues), 3)
# Add new variable
values$C <- 1
flushReact()
expect_equal(execCount(depNames), 2)
expect_equal(execCount(depValues), 3)
expect_equal(execCount(depAllValues), 4)
# Add new hidden variable
values$.D <- 1
flushReact()
expect_equal(execCount(depNames), 3)
expect_equal(execCount(depValues), 3)
expect_equal(execCount(depAllValues), 5)
})
test_that("Observer pausing works", {
values <- reactiveValues(a=1)
funcA <- reactive({
values$a
})
obsB <- observe({
funcA()
})
# Important: suspend() only affects observer at invalidation time
# Observers are invalidated at creation time, so it will run once regardless
# of being suspended
obsB$suspend()
flushReact()
expect_equal(execCount(funcA), 1)
expect_equal(execCount(obsB), 1)
# When resuming, if nothing changed, don't do anything
obsB$resume()
flushReact()
expect_equal(execCount(funcA), 1)
expect_equal(execCount(obsB), 1)
# Make sure suspended observers do not flush, but do invalidate
obsB_invalidated <- FALSE
obsB$onInvalidate(function() {obsB_invalidated <<- TRUE})
obsB$suspend()
values$a <- 2
flushReact()
expect_equal(obsB_invalidated, TRUE)
expect_equal(execCount(funcA), 1)
expect_equal(execCount(obsB), 1)
obsB$resume()
values$a <- 2.5
obsB$suspend()
flushReact()
expect_equal(execCount(funcA), 2)
expect_equal(execCount(obsB), 2)
values$a <- 3
flushReact()
expect_equal(execCount(funcA), 2)
expect_equal(execCount(obsB), 2)
# If onInvalidate() is added _after_ obsB is suspended and the values$a
# changes, then it shouldn't get run (onInvalidate runs on invalidation, not
# on flush)
values$a <- 4
obsB_invalidated2 <- FALSE
obsB$onInvalidate(function() {obsB_invalidated2 <<- TRUE})
obsB$resume()
flushReact()
expect_equal(execCount(funcA), 3)
expect_equal(execCount(obsB), 3)
expect_equal(obsB_invalidated2, FALSE)
})
test_that("suspended/resumed observers run at most once", {
values <- reactiveValues(A=1)
obs <- observe(function() {
values$A
})
expect_equal(execCount(obs), 0)
# First flush should run obs once
flushReact()
expect_equal(execCount(obs), 1)
# Modify the dependency at each stage of suspend/resume/flush should still
# only result in one run of obs()
values$A <- 2
obs$suspend()
values$A <- 3
obs$resume()
values$A <- 4
flushReact()
expect_equal(execCount(obs), 2)
})
test_that("reactive() accepts quoted and unquoted expressions", {
vals <- reactiveValues(A=1)
# Unquoted expression, with curly braces
fun <- reactive({ vals$A + 1 })
expect_equal(isolate(fun()), 2)
# Unquoted expression, no curly braces
fun <- reactive(vals$A + 1)
expect_equal(isolate(fun()), 2)
# Quoted expression
fun <- reactive(quote(vals$A + 1), quoted = TRUE)
expect_equal(isolate(fun()), 2)
# Quoted expression, saved in a variable
q_expr <- quote(vals$A + 1)
fun <- reactive(q_expr, quoted = TRUE)
expect_equal(isolate(fun()), 2)
# If function is used, work, but print message
expect_message(fun <- reactive(function() { vals$A + 1 }))
expect_equal(isolate(fun()), 2)
# Check that environment is correct - parent environment should be this one
this_env <- environment()
fun <- reactive(environment())
expect_identical(isolate(parent.env(fun())), this_env)
# Sanity check: environment structure for a reactive() should be the same as for
# a normal function
fun <- function() environment()
expect_identical(parent.env(fun()), this_env)
})
test_that("observe() accepts quoted and unquoted expressions", {
vals <- reactiveValues(A=0)
valB <- 0
# Unquoted expression, with curly braces
observe({ valB <<- vals$A + 1})
flushReact()
expect_equal(valB, 1)
# Unquoted expression, no curly braces
observe({ valB <<- vals$A + 2})
flushReact()
expect_equal(valB, 2)
# Quoted expression
observe(quote(valB <<- vals$A + 3), quoted = TRUE)
flushReact()
expect_equal(valB, 3)
# Quoted expression, saved in a variable
q_expr <- quote(valB <<- vals$A + 4)
fun <- observe(q_expr, quoted = TRUE)
flushReact()
expect_equal(valB, 4)
# If function is used, work, but print message
expect_message(observe(function() { valB <<- vals$A + 5 }))
flushReact()
expect_equal(valB, 5)
# Check that environment is correct - parent environment should be this one
this_env <- environment()
inside_env <- NULL
fun <- observe(inside_env <<- environment())
flushReact()
expect_identical(parent.env(inside_env), this_env)
})
test_that("Observer priorities are respected", {
results <- c()
observe(results <<- c(results, 10), priority=10)
observe(results <<- c(results, 30), priority=30)
observe(results <<- c(results, 20), priority=20L)
observe(results <<- c(results, 21), priority=20)
observe(results <<- c(results, 22), priority=20L)
flushReact()
expect_identical(results, c(30, 20, 21, 22, 10))
})

300
inst/tests/test-tags.r Normal file
View File

@@ -0,0 +1,300 @@
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]], "<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]], "<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)
})

56
inst/tests/test-text.R Normal file
View File

@@ -0,0 +1,56 @@
context("text")
test_that("renderPrint and renderText behavior is correct", {
expect_equal(isolate(renderPrint({ "foo" })()),
'[1] "foo"')
expect_equal(isolate(renderPrint({ invisible("foo") })()),
'')
expect_equal(isolate(renderPrint({ print("foo"); "bar"})()),
'[1] "foo"\n[1] "bar"')
expect_equal(isolate(renderPrint({ NULL })()),
'NULL')
expect_equal(isolate(renderPrint({ invisible() })()),
'')
expect_equal(isolate(renderPrint({ 1:5 })()),
'[1] 1 2 3 4 5')
expect_equal(isolate(renderText({ "foo" })()),
'foo')
expect_equal(isolate(renderText({ invisible("foo") })()),
'foo')
# Capture the print output so it's not shown on console during test, and
# also check that it is correct
print_out <- capture.output(ret <- isolate(renderText({ print("foo"); "bar"})()))
expect_equal(ret, 'bar')
expect_equal(print_out, '[1] "foo"')
expect_equal(isolate(renderText({ NULL })()),
'')
expect_equal(isolate(renderText({ invisible() })()),
'')
expect_equal(isolate(renderText({ 1:5 })()),
'1 2 3 4 5')
})
test_that("reactive functions save visibility state", {
# Call each function twice - should be no change in state with second call
# invisible NULL
f <- reactive({ invisible() })
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=FALSE))
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=FALSE))
# visible NULL
f <- reactive({ NULL })
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=TRUE))
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=TRUE))
# invisible non-NULL value
f <- reactive({ invisible(10)})
expect_identical(withVisible(isolate(f())), list(value=10, visible=FALSE))
expect_identical(withVisible(isolate(f())), list(value=10, visible=FALSE))
# visible non-NULL value
f <- reactive({ 10 })
expect_identical(withVisible(isolate(f())), list(value=10, visible=TRUE))
expect_identical(withVisible(isolate(f())), list(value=10, visible=TRUE))
})

20
inst/tests/test-url.R Normal file
View File

@@ -0,0 +1,20 @@
context("URL")
test_that("Query string parsing", {
expect_identical(
parseQueryString("?foo=1&bar=b+a%20r&b+a%20z=baz&=nokey&novalue=&=&noequal&end=end"),
list(
foo = '1',
bar = 'b a r',
`b a z` = 'baz',
'nokey',
novalue = '',
'',
noequal = '',
end = 'end'
)
)
# Should be the same with or without leading question mark
expect_identical(parseQueryString("?foo=1&bar=b"), parseQueryString("foo=1&bar=b"))
})

12
inst/www/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head></head>
<body>
<h1>No UI defined</h1>
<p>Shiny couldn't find any UI for this application. We looked in:</p>
<ul>
<li><code>www/index.html</code></li>
<li><code>ui.R</code></li>
</ul>
</body>
</html>

View File

@@ -0,0 +1,570 @@
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400,600' rel='stylesheet' type='text/css'>
<style type="text/css">
html, body {
font-family: 'Source Sans Pro', sans-serif;
font-weight: 400;
overflow: hidden;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
div {
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
cursor: default;
}
#instructions, #ended {
position: relative;
font-weight: 200;
color: #444;
top: 20px;
font-size: 30px;
text-align: center;
}
#ended strong {
font-weight: 600;
}
svg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.node {
cursor: pointer;
}
.node text {
font-family: 'Source Code Pro', monospace;
font-weight: normal;
text-anchor: start;
fill: #999;
user-select: none;
transition: fill 0.75s ease;
}
.node.running text {
fill: black;
}
.node.changed text {
fill: red;
}
.node text tspan {
white-space: pre;
}
.node path {
fill: white;
stroke: #777;
stroke-width: 7.5px;
transition: fill 0.75s ease;
}
.node.observer path {
}
.node.observable path {
}
.node.value path {
}
.node.invalidated path {
fill: #E0E0E0;
/*fill: url(#diagonalHatch);*/
}
.node.running path {
fill: #61B97E;
}
#legend {
font-size: 22px;
position: fixed;
bottom: 10px;
right: 20px;
}
.color {
display: inline-block;
border: 1px solid #777;
height: 14px;
width: 14px;
}
.color.normal {
background-color: #white;
}
.color.invalidated {
background-color: #E0E0E0;
}
.color.running {
background-color: #61B97E;
}
#triangle {
fill: #CCC;
}
.link {
fill: none;
stroke: #CCC;
stroke-width: 0.5px;
}
#description {
position: fixed;
width: 300px;
left: 630px;
top: 36px;
height: auto;
display: none;
}
</style>
<script>
var log = [
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr \"dataset\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 1\n $ dataset: chr \"rock\"" },
{ "action" : "valueChange", "id" : "input$dataset", "value" : " chr \"rock\"" },
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:2] \"caption\" \"dataset\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 2\n $ caption: chr \"Data Summary\"\n $ dataset: chr \"rock\"" },
{ "action" : "valueChange", "id" : "input$caption", "value" : " chr \"Data Summary\"" },
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Data Summary\"\n $ obs : num 10\n $ dataset: chr \"rock\"" },
{ "action" : "valueChange", "id" : "input$obs", "value" : " num 10" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr \"output_caption_hidden\"" },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 1\n $ output_caption_hidden: logi FALSE" },
{ "action" : "valueChange", "id" : "clientData$output_caption_hidden", "value" : " logi FALSE" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:2] \"output_caption_hidden\" \"output_summary_hidden\"" },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 2\n $ output_caption_hidden: logi FALSE\n $ output_summary_hidden: logi FALSE" },
{ "action" : "valueChange", "id" : "clientData$output_summary_hidden", "value" : " logi FALSE" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:3] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\"" },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 3\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ output_summary_hidden: logi FALSE" },
{ "action" : "valueChange", "id" : "clientData$output_view_hidden", "value" : " logi FALSE" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:4] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 4\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ output_summary_hidden: logi FALSE" },
{ "action" : "valueChange", "id" : "clientData$pixelratio", "value" : " num 2" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:5] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 5\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_protocol", "value" : " chr \"http:\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:6] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 6\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_hostname", "value" : " chr \"localhost\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:7] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 7\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_port", "value" : " chr \"8100\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:8] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 8\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_pathname", "value" : " chr \"/\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:9] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 9\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_search : chr \"\"\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_search", "value" : " chr \"\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:10] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 10\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ url_hash_initial : chr \"\"\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_search : chr \"\"\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_hash_initial", "value" : " chr \"\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:11] \"allowDataUriScheme\" \"output_caption_hidden\" \"output_summary_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 11\n $ allowDataUriScheme : logi TRUE\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ url_hash_initial : chr \"\"\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_search : chr \"\"\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$allowDataUriScheme", "value" : " logi TRUE" },
{ "action" : "ctx", "id" : "1", "label" : "output$caption <- renderText({ \n input$caption\n})", "type" : "observer", "prevId" : "" },
{ "action" : "invalidate", "id" : "1" },
{ "action" : "ctx", "id" : "2", "label" : "output$summary <- renderPrint({ \n dataset <- datasetInput()\n summary(dataset)\n})", "type" : "observer", "prevId" : "" },
{ "action" : "invalidate", "id" : "2" },
{ "action" : "ctx", "id" : "3", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "" },
{ "action" : "invalidate", "id" : "3" },
{ "action" : "ctx", "id" : "4", "label" : "output$caption <- renderText({ \n input$caption\n})", "type" : "observer", "prevId" : "1" },
{ "action" : "enter", "id" : "4" },
{ "action" : "dep", "id" : "4", "dependsOn" : "input$caption" },
{ "action" : "exit", "id" : "4" },
{ "action" : "ctx", "id" : "5", "label" : "output$summary <- renderPrint({ \n dataset <- datasetInput()\n summary(dataset)\n})", "type" : "observer", "prevId" : "2" },
{ "action" : "enter", "id" : "5" },
{ "action" : "ctx", "id" : "6", "label" : "reactive({ \n switch(input$dataset, rock = rock, pressure = pressure, cars = cars)\n})", "type" : "observable", "prevId" : "" },
{ "action" : "enter", "id" : "6" },
{ "action" : "dep", "id" : "6", "dependsOn" : "input$dataset" },
{ "action" : "exit", "id" : "6" },
{ "action" : "depId", "id" : "5", "dependsOn" : "6" },
{ "action" : "exit", "id" : "5" },
{ "action" : "ctx", "id" : "7", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "3" },
{ "action" : "enter", "id" : "7" },
{ "action" : "depId", "id" : "7", "dependsOn" : "6" },
{ "action" : "dep", "id" : "7", "dependsOn" : "input$obs" },
{ "action" : "exit", "id" : "7" },
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Pressure Summary\"\n $ obs : num 10\n $ dataset: chr \"rock\"" },
{ "action" : "valueChange", "id" : "input$caption", "value" : " chr \"Pressure Summary\"" },
{ "action" : "invalidate", "id" : "4" },
{ "action" : "ctx", "id" : "8", "label" : "output$caption <- renderText({ \n input$caption\n})", "type" : "observer", "prevId" : "4" },
{ "action" : "enter", "id" : "8" },
{ "action" : "dep", "id" : "8", "dependsOn" : "input$caption" },
{ "action" : "exit", "id" : "8" },
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Pressure Summary\"\n $ obs : num 10\n $ dataset: chr \"pressure\"" },
{ "action" : "valueChange", "id" : "input$dataset", "value" : " chr \"pressure\"" },
{ "action" : "invalidate", "id" : "6" },
{ "action" : "invalidate", "id" : "5" },
{ "action" : "invalidate", "id" : "7" },
{ "action" : "ctx", "id" : "9", "label" : "output$summary <- renderPrint({ \n dataset <- datasetInput()\n summary(dataset)\n})", "type" : "observer", "prevId" : "5" },
{ "action" : "enter", "id" : "9" },
{ "action" : "ctx", "id" : "10", "label" : "reactive({ \n switch(input$dataset, rock = rock, pressure = pressure, cars = cars)\n})", "type" : "observable", "prevId" : "6" },
{ "action" : "enter", "id" : "10" },
{ "action" : "dep", "id" : "10", "dependsOn" : "input$dataset" },
{ "action" : "exit", "id" : "10" },
{ "action" : "depId", "id" : "9", "dependsOn" : "10" },
{ "action" : "exit", "id" : "9" },
{ "action" : "ctx", "id" : "11", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "7" },
{ "action" : "enter", "id" : "11" },
{ "action" : "depId", "id" : "11", "dependsOn" : "10" },
{ "action" : "dep", "id" : "11", "dependsOn" : "input$obs" },
{ "action" : "exit", "id" : "11" },
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Pressure Summary\"\n $ obs : num 15\n $ dataset: chr \"pressure\"" },
{ "action" : "valueChange", "id" : "input$obs", "value" : " num 15" },
{ "action" : "invalidate", "id" : "11" },
{ "action" : "ctx", "id" : "12", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "11" },
{ "action" : "enter", "id" : "12" },
{ "action" : "depId", "id" : "12", "dependsOn" : "10" },
{ "action" : "dep", "id" : "12", "dependsOn" : "input$obs" },
{ "action" : "exit", "id" : "12" }
];
try {
log = __DATA__;
} catch (e) {}
var nodes = {};
var nodeList = [];
var nodeSelection = null;
var links = [];
var linkSelection = null;
var node, link; // d3 selections
var MAX_LINES = 6;
var force = d3.layout.force()
.charge(-100)
.nodes(nodeList)
.links(links);
force.on('tick', onTick);
function pathDataForNode(node) {
/*
d="m 58,2 c -75,0 -75,100 0,100 l 60,0 l 50,-50 l -50,-50 Z"
d="m 58,2 c -75,0 -75,100 0,100 l 100,0 l 0,-100 Z"
d="m 2,0 l 0,100 l 100,0 l 50,-50 l -50,-50 Z"
*/
switch (node.type) {
case 'observer':
return 'M -25,-50 c -75,0 -75,100 0,100 l 100,0 l 0,-100 Z';
case 'observable':
return 'M -25,-50 c -75,0 -75,100 0,100 l 60,0 l 50,-50 l -50,-50 Z';
case 'value':
return 'M -50,-50 l 0,100 l 100,0 l 50,-50 l -50,-50 Z';
}
}
function getSourceCoords(node) {
switch (node.type) {
case 'observer':
case 'observable':
return {x: node.x - 5, y: node.y};
default:
return {x: node.x, y: node.y};
}
}
function getTargetCoords(node) {
switch (node.type) {
case 'observable':
return {x: node.x + 7, y: node.y};
case 'value':
return {x: node.x + 8, y: node.y};
default:
return {x: node.x, y: node.y};
}
}
function multilineTextNode(node) {
var MAX_LINES = 6;
var fade = false;
var el = d3.select(this);
var lines = el.text().split('\n');
if (lines.length > MAX_LINES) {
lines.splice(MAX_LINES);
fade = true;
}
el.text('');
var tspan = el.selectAll('tspan').data(lines);
tspan.enter().append('tspan');
tspan
.attr('x', 8)
.attr('dy', function(line, i) { return i > 0 ? '1em' : 0})
.attr('opacity', function(line, i) {
if (!fade)
return 1;
return Math.min(1, (MAX_LINES - i) * 0.25 - 0.15);
})
.text(function(line) { return line; });
}
function update() {
force.size([document.documentElement.clientWidth / 4,
document.documentElement.clientHeight / 4]);
var layoutDirty = true;
node = d3.select('#nodes').selectAll('.node').data(nodeList);
//layoutDirty = layoutDirty || !node.enter().empty() || !node.exit().empty();
var newG = node.enter().append('g')
.attr('class', function(n) {return 'node ' + n.type;})
.attr('r', 5)
// don't show until next tick
.style('display', 'none')
.on('mousedown', function() {
d3.event.stopPropagation();
})
.on('mouseover', function(n) {
$('#description').text(n.label);
})
.on('mouseout', function(d, i) {
$('#description').html('');
})
.call(force.drag);
newG.append('path')
.attr('transform', 'scale(0.08)')
.attr('stroke', 'black')
.attr('stroke-width', 4)
.attr('fill', 'white')
.attr('d', pathDataForNode);
newG.append('text')
.attr('x', 3)
.attr('y', 0)
.attr('font-size', 2.5)
.attr('transform', function(n) {
if (n.type !== 'observer')
return 'translate(1.5, 0)';
else
return null;
})
node.exit().remove();
node
.classed('invalidated', function(n) { return n.invalidated; })
.classed('running', function(n) { return n.running; })
.classed('changed', function(n) { return n.changed; })
.attr('fill', function(n) {
if (n.invalidated)
return "url(#diagonalHatch)";
else
return null;
});
var tspan = node.selectAll('text').filter(function(n) {
// This filter is used to disregard all nodes whose labels have
// not changed since the last time we updated them.
var changed = n.label !== this.label;
this.label = n.label;
return changed;
}).selectAll('tspan')
.data(function(n) {
var lines = n.label.split('\n');
if (lines.length > MAX_LINES) {
lines.splice(MAX_LINES);
}
return lines;
});
tspan.enter().append('tspan');
tspan.exit().remove();
tspan
.attr('x', 8)
.attr('dy', function(line, i) { return i > 0 ? '1em' : 0})
.attr('opacity', function(line, i) {
return Math.min(1, (MAX_LINES - i) * 0.25 - 0.15);
})
.text(function(line) { return line; });
link = d3.select('#links').selectAll('.link').data(links);
//layoutDirty = layoutDirty || !link.enter().empty() || !link.exit().empty();
link.enter().append('path')
.attr('class', 'link')
.attr('marker-mid', 'url(#triangle)');
link.exit().remove();
if (layoutDirty) {
force
.nodes(nodeList.filter(function(n) {return !n.hide;}))
.start();
layoutDirty = false;
}
}
function onTick() {
node
.style('display', null)
.attr('transform', function(n) {
return 'translate(' + n.x + ' ' + n.y + ')';
});
link
.attr('d', function(link) {
var source = getSourceCoords(link.source);
var target = getTargetCoords(link.target)
var mid = {
x: (source.x + target.x) / 2,
y: (source.y + target.y) / 2
}
return 'M' + source.x + ',' + source.y +
' L' + mid.x + ',' + mid.y +
' L' + target.x + ',' + target.y;
});
}
function createNode(data) {
var node;
if (!data.prevId) {
node = {
label: data.label,
type: data.type,
hide: data.hide
};
nodes[data.id] = node;
if (!node.hide)
nodeList.push(node);
} else {
node = nodes[data.prevId];
delete nodes[data.prevId];
nodes[data.id] = node;
node.label = data.label;
node.invalidated = false;
}
}
var callbacks = {
ctx: function(data) {
createNode(data);
return true;
},
dep: function(data) {
var dependsOn = nodes[data.dependsOn];
if (!dependsOn) {
createNode({id: data.dependsOn, label: data.dependsOn, type: 'value'});
dependsOn = nodes[data.dependsOn];
}
if (dependsOn.hide) {
dependsOn.hide = false;
nodeList.push(dependsOn);
}
links.push({
source: nodes[data.id],
target: nodes[data.dependsOn]
});
},
depId: function(data) {
links.push({
source: nodes[data.id],
target: nodes[data.dependsOn]
});
},
invalidate: function(data) {
var node = nodes[data.id];
node.invalidated = true;
links = links.filter(function(link) {
return link.source !== node;
});
},
valueChange: function(data) {
var existed = !!nodes[data.id];
createNode({
id: data.id,
label: data.id + ' = ' + data.value,
type: 'value',
prevId: nodes[data.id] ? data.id : null,
hide: existed ? nodes[data.id].hide : true
});
if (!existed || nodes[data.id].hide)
return true;
nodes[data.id].changed = true;
executeBeforeNextCommand.push(function() {
nodes[data.id].changed = false;
});
},
enter: function(data) {
var node = nodes[data.id];
node.running = true;
},
exit: function(data) {
var node = nodes[data.id];
node.running = false;
}
};
function processMessage(data) {
console.log(JSON.stringify(data));
if (!callbacks.hasOwnProperty(data.action))
throw new Error('Unknown action ' + data.action);
var result = callbacks[data.action].call(callbacks, data);
update();
return result;
}
var executeBeforeNextCommand = [];
function doNext() {
while (executeBeforeNextCommand.length)
executeBeforeNextCommand.shift()();
while (log.length)
if (!processMessage(log.shift()))
break;
if (!log.length)
$('#ended').fadeIn(1500);
}
function zoom() {
var scale = d3.event.scale;
var x = d3.event.translate[0];
var y = d3.event.translate[1];
d3.select('#viz').attr('transform', 'scale(' + scale + ') translate(' + x/scale + ' ' + y/scale + ')');
}
$(function() {
d3.select('svg').call(d3.behavior.zoom().scale(4).on('zoom', zoom));
$(document.body).on('keydown', function(e) {
if (e.which === 39 || e.which === 32)
doNext();
if (e.which === 35) {
while (log.length) {
doNext();
}
}
});
doNext();
executeBeforeNextCommand.push(function() {
$('#instructions').fadeOut(1000);
});
});
</script>
<body>
<svg>
<defs>
<marker id="triangle"
viewBox="0 0 10 10"
refX="5" refY="5"
markerWidth="6"
markerHeight="6"
orient="auto">
<path d="M 10 0 L 0 5 L 10 10 z" />
</marker>
<pattern id="diagonalHatch" patternUnits="userSpaceOnUse" width="1" height="1">
<path stroke="black" stroke-width="0.25" fill="none"
d="M-1,1 l2,-2
M0,4 l4,-4
M3,5 l2,-2" />
</pattern>
</defs>
<g id="viz" transform="scale(4)">
<g id="links"></g>
<g id="nodes"></g>
</g>
</svg>
<div id="instructions">
Press right-arrow to advance
</div>
<div id="ended" style="display: none;">
<strong>You&rsquo;ve reached the end</strong><br/>Reload the page to start over
</div>
<div id="legend">
<div class="color normal"></div> Normal<br/>
<div class="color invalidated"></div> Invalidated<br/>
<div class="color running"></div> Running<br/>
</div>
<br/>
<pre id="description"><br/></pre>
</body>
</html>

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 it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

2027
inst/www/shared/bootstrap/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,442 @@
/*!
* Datepicker for Bootstrap
*
* Copyright 2012 Stefan Petre
* Improvements by Andrew Rowls
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.datepicker {
padding: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
direction: ltr;
/*.dow {
border-top: 1px solid #ddd !important;
}*/
}
.datepicker-inline {
width: 220px;
}
.datepicker.datepicker-rtl {
direction: rtl;
}
.datepicker.datepicker-rtl table tr td span {
float: right;
}
.datepicker-dropdown {
top: 0;
left: 0;
}
.datepicker-dropdown:before {
content: '';
display: inline-block;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-bottom-color: rgba(0, 0, 0, 0.2);
position: absolute;
top: -7px;
left: 6px;
}
.datepicker-dropdown:after {
content: '';
display: inline-block;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #ffffff;
position: absolute;
top: -6px;
left: 7px;
}
.datepicker > div {
display: none;
}
.datepicker.days div.datepicker-days {
display: block;
}
.datepicker.months div.datepicker-months {
display: block;
}
.datepicker.years div.datepicker-years {
display: block;
}
.datepicker table {
margin: 0;
}
.datepicker td,
.datepicker th {
text-align: center;
width: 20px;
height: 20px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
border: none;
}
.table-striped .datepicker table tr td,
.table-striped .datepicker table tr th {
background-color: transparent;
}
.datepicker table tr td.day:hover {
background: #eeeeee;
cursor: pointer;
}
.datepicker table tr td.old,
.datepicker table tr td.new {
color: #999999;
}
.datepicker table tr td.disabled,
.datepicker table tr td.disabled:hover {
background: none;
color: #999999;
cursor: default;
}
.datepicker table tr td.today,
.datepicker table tr td.today:hover,
.datepicker table tr td.today.disabled,
.datepicker table tr td.today.disabled:hover {
background-color: #fde19a;
background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
background-image: linear-gradient(top, #fdd49a, #fdf59a);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
border-color: #fdf59a #fdf59a #fbed50;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
color: #000 !important;
}
.datepicker table tr td.today:hover,
.datepicker table tr td.today:hover:hover,
.datepicker table tr td.today.disabled:hover,
.datepicker table tr td.today.disabled:hover:hover,
.datepicker table tr td.today:active,
.datepicker table tr td.today:hover:active,
.datepicker table tr td.today.disabled:active,
.datepicker table tr td.today.disabled:hover:active,
.datepicker table tr td.today.active,
.datepicker table tr td.today:hover.active,
.datepicker table tr td.today.disabled.active,
.datepicker table tr td.today.disabled:hover.active,
.datepicker table tr td.today.disabled,
.datepicker table tr td.today:hover.disabled,
.datepicker table tr td.today.disabled.disabled,
.datepicker table tr td.today.disabled:hover.disabled,
.datepicker table tr td.today[disabled],
.datepicker table tr td.today:hover[disabled],
.datepicker table tr td.today.disabled[disabled],
.datepicker table tr td.today.disabled:hover[disabled] {
background-color: #fdf59a;
}
.datepicker table tr td.today:active,
.datepicker table tr td.today:hover:active,
.datepicker table tr td.today.disabled:active,
.datepicker table tr td.today.disabled:hover:active,
.datepicker table tr td.today.active,
.datepicker table tr td.today:hover.active,
.datepicker table tr td.today.disabled.active,
.datepicker table tr td.today.disabled:hover.active {
background-color: #fbf069 \9;
}
.datepicker table tr td.range,
.datepicker table tr td.range:hover,
.datepicker table tr td.range.disabled,
.datepicker table tr td.range.disabled:hover {
background: #eeeeee;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.datepicker table tr td.range.today,
.datepicker table tr td.range.today:hover,
.datepicker table tr td.range.today.disabled,
.datepicker table tr td.range.today.disabled:hover {
background-color: #f3d17a;
background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a);
background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a));
background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a);
background-image: -o-linear-gradient(top, #f3c17a, #f3e97a);
background-image: linear-gradient(top, #f3c17a, #f3e97a);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);
border-color: #f3e97a #f3e97a #edde34;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.datepicker table tr td.range.today:hover,
.datepicker table tr td.range.today:hover:hover,
.datepicker table tr td.range.today.disabled:hover,
.datepicker table tr td.range.today.disabled:hover:hover,
.datepicker table tr td.range.today:active,
.datepicker table tr td.range.today:hover:active,
.datepicker table tr td.range.today.disabled:active,
.datepicker table tr td.range.today.disabled:hover:active,
.datepicker table tr td.range.today.active,
.datepicker table tr td.range.today:hover.active,
.datepicker table tr td.range.today.disabled.active,
.datepicker table tr td.range.today.disabled:hover.active,
.datepicker table tr td.range.today.disabled,
.datepicker table tr td.range.today:hover.disabled,
.datepicker table tr td.range.today.disabled.disabled,
.datepicker table tr td.range.today.disabled:hover.disabled,
.datepicker table tr td.range.today[disabled],
.datepicker table tr td.range.today:hover[disabled],
.datepicker table tr td.range.today.disabled[disabled],
.datepicker table tr td.range.today.disabled:hover[disabled] {
background-color: #f3e97a;
}
.datepicker table tr td.range.today:active,
.datepicker table tr td.range.today:hover:active,
.datepicker table tr td.range.today.disabled:active,
.datepicker table tr td.range.today.disabled:hover:active,
.datepicker table tr td.range.today.active,
.datepicker table tr td.range.today:hover.active,
.datepicker table tr td.range.today.disabled.active,
.datepicker table tr td.range.today.disabled:hover.active {
background-color: #efe24b \9;
}
.datepicker table tr td.selected,
.datepicker table tr td.selected:hover,
.datepicker table tr td.selected.disabled,
.datepicker table tr td.selected.disabled:hover {
background-color: #9e9e9e;
background-image: -moz-linear-gradient(top, #b3b3b3, #808080);
background-image: -ms-linear-gradient(top, #b3b3b3, #808080);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080));
background-image: -webkit-linear-gradient(top, #b3b3b3, #808080);
background-image: -o-linear-gradient(top, #b3b3b3, #808080);
background-image: linear-gradient(top, #b3b3b3, #808080);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);
border-color: #808080 #808080 #595959;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker table tr td.selected:hover,
.datepicker table tr td.selected:hover:hover,
.datepicker table tr td.selected.disabled:hover,
.datepicker table tr td.selected.disabled:hover:hover,
.datepicker table tr td.selected:active,
.datepicker table tr td.selected:hover:active,
.datepicker table tr td.selected.disabled:active,
.datepicker table tr td.selected.disabled:hover:active,
.datepicker table tr td.selected.active,
.datepicker table tr td.selected:hover.active,
.datepicker table tr td.selected.disabled.active,
.datepicker table tr td.selected.disabled:hover.active,
.datepicker table tr td.selected.disabled,
.datepicker table tr td.selected:hover.disabled,
.datepicker table tr td.selected.disabled.disabled,
.datepicker table tr td.selected.disabled:hover.disabled,
.datepicker table tr td.selected[disabled],
.datepicker table tr td.selected:hover[disabled],
.datepicker table tr td.selected.disabled[disabled],
.datepicker table tr td.selected.disabled:hover[disabled] {
background-color: #808080;
}
.datepicker table tr td.selected:active,
.datepicker table tr td.selected:hover:active,
.datepicker table tr td.selected.disabled:active,
.datepicker table tr td.selected.disabled:hover:active,
.datepicker table tr td.selected.active,
.datepicker table tr td.selected:hover.active,
.datepicker table tr td.selected.disabled.active,
.datepicker table tr td.selected.disabled:hover.active {
background-color: #666666 \9;
}
.datepicker table tr td.active,
.datepicker table tr td.active:hover,
.datepicker table tr td.active.disabled,
.datepicker table tr td.active.disabled:hover {
background-color: #006dcc;
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
background-image: linear-gradient(top, #0088cc, #0044cc);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
border-color: #0044cc #0044cc #002a80;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker table tr td.active:hover,
.datepicker table tr td.active:hover:hover,
.datepicker table tr td.active.disabled:hover,
.datepicker table tr td.active.disabled:hover:hover,
.datepicker table tr td.active:active,
.datepicker table tr td.active:hover:active,
.datepicker table tr td.active.disabled:active,
.datepicker table tr td.active.disabled:hover:active,
.datepicker table tr td.active.active,
.datepicker table tr td.active:hover.active,
.datepicker table tr td.active.disabled.active,
.datepicker table tr td.active.disabled:hover.active,
.datepicker table tr td.active.disabled,
.datepicker table tr td.active:hover.disabled,
.datepicker table tr td.active.disabled.disabled,
.datepicker table tr td.active.disabled:hover.disabled,
.datepicker table tr td.active[disabled],
.datepicker table tr td.active:hover[disabled],
.datepicker table tr td.active.disabled[disabled],
.datepicker table tr td.active.disabled:hover[disabled] {
background-color: #0044cc;
}
.datepicker table tr td.active:active,
.datepicker table tr td.active:hover:active,
.datepicker table tr td.active.disabled:active,
.datepicker table tr td.active.disabled:hover:active,
.datepicker table tr td.active.active,
.datepicker table tr td.active:hover.active,
.datepicker table tr td.active.disabled.active,
.datepicker table tr td.active.disabled:hover.active {
background-color: #003399 \9;
}
.datepicker table tr td span {
display: block;
width: 23%;
height: 54px;
line-height: 54px;
float: left;
margin: 1%;
cursor: pointer;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.datepicker table tr td span:hover {
background: #eeeeee;
}
.datepicker table tr td span.disabled,
.datepicker table tr td span.disabled:hover {
background: none;
color: #999999;
cursor: default;
}
.datepicker table tr td span.active,
.datepicker table tr td span.active:hover,
.datepicker table tr td span.active.disabled,
.datepicker table tr td span.active.disabled:hover {
background-color: #006dcc;
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
background-image: linear-gradient(top, #0088cc, #0044cc);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
border-color: #0044cc #0044cc #002a80;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker table tr td span.active:hover,
.datepicker table tr td span.active:hover:hover,
.datepicker table tr td span.active.disabled:hover,
.datepicker table tr td span.active.disabled:hover:hover,
.datepicker table tr td span.active:active,
.datepicker table tr td span.active:hover:active,
.datepicker table tr td span.active.disabled:active,
.datepicker table tr td span.active.disabled:hover:active,
.datepicker table tr td span.active.active,
.datepicker table tr td span.active:hover.active,
.datepicker table tr td span.active.disabled.active,
.datepicker table tr td span.active.disabled:hover.active,
.datepicker table tr td span.active.disabled,
.datepicker table tr td span.active:hover.disabled,
.datepicker table tr td span.active.disabled.disabled,
.datepicker table tr td span.active.disabled:hover.disabled,
.datepicker table tr td span.active[disabled],
.datepicker table tr td span.active:hover[disabled],
.datepicker table tr td span.active.disabled[disabled],
.datepicker table tr td span.active.disabled:hover[disabled] {
background-color: #0044cc;
}
.datepicker table tr td span.active:active,
.datepicker table tr td span.active:hover:active,
.datepicker table tr td span.active.disabled:active,
.datepicker table tr td span.active.disabled:hover:active,
.datepicker table tr td span.active.active,
.datepicker table tr td span.active:hover.active,
.datepicker table tr td span.active.disabled.active,
.datepicker table tr td span.active.disabled:hover.active {
background-color: #003399 \9;
}
.datepicker table tr td span.old {
color: #999999;
}
.datepicker th.datepicker-switch {
width: 145px;
}
.datepicker thead tr:first-child th,
.datepicker tfoot tr:first-child th {
cursor: pointer;
}
.datepicker thead tr:first-child th:hover,
.datepicker tfoot tr:first-child th:hover {
background: #eeeeee;
}
.datepicker .cw {
font-size: 10px;
width: 12px;
padding: 0 2px 0 5px;
vertical-align: middle;
}
.datepicker thead tr:first-child th.cw {
cursor: default;
background-color: transparent;
}
.input-append.date .add-on i,
.input-prepend.date .add-on i {
display: block;
cursor: pointer;
width: 16px;
height: 16px;
}
.input-daterange input {
text-align: center;
}
.input-daterange input:first-child {
-webkit-border-radius: 3px 0 0 3px;
-moz-border-radius: 3px 0 0 3px;
border-radius: 3px 0 0 3px;
}
.input-daterange input:last-child {
-webkit-border-radius: 0 3px 3px 0;
-moz-border-radius: 0 3px 3px 0;
border-radius: 0 3px 3px 0;
}
.input-daterange .add-on {
display: inline-block;
width: auto;
min-width: 16px;
height: 18px;
padding: 4px 5px;
font-weight: normal;
line-height: 18px;
text-align: center;
text-shadow: 0 1px 0 #ffffff;
vertical-align: middle;
background-color: #eeeeee;
border: 1px solid #ccc;
margin-left: -5px;
margin-right: -5px;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
/**
* Bulgarian translation for bootstrap-datepicker
* Apostol Apostolov <apostol.s.apostolov@gmail.com>
*/
;(function($){
$.fn.datepicker.dates['bg'] = {
days: ["Неделя", "Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота", "Неделя"],
daysShort: ["Нед", "Пон", "Вто", "Сря", "Чет", "Пет", "Съб", "Нед"],
daysMin: ["Н", "П", "В", "С", "Ч", "П", "С", "Н"],
months: ["Януари", "Февруари", "Март", "Април", "Май", "Юни", "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември"],
monthsShort: ["Ян", "Фев", "Мар", "Апр", "Май", "Юни", "Юли", "Авг", "Сеп", "Окт", "Ное", "Дек"],
today: "днес"
};
}(jQuery));

View File

@@ -0,0 +1,14 @@
/**
* Catalan translation for bootstrap-datepicker
* J. Garcia <jogaco.en@gmail.com>
*/
;(function($){
$.fn.datepicker.dates['ca'] = {
days: ["Diumenge", "Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte", "Diumenge"],
daysShort: ["Diu", "Dil", "Dmt", "Dmc", "Dij", "Div", "Dis", "Diu"],
daysMin: ["dg", "dl", "dt", "dc", "dj", "dv", "ds", "dg"],
months: ["Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Agost", "Setembre", "Octubre", "Novembre", "Desembre"],
monthsShort: ["Gen", "Feb", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Oct", "Nov", "Des"],
today: "Avui"
};
}(jQuery));

View File

@@ -0,0 +1,15 @@
/**
* Czech translation for bootstrap-datepicker
* Matěj Koubík <matej@koubik.name>
* Fixes by Michal Remiš <michal.remis@gmail.com>
*/
;(function($){
$.fn.datepicker.dates['cs'] = {
days: ["Neděle", "Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota", "Neděle"],
daysShort: ["Ned", "Pon", "Úte", "Stř", "Čtv", "Pát", "Sob", "Ned"],
daysMin: ["Ne", "Po", "Út", "St", "Čt", "Pá", "So", "Ne"],
months: ["Leden", "Únor", "Březen", "Duben", "Květen", "Červen", "Červenec", "Srpen", "Září", "Říjen", "Listopad", "Prosinec"],
monthsShort: ["Led", "Úno", "Bře", "Dub", "Kvě", "Čer", "Čnc", "Srp", "Zář", "Říj", "Lis", "Pro"],
today: "Dnes"
};
}(jQuery));

View File

@@ -0,0 +1,14 @@
/**
* Danish translation for bootstrap-datepicker
* Christian Pedersen <http://github.com/chripede>
*/
;(function($){
$.fn.datepicker.dates['da'] = {
days: ["Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag"],
daysShort: ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"],
daysMin: ["Sø", "Ma", "Ti", "On", "To", "Fr", "Lø", "Sø"],
months: ["Januar", "Februar", "Marts", "April", "Maj", "Juni", "Juli", "August", "September", "Oktober", "November", "December"],
monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"],
today: "I Dag"
};
}(jQuery));

View File

@@ -0,0 +1,16 @@
/**
* German translation for bootstrap-datepicker
* Sam Zurcher <sam@orelias.ch>
*/
;(function($){
$.fn.datepicker.dates['de'] = {
days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"],
daysShort: ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam", "Son"],
daysMin: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"],
months: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
monthsShort: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"],
today: "Heute",
weekStart: 1,
format: "dd.mm.yyyy"
};
}(jQuery));

View File

@@ -0,0 +1,13 @@
/**
* Greek translation for bootstrap-datepicker
*/
;(function($){
$.fn.datepicker.dates['el'] = {
days: ["Κυριακή", "Δευτέρα", "Τρίτη", "Τετάρτη", "Πέμπτη", "Παρασκευή", "Σάββατο", "Κυριακή"],
daysShort: ["Κυρ", "Δευ", "Τρι", "Τετ", "Πεμ", "Παρ", "Σαβ", "Κυρ"],
daysMin: ["Κυ", "Δε", "Τρ", "Τε", "Πε", "Πα", "Σα", "Κυ"],
months: ["Ιανουάριος", "Φεβρουάριος", "Μάρτιος", "Απρίλιος", "Μάιος", "Ιούνιος", "Ιούλιος", "Αύγουστος", "Σεπτέμβριος", "Οκτώβριος", "Νοέμβριος", "Δεκέμβριος"],
monthsShort: ["Ιαν", "Φεβ", "Μαρ", "Απρ", "Μάι", "Ιουν", "Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ"],
today: "Σήμερα"
};
}(jQuery));

View File

@@ -0,0 +1,14 @@
/**
* Spanish translation for bootstrap-datepicker
* Bruno Bonamin <bruno.bonamin@gmail.com>
*/
;(function($){
$.fn.datepicker.dates['es'] = {
days: ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"],
daysShort: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", "Dom"],
daysMin: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "Sa", "Do"],
months: ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
monthsShort: ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"],
today: "Hoy"
};
}(jQuery));

View File

@@ -0,0 +1,14 @@
/**
* Finnish translation for bootstrap-datepicker
* Jaakko Salonen <https://github.com/jsalonen>
*/
;(function($){
$.fn.datepicker.dates['fi'] = {
days: ["sunnuntai", "maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai", "sunnuntai"],
daysShort: ["sun", "maa", "tii", "kes", "tor", "per", "lau", "sun"],
daysMin: ["su", "ma", "ti", "ke", "to", "pe", "la", "su"],
months: ["tammikuu", "helmikuu", "maaliskuu", "huhtikuu", "toukokuu", "kesäkuu", "heinäkuu", "elokuu", "syyskuu", "lokakuu", "marraskuu", "joulukuu"],
monthsShort: ["tam", "hel", "maa", "huh", "tou", "kes", "hei", "elo", "syy", "lok", "mar", "jou"],
today: "tänään"
};
}(jQuery));

View File

@@ -0,0 +1,16 @@
/**
* French translation for bootstrap-datepicker
* Nico Mollet <nico.mollet@gmail.com>
*/
;(function($){
$.fn.datepicker.dates['fr'] = {
days: ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"],
daysShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"],
daysMin: ["D", "L", "Ma", "Me", "J", "V", "S", "D"],
months: ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
monthsShort: ["Jan", "Fev", "Mar", "Avr", "Mai", "Jui", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"],
today: "Aujourd'hui",
weekStart: 1,
format: "dd/mm/yyyy"
};
}(jQuery));

View File

@@ -0,0 +1,15 @@
/**
* Hebrew translation for bootstrap-datepicker
* Sagie Maoz <sagie@maoz.info>
*/
;(function($){
$.fn.datepicker.dates['he'] = {
days: ["ראשון", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת", "ראשון"],
daysShort: ["א", "ב", "ג", "ד", "ה", "ו", "ש", "א"],
daysMin: ["א", "ב", "ג", "ד", "ה", "ו", "ש", "א"],
months: ["ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר"],
monthsShort: ["ינו", "פבר", "מרץ", "אפר", "מאי", "יונ", "יול", "אוג", "ספט", "אוק", "נוב", "דצמ"],
today: "היום",
rtl: true
};
}(jQuery));

View File

@@ -0,0 +1,13 @@
/**
* Croatian localisation
*/
;(function($){
$.fn.datepicker.dates['hr'] = {
days: ["Nedjelja", "Ponedjelja", "Utorak", "Srijeda", "Četrtak", "Petak", "Subota", "Nedjelja"],
daysShort: ["Ned", "Pon", "Uto", "Srr", "Čet", "Pet", "Sub", "Ned"],
daysMin: ["Ne", "Po", "Ut", "Sr", "Če", "Pe", "Su", "Ne"],
months: ["Siječanj", "Veljača", "Ožujak", "Travanj", "Svibanj", "Lipanj", "Srpanj", "Kolovoz", "Rujan", "Listopad", "Studeni", "Prosinac"],
monthsShort: ["Sije", "Velj", "Ožu", "Tra", "Svi", "Lip", "Jul", "Kol", "Ruj", "Lis", "Stu", "Pro"],
today: "Danas"
};
}(jQuery));

View File

@@ -0,0 +1,16 @@
/**
* Hungarian translation for bootstrap-datepicker
* Sotus László <lacisan@gmail.com>
*/
;(function($){
$.fn.datepicker.dates['hu'] = {
days: ["Vasárnap", "Hétfő", "Kedd", "Szerda", "Csütörtök", "Péntek", "Szombat", "Vasárnap"],
daysShort: ["Vas", "Hét", "Ked", "Sze", "Csü", "Pén", "Szo", "Vas"],
daysMin: ["Va", "Hé", "Ke", "Sz", "Cs", "Pé", "Sz", "Va"],
months: ["Január", "Február", "Március", "Április", "Május", "Június", "Július", "Augusztus", "Szeptember", "Október", "November", "December"],
monthsShort: ["Jan", "Feb", "Már", "Ápr", "Máj", "Jún", "Júl", "Aug", "Sze", "Okt", "Nov", "Dec"],
today: "Ma",
weekStart: 1,
format: "yyyy.mm.dd"
};
}(jQuery));

View File

@@ -0,0 +1,13 @@
/**
* Bahasa translation for bootstrap-datepicker
* Azwar Akbar <azwar.akbar@gmail.com>
*/
;(function($){
$.fn.datepicker.dates['id'] = {
days: ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"],
daysShort: ["Mgu", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab", "Mgu"],
daysMin: ["Mg", "Sn", "Sl", "Ra", "Ka", "Ju", "Sa", "Mg"],
months: ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"],
monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Ags", "Sep", "Okt", "Nov", "Des"]
};
}(jQuery));

View File

@@ -0,0 +1,14 @@
/**
* Icelandic translation for bootstrap-datepicker
* Hinrik Örn Sigurðsson <hinrik.sig@gmail.com>
*/
;(function($){
$.fn.datepicker.dates['is'] = {
days: ["Sunnudagur", "Mánudagur", "Þriðjudagur", "Miðvikudagur", "Fimmtudagur", "Föstudagur", "Laugardagur", "Sunnudagur"],
daysShort: ["Sun", "Mán", "Þri", "Mið", "Fim", "Fös", "Lau", "Sun"],
daysMin: ["Su", "Má", "Þr", "Mi", "Fi", "Fö", "La", "Su"],
months: ["Janúar", "Febrúar", "Mars", "Apríl", "Maí", "Júní", "Júlí", "Ágúst", "September", "Október", "Nóvember", "Desember"],
monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maí", "Jún", "Júl", "Ágú", "Sep", "Okt", "Nóv", "Des"],
today: "Í Dag"
};
}(jQuery));

View File

@@ -0,0 +1,16 @@
/**
* Italian translation for bootstrap-datepicker
* Enrico Rubboli <rubboli@gmail.com>
*/
;(function($){
$.fn.datepicker.dates['it'] = {
days: ["Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato", "Domenica"],
daysShort: ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab", "Dom"],
daysMin: ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"],
months: ["Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"],
monthsShort: ["Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"],
today: "Oggi",
weekStart: 1,
format: "dd/mm/yyyy"
};
}(jQuery));

View File

@@ -0,0 +1,15 @@
/**
* Japanese translation for bootstrap-datepicker
* Norio Suzuki <https://github.com/suzuki/>
*/
;(function($){
$.fn.datepicker.dates['ja'] = {
days: ["日曜", "月曜", "火曜", "水曜", "木曜", "金曜", "土曜", "日曜"],
daysShort: ["日", "月", "火", "水", "木", "金", "土", "日"],
daysMin: ["日", "月", "火", "水", "木", "金", "土", "日"],
months: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
monthsShort: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
today: "今日",
format: "yyyy/mm/dd"
};
}(jQuery));

View File

@@ -0,0 +1,13 @@
/**
* Korean translation for bootstrap-datepicker
* Gu Youn <http://github.com/guyoun>
*/
;(function($){
$.fn.datepicker.dates['kr'] = {
days: ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일", "일요일"],
daysShort: ["일", "월", "화", "수", "목", "금", "토", "일"],
daysMin: ["일", "월", "화", "수", "목", "금", "토", "일"],
months: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
monthsShort: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"]
};
}(jQuery));

View File

@@ -0,0 +1,16 @@
/**
* Lithuanian translation for bootstrap-datepicker
* Šarūnas Gliebus <ssharunas@yahoo.co.uk>
*/
;(function($){
$.fn.datepicker.dates['lt'] = {
days: ["Sekmadienis", "Pirmadienis", "Antradienis", "Trečiadienis", "Ketvirtadienis", "Penktadienis", "Šeštadienis", "Sekmadienis"],
daysShort: ["S", "Pr", "A", "T", "K", "Pn", "Š", "S"],
daysMin: ["Sk", "Pr", "An", "Tr", "Ke", "Pn", "Št", "Sk"],
months: ["Sausis", "Vasaris", "Kovas", "Balandis", "Gegužė", "Birželis", "Liepa", "Rugpjūtis", "Rugsėjis", "Spalis", "Lapkritis", "Gruodis"],
monthsShort: ["Sau", "Vas", "Kov", "Bal", "Geg", "Bir", "Lie", "Rugp", "Rugs", "Spa", "Lap", "Gru"],
today: "Šiandien",
weekStart: 1
};
}(jQuery));

View File

@@ -0,0 +1,16 @@
/**
* Latvian translation for bootstrap-datepicker
* Artis Avotins <artis@apit.lv>
*/
;(function($){
$.fn.datepicker.dates['lv'] = {
days: ["Svētdiena", "Pirmdiena", "Otrdiena", "Trešdiena", "Ceturtdiena", "Piektdiena", "Sestdiena", "Svētdiena"],
daysShort: ["Sv", "P", "O", "T", "C", "Pk", "S", "Sv"],
daysMin: ["Sv", "Pr", "Ot", "Tr", "Ce", "Pk", "St", "Sv"],
months: ["Janvāris", "Februāris", "Marts", "Aprīlis", "Maijs", "Jūnijs", "Jūlijs", "Augusts", "Septembris", "Oktobris", "Novembris", "Decembris"],
monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mai", "Jūn", "Jūl", "Aug", "Sep", "Okt", "Nov", "Dec."],
today: "Šodien",
weekStart: 1
};
}(jQuery));

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