Compare commits

...

197 Commits

Author SHA1 Message Date
Joe Cheng
fb4da933d4 Update version number in NEWS 2013-10-29 12:31:58 -07:00
Yihui Xie
7483900db2 fixes #288: moving shinyCallingHandlers() to a lower-level so that the shiny.error handler can be applied to observe() and isolate() as well 2013-10-28 23:17:02 -05:00
Yihui Xie
ef9b9bdd6d prepare the v0.8 release in the RC branch 2013-10-24 23:48:48 -05:00
Yihui Xie
1937aa43ba Ah, brain poisoned by R's return() syntax; check if data is empty, then check data.colnames to make sure column names are passed in 2013-10-23 21:26:47 -05:00
Yihui Xie
293ea66784 fixes #286: if the data passed to renderDataTable() does not have dim==2, return an empty object 2013-10-23 21:14:18 -05:00
Yihui Xie
e98d8f4ced Merge pull request #284 from jcheng5/shiny-server-creds
Make Shiny Server credentials available on session object
2013-10-22 11:35:20 -07:00
Joe Cheng
418d2afb2a Make Shiny Server credentials available on session object 2013-10-22 10:42:29 -07:00
Yihui Xie
a4c1a6187f roxygenize and sync doc 2013-10-22 12:33:07 -05:00
Yihui Xie
123ca34040 tweak the roxygen doc for installExprFunction() (otherwise the second paragraph is treated as \description{}) 2013-10-22 12:33:07 -05:00
Joe Cheng
6b3224116c Merge pull request #279 from yihui/datatables
DataTables
2013-10-22 09:43:13 -07:00
Yihui Xie
635e0c9788 news for shiny v0.8 2013-10-22 10:40:29 -05:00
Yihui Xie
dd33a0e0ec the error object may not be interesting at all; just call the handler without arguments
this makes it easier to set options(shiny.error = browser/recover/traceback/...), otherwise will have to do options(shiny.error = function(e) traceback()), which seems awkward
2013-10-22 10:33:01 -05:00
Yihui Xie
191deeaba6 a hack to remove the scrollbars in tab panels 2013-10-22 02:07:06 -05:00
Yihui Xie
245072f7a2 make sure colnames is an array, even when there is only one column in the data 2013-10-22 02:07:06 -05:00
Yihui Xie
6b858512b6 must empty the data table first 2013-10-22 02:07:06 -05:00
Yihui Xie
b857a01c30 save $(el) in $el 2013-10-22 02:07:06 -05:00
Yihui Xie
94c9a3e05b should not have assigned installExprFunction() to func 2013-10-22 02:07:06 -05:00
Yihui Xie
8928d2c488 use the new installExprFunction() instead of exprToFunction() 2013-10-22 02:07:06 -05:00
Yihui Xie
25bd5654aa display 25 rows by default, again per suggestion of JJ 2013-10-22 02:07:06 -05:00
Yihui Xie
83d5b96adf no CSS classes for sorted columns, per suggestion of JJ 2013-10-22 02:07:06 -05:00
Yihui Xie
7eb90c5718 roxygenize 2013-10-22 02:07:06 -05:00
Yihui Xie
4b1af75724 debouncing is done single-handedly, thanks to Joe's smart debounce() function 2013-10-22 02:07:06 -05:00
Yihui Xie
8d07ab6527 roxygenize 2013-10-22 02:07:06 -05:00
Yihui Xie
ce4ea7e7a9 allow users to pass initialization options to datatables 2013-10-22 02:07:06 -05:00
Yihui Xie
50ab5e7517 BSD license for DataTables 2013-10-22 02:07:06 -05:00
Yihui Xie
431c1d7f66 css for the processing indicator 2013-10-22 02:07:06 -05:00
Yihui Xie
a55090dc2f the searching should use intersection instead of union 2013-10-22 02:07:06 -05:00
Yihui Xie
d76cdb73b0 remove the ColumnFilter plugin; it is too heavy, and I just added the <input> by myself and implemented searching by individual columns 2013-10-22 02:07:05 -05:00
Yihui Xie
2594664330 use the nicer bootstrap style 2013-10-22 02:07:05 -05:00
Yihui Xie
f9ed075db6 write a datatable output binding; the column names and action url are passed from renderDataTable() to the output binding 2013-10-22 02:07:05 -05:00
Yihui Xie
099ced4f94 implement searching by columns 2013-10-22 02:07:05 -05:00
Yihui Xie
13d2513930 index from 0, sigh 2013-10-22 02:07:05 -05:00
Yihui Xie
2211b1c65e now we can sort multiple columns: press Shift and click the column headers 2013-10-22 02:07:05 -05:00
Yihui Xie
1fd37ca2b2 implement sorting; the very basic features are there now, but this still needs a lot of improvement in terms of details 2013-10-22 02:07:05 -05:00
Yihui Xie
7070e3748d disable default sorting 2013-10-22 02:07:05 -05:00
Yihui Xie
dfaef908c2 make sure the data has two dimensions 2013-10-22 02:07:05 -05:00
Yihui Xie
67540c763b a simple implementation of global searching 2013-10-22 02:07:05 -05:00
Yihui Xie
14269bd4d9 document renderDataTable() and dataTableOutput() 2013-10-22 02:07:05 -05:00
Yihui Xie
131663032c the ... argument is not really used 2013-10-22 02:07:05 -05:00
Yihui Xie
8ac71165e9 add dataTableOutput() and renderDataTable() for the DataTables library
not yet done, but at least paging is working now
2013-10-22 02:07:05 -05:00
Yihui Xie
346758d3f0 white spaces 2013-10-21 21:40:28 -05:00
Yihui Xie
aef8837b5d add .csv to fileInput() to make it consistent with the tutorial (#280) 2013-10-21 21:25:42 -05:00
Yihui Xie
dc0832adba should pass a session object to invalidateLater() 2013-10-21 21:20:41 -05:00
Joe Cheng
0ad3ff655e Merge remote-tracking branch 'origin/pr/276'
Conflicts:
	R/shiny.R
2013-10-21 10:11:05 -07:00
Yihui Xie
ef45a62cc9 Merge pull request #282 from rstudio/feature/httpuv-version
bump the required version of httpuv to v1.2
2013-10-20 09:56:17 -07:00
JJ Allaire
b79abbdea9 bump the required version of httpuv to v1.2
this is to pull the changes to httpuv that enable shiny applications to run inside the rstudio viewer pane
2013-10-20 06:34:52 -04:00
Yihui Xie
930e2d1d9d closes #272: the argument ws_env was not used anywhere 2013-10-18 22:01:17 -05:00
Joe Cheng
ec2992cd2d Merge pull request #264 from rstudio/feature/debug-hooks
Export installExprFunction and add supporting documentation
2013-10-17 09:44:03 -07:00
Joe Cheng
619208565b Merge pull request #273 from yihui/feature/callinghandlers
use withCallingHandlers() so that users can do error handling like recover()
2013-10-17 09:39:01 -07:00
Joe Cheng
dcd689d2ea Don't clear timers when runApp returns
The lifetimes of reactive expressions and observers can span multiple
runApp calls, so timers should as well.
2013-10-16 15:34:25 -07:00
Yihui Xie
e94de15f83 well, we still have to use do.call($) on .self; closes #274
Winston reported the issue at https://stat.ethz.ch/pipermail/r-devel/2013-October/067744.html

partially reverted 86d61e0b44
2013-10-16 15:42:36 -05:00
Yihui Xie
6af7de51a5 another attempt to close #249, using withCallingHandlers() instead of modifying tryCatch()
also closes #217 if everyone agrees with this approach
2013-10-16 00:57:54 -05:00
Joe Cheng
f4a4af0fa4 Merge pull request #265 from yihui/bug/offset
a more reliable solution for e.offsetX/Y in Firefox
2013-10-14 11:02:20 -07:00
Joe Cheng
6934838974 Merge pull request #268 from rstudio/feature/parent-notify-disconnected
send disconnected message to parent frame when running on localhost
2013-10-11 07:51:34 -07:00
JJ Allaire
1aadd25cb5 notify iframe parent of disconnect when on the same domain 2013-10-10 06:20:26 -04:00
Winston Chang
0caf944668 Fix typo 2013-10-09 16:04:14 -05:00
Jonathan McPherson
6452f62b88 use a check hint (globalVariables()) in favor of modifying code in renderImage 2013-10-08 23:24:27 -07:00
Yihui Xie
e061dfd808 roxygenize 2013-10-08 23:42:19 -05:00
Yihui Xie
4da53ef219 a better solution for e.offsetX/Y in Firefox based on http://stackoverflow.com/q/12704686/559676
which fixes the bug reported by Greg D: https://groups.google.com/forum/#!topic/shiny-discuss/E6oYvyvx0oU
2013-10-08 23:34:34 -05:00
Jonathan McPherson
347e44f04d look up function by name (for R CMD check --as-cran) 2013-10-08 15:29:35 -07:00
Joe Cheng
8997fa7242 Merge pull request #252 from jcheng5/random-ports2
Try up to 20 random ports if necessary
2013-10-08 13:58:17 -07:00
JJ Allaire
19ba6efb82 allow a custom function for the launch.browser parameter 2013-10-08 13:56:14 -07:00
Jonathan McPherson
d10cbc9984 export and add docs for installExprFunction 2013-10-08 10:36:31 -07:00
Jonathan McPherson
6c7d9ded00 simplify syntax for creating new debuggable expressions 2013-10-07 11:11:59 -07:00
Yihui Xie
6d04e89d7d Merge pull request #262 from yihui/r-check
synchronize doc
2013-10-06 15:50:01 -07:00
Yihui Xie
2beb24147d roxygenize 2013-10-06 17:47:09 -05:00
Yihui Xie
16c5f4e377 no need to expose the documentation to users; expose registerShinyDebugHook to R so that R CMD check does not complain
per discussion in #258, and closes #259
2013-10-06 17:46:45 -05:00
Joe Cheng
03a6f1753c Merge pull request #258 from rstudio/feature/debug-hooks
Add debug hook (when present) for functions generated by exprToFunction
2013-10-02 20:06:46 -07:00
Jonathan McPherson
9fb61d8446 tag Shiny server function for special debug treatment if needed 2013-10-02 12:27:44 -07:00
Jonathan McPherson
bc3322d3c9 allow debugging Shiny server function itself 2013-10-02 10:47:38 -07:00
Jonathan McPherson
06c7bf7514 invoke debug hook function if present after instantiating app functions 2013-10-02 08:53:45 -07:00
Yihui Xie
4c89a000e4 Merge pull request #257 from yihui/trycatch
rewrite some instances of do.call()
2013-10-01 23:33:44 -07:00
Yihui Xie
86d61e0b44 we do not really need do.call() in these cases 2013-10-02 01:30:31 -05:00
Joe Cheng
6407390d72 Try up to 20 random ports if necessary 2013-09-30 16:13:46 -07:00
Yihui Xie
648120cabf Merge pull request #243 from jcheng5/master
Conditional panel expressions were broken for typed inputs
2013-09-28 21:30:42 -07:00
Joe Cheng
ce5b3f290a Merge pull request #245 from rstudio/feature/random-port
choose a random port for runApp
2013-09-27 12:36:49 -07:00
JJ Allaire
5308ca1806 use .globals rather than .runContext 2013-09-27 15:36:02 -04:00
JJ Allaire
6df6d408d2 choose a random port for runApp and continue to use it for the duration of the session unless explicitly overridden 2013-09-27 07:59:44 -04:00
Joe Cheng
b60d6ccdd8 Conditional panel expressions were broken for typed inputs 2013-09-26 15:31:14 -07:00
Joe Cheng
de01c9685e Merge pull request #235 from crtahlin/patch-2
Typo fix.
2013-09-26 10:37:56 -07:00
Joe Cheng
31d2ecc9fd Merge pull request #234 from crtahlin/patch-1
Typo fix.
2013-09-26 10:37:31 -07:00
Yihui Xie
2f8502aec6 save coordmap in $el.data() so it can be dynamically retrieved
6161eaa was an inappropriate fix

(jcheng: cherry-picked from @yihui/master)
2013-09-26 10:33:37 -07:00
Joe Cheng
d377b04dad Make websocket path relative to Shiny page path
This is necessary for proxy situations that don't override the
Shiny.createSocket function (so, not including Shiny Server,
but more like Nginx, HAProxy, and a future version of RStudio
Server).

Without the path being preserved, it's impossible for these
proxies to know that the URL should be forwarded to the host
and port that belongs to Shiny.
2013-09-19 22:53:11 -07:00
crtahlin
40cc78ae1e Typo fix. 2013-09-14 23:12:45 +02:00
crtahlin
268f1e8472 Typo fix. 2013-09-14 22:54:20 +02:00
Winston Chang
004b7c782d Add mailing list information to README 2013-09-11 11:09:18 -05:00
Winston Chang
33b293f0aa Merge pull request #226 from crtahlin/patch-2
Fixed typo.
2013-09-05 17:53:51 -07:00
crtahlin
ad584a98ad Fixed typo. 2013-09-05 17:25:27 +02:00
Jeff Allen
e2509eddb2 Merge pull request #225 from crtahlin/patch-1
Fixed typo.
2013-09-05 08:09:03 -07:00
crtahlin
b9f72d0e78 Fixed typo. 2013-09-05 16:35:04 +02:00
Joe Cheng
c839bb2db3 Merge pull request #224 from yihui/patch-1
websockets has been changed to httpuv
2013-09-04 10:38:04 -07:00
Yihui Xie
30161369a8 websockets has been changed to httpuv 2013-09-03 17:24:05 -05:00
Winston Chang
c65aa9732e Bump version to 0.7.0.99 for development 2013-08-29 11:44:30 -05:00
Joe Cheng
bc72b8fd1c Merge pull request #214 from hadley/doc-tweaks
Doc tweaks
2013-08-27 16:50:00 -07:00
Winston Chang
f089531bd1 Put man-roxygen in .Rbuildignore 2013-08-27 10:08:03 -05:00
Winston Chang
8d8ea53804 Fixes to imports for R-devel 2013-08-26 14:19:50 -05:00
Winston Chang
89e405e927 Bump version to 0.7.0 2013-08-26 12:03:02 -05:00
Joe Cheng
ca984a6630 Implement shiny.sharedSecret option 2013-08-25 22:20:38 -07:00
Joe Cheng
fa39a55eca Wow, IE10 is *really* picky about websocket URLs 2013-08-23 15:42:19 -07:00
Joe Cheng
c3a1ba2f2d Make websocket URL work with IE10
See https://github.com/einaros/ws/issues/131#issuecomment-15715373
2013-08-23 15:38:12 -07:00
Joe Cheng
86e291f250 Add docs for showReactLog. Un-export writeReactLog. 2013-08-23 13:11:04 -07:00
Jeff Allen
dd1d4439a9 Merge pull request #218 from wch/fix-log
Cleanups to reactive logging code for R CMD check
2013-08-23 12:16:23 -07:00
Joe Cheng
cbfde18f8c More updates to NEWS 2013-08-23 12:03:11 -07:00
Winston Chang
e2c2e23d2a Cleanups to reactive logging code for R CMD check
* Fix 'env' partial argument match for get().
* Fix unbound variable access of .shiny__stdout.
* Restructure if statement so test is only done once.
2013-08-23 12:16:18 -05:00
Winston Chang
40cc5d5242 Doc fixes for R CMD check 2013-08-23 12:07:34 -05:00
Winston Chang
9765194ace Bump httpuv version dependency to 1.1.0 2013-08-23 10:16:47 -05:00
Winston Chang
628465e6b5 Update NEWS 2013-08-23 10:16:12 -05:00
Winston Chang
58706df120 Update sliderInput docs 2013-08-23 10:16:05 -05:00
Joe Cheng
b19225c747 Don't send websocket subprotocol
Some time recently, Google Chrome started actually caring what the
client sends for this and what the server replies with. httpuv
doesn't currently have any logic for subprotocol selection, so
it always replies with no Sec-WebSocket-Protocol header, which
now Google Chrome reacts to by closing the websocket connection.

This problem goes away if we just don't send a subprotocol at all.
2013-08-23 02:15:54 -07:00
Joe Cheng
c304889e61 Merge pull request #208 from trestletech/master
Add reactive timing logging, if the necessary variables are available.
2013-08-22 13:55:11 -07:00
trestletech
05a9204678 Added timestamp to reactive log. 2013-08-22 11:20:26 -05:00
hadley
ed8537bb0b Redue duplication in docs betwee date/dateRange 2013-08-14 13:37:14 -05:00
hadley
6a9ae10fcf Add cross-references between input types 2013-08-14 13:33:44 -05:00
hadley
05358904bf Re-document 2013-08-14 13:31:16 -05:00
hadley wickham
1a6901c3e3 Document range slider 2013-08-14 13:25:42 -05:00
Winston Chang
7aaba8244b Add is.reactivevalues function 2013-08-05 14:02:50 -05:00
Winston Chang
8c45dcde88 Merge pull request #205 from hadley/master
Class output of reactive.
2013-08-05 12:02:14 -07:00
Winston Chang
6c155b04b2 Merge pull request #197 from wch/fix-style
Add compatibility wrapper for getComputedStyle in IE8
2013-07-27 17:43:01 -07:00
Winston Chang
cd8ad9a2ec Add compatibility wrapper for getComputedStyle in IE8 2013-07-27 19:41:56 -05:00
hadley
a5db7d0246 Class output of reactive.
Also add print method and test
2013-07-27 11:06:35 -05:00
trestletech
b84b467b96 Added logging of start/stop flushing of reactives using a param provided via HTTP headers. 2013-07-24 17:40:24 -04:00
trestletech
0812aaac88 Merge remote-tracking branch 'rstudio/master' 2013-07-24 08:59:34 -04:00
Joe Cheng
194d2f911e Changes to public-facing session object
- Change from list to environment. This enables the next feature:
- Make $request a promise. websocket$request breaks on some (earlier?) versions
  of httpuv. I'm not sure why this is but in the few hours since I submitted
  the websocket$request change we've had a number of complaints. This way the
  error only occurs if the app actually asks for session$request.
- Make the private session object accessible via `.impl`. Obviously any data or
  methods on the private session object are unsupported but they are there if
  you are desperate and don't mind possible future breakage.
2013-07-23 15:39:27 -07:00
Joe Cheng
e360b36b8a Make WS request available on session object 2013-07-22 15:15:27 -07:00
trestletech
b6f66dd287 Moved print out so we can put it where it belongs. 2013-07-16 08:01:22 -07:00
trestletech
0a4bb48cd3 Added output of process ID on app start. 2013-07-15 14:26:25 -07:00
Joe Cheng
15d62d4a91 Add instructions for installing from GitHub 2013-07-12 01:32:49 +02:00
Joe Cheng
5b13c44ef9 Fix isolate return value bug (issue #200) 2013-07-12 01:30:07 +02:00
Joe Cheng
0a4250f3b4 Merge pull request #193 from jcheng5/reactlog
React log
2013-07-09 00:58:53 -07:00
Joe Cheng
f79223ed58 Merge remote-tracking branch 'origin/master' into reactlog 2013-07-07 01:20:59 -07:00
Joe Cheng
2d28218a2a Allow Cmd+F3 to launch reactlog 2013-07-07 01:20:40 -07:00
Joe Cheng
35974f2ee1 Firefox scrubbing fix 2013-07-06 23:13:25 -07:00
Joe Cheng
1f73323fb9 reactlog: Support arbitrary temporal movement 2013-07-06 23:06:38 -07:00
Joe Cheng
a3d0736eec Use more obscure keyboard shortcut for reactlog 2013-07-06 18:27:10 -07:00
Joe Cheng
4bdd486c00 reactlog: Firefox compatibility; visual tweaks 2013-07-06 18:22:40 -07:00
Joe Cheng
c3895c9bd7 Configurable hover delay type (debounce/throttle) 2013-07-05 17:29:57 -07:00
Joe Cheng
e9ddd89b32 Simpler math 2013-07-05 15:08:42 -07:00
Joe Cheng
88a8f2d609 Fix locator Retina compatibility. Again.
Was working locally on Macs but not on spark.rstudio.com accessed via rMBP.
2013-07-05 13:52:56 -07:00
Joe Cheng
a5dc5c89e8 Firefox locator compat 2013-07-05 12:30:14 -07:00
Joe Cheng
3a15a35137 Merge pull request #192 from jcheng5/flush-callbacks
Add session$onFlush and session$onFlushed
2013-07-05 02:10:02 -07:00
Joe Cheng
b644640804 Add session$onFlush and session$onFlushed 2013-07-05 02:07:51 -07:00
Joe Cheng
aaa4f66671 Merge pull request #183 from jcheng5/plot-mouse-events
Click and hover on static plots. Also, fix retina compatibility and make hover delay configurable
2013-07-04 23:43:54 -07:00
Joe Cheng
07e021199e Use crosshair cursor when plot supports hover/click 2013-07-04 23:42:44 -07:00
Joe Cheng
6b2ca7dc80 Merge pull request #182 from jcheng5/reactive-poll
Implement reactivePoll and reactiveFileReader
2013-07-04 23:31:16 -07:00
Joe Cheng
091d62803e Merge pull request #191 from trestletech/master
Restrict the number of observations to a valid, positive number.
2013-07-04 12:15:57 -07:00
trestletech
547999bae0 Restrict the number of observations to a valid, positive number. 2013-07-03 23:06:42 -05: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
d403ec7399 Make hover delay configurable 2013-06-24 16:39:04 -05:00
Winston Chang
6ac77835df Fix Retina compatibility (revert b113119) 2013-06-24 16:31:43 -05:00
Joe Cheng
b113119a9a Retina display compatibility 2013-06-21 21:38:23 -07:00
Joe Cheng
b713057614 Implement click and hover events on static plots
plotOutput now takes clickId and hoverId params that tell Shiny
where to send click and hover events for that plot. The server.R
file can listen on input$<clickId> and/or input$<hoverId>. In
both cases, the resulting value will have numeric x and y elements
that indicate the mouse position in user coordinates. In the case
of hover events, it's also possible to have a NULL value which
means the mouse is not currently hovering over the plot.
2013-06-21 16:58:43 -07: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
Joe Cheng
d897df6a30 Implement reactivePoll and reactiveFileReader 2013-06-19 09:16:04 -07: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
73 changed files with 3597 additions and 387 deletions

View File

@@ -9,3 +9,4 @@
^\.gitignore$
^res$
^tools$
^man-roxygen$

View File

@@ -1,8 +1,8 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 0.6.0
Date: 2013-01-23
Version: 0.8.0
Date: 2013-10-26
Author: RStudio, Inc.
Maintainer: Winston Chang <winston@rstudio.com>
Description: Shiny makes it incredibly easy to build interactive web
@@ -16,14 +16,14 @@ Imports:
stats,
tools,
utils,
datasets,
methods,
httpuv (>= 1.0.6.2),
httpuv (>= 1.2.0),
caTools,
RJSONIO,
xtable,
digest
Suggests:
datasets,
markdown,
Cairo,
testthat
@@ -37,6 +37,7 @@ Collate:
'timer.R'
'tags.R'
'cache.R'
'graph.R'
'react.R'
'reactives.R'
'fileupload.R'

View File

@@ -17,6 +17,7 @@ S3method(as.list,reactivevalues)
S3method(format,shiny.tag)
S3method(format,shiny.tag.list)
S3method(names,reactivevalues)
S3method(print,reactive)
S3method(print,shiny.tag)
S3method(print,shiny.tag.list)
export(HTML)
@@ -31,6 +32,7 @@ export(checkboxGroupInput)
export(checkboxInput)
export(code)
export(conditionalPanel)
export(dataTableOutput)
export(dateInput)
export(dateRangeInput)
export(div)
@@ -51,10 +53,15 @@ export(helpText)
export(htmlOutput)
export(imageOutput)
export(img)
export(includeCSS)
export(includeHTML)
export(includeMarkdown)
export(includeScript)
export(includeText)
export(installExprFunction)
export(invalidateLater)
export(is.reactive)
export(is.reactivevalues)
export(isolate)
export(mainPanel)
export(numericInput)
@@ -68,7 +75,9 @@ export(plotPNG)
export(pre)
export(radioButtons)
export(reactive)
export(reactiveFileReader)
export(reactivePlot)
export(reactivePoll)
export(reactivePrint)
export(reactiveTable)
export(reactiveText)
@@ -76,6 +85,7 @@ export(reactiveTimer)
export(reactiveUI)
export(reactiveValues)
export(reactiveValuesToList)
export(renderDataTable)
export(renderImage)
export(renderPlot)
export(renderPrint)
@@ -91,10 +101,12 @@ 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)
@@ -102,7 +114,9 @@ export(tableOutput)
export(tabsetPanel)
export(tag)
export(tagAppendChild)
export(tagAppendChildren)
export(tagList)
export(tagSetChildren)
export(tags)
export(textInput)
export(textOutput)
@@ -117,6 +131,7 @@ export(updateSelectInput)
export(updateSliderInput)
export(updateTabsetPanel)
export(updateTextInput)
export(validateCssUnit)
export(verbatimTextOutput)
export(wellPanel)
export(withTags)
@@ -124,4 +139,5 @@ import(RJSONIO)
import(caTools)
import(digest)
import(httpuv)
import(methods)
import(xtable)

95
NEWS
View File

@@ -1,3 +1,94 @@
shiny 0.8.0
--------------------------------------------------------------------------------
* Debug hooks are registered on all user-provided functions and (reactive)
expressions (e.g., in renderPlot()), which makes it possible to set
breakpoints in these functions using the latest version of the RStudio
IDE, and the RStudio visual debugging tools can be used to debug Shiny
apps. Internally, the registration is done via installExprFunction(),
which is a new function introduced in this version to replace
exprToFunction() so that the registration can be automatically done.
* Added a new function renderDataTable() to display tables using the
JavaScript library DataTables. It includes basic features like pagination,
searching (global search or search by individual columns), sorting (by
single or multiple columns). All these features are implemented on the R
side; for example, we can use R regular expressions for searching.
Besides, it also uses the Twitter Bootstrap CSS style. See the full
documentation and examples in the tutorial:
http://rstudio.github.io/shiny/tutorial/#datatables
* Added a new option `shiny.error` which can take a function as an error
handler. It is called when an error occurs in an app (in user-provided
code), e.g., after we set options(shiny.error = recover), we can enter a
specified environment in the call stack to debug our code after an error
occurs.
* The argument `launch.browser` in runApp() can also be a function,
which takes the URL of the shiny app as its input value.
* runApp() uses a random port between 3000 and 8000 instead of 8100 now. It
will try up to 20 ports in case certain ports are not available.
* Fixed a bug for conditional panels: the value `input.id` in the condition
was not correctly retrieved when the input widget had a type, such as
numericInput(). (reported by Jason Bryer)
* Fixed two bugs in plotOutput(); clickId and hoverId did not give correct
coordinates in Firefox, or when the axis limits of the plot were changed.
(reported by Chris Warth and Greg D)
* The minimal required version for the httpuv package was increased to 1.2
(on CRAN now).
shiny 0.7.0
--------------------------------------------------------------------------------
* Stopped sending websocket subprotocol. This fixes a compatibility issue with
Google Chrome 30.
* The `input` and `output` objects are now also accessible via `session$input`
and `session$output`.
* Added click and hover events for static plots; see `?plotOutput` for details.
* Added optional logging of the execution states of a reactive program, and
tools for visualizing the log data. To use, start a new R session and call
`options(shiny.reactlog=TRUE)`. Then launch a Shiny app and interact with it.
Press Ctrl+F3 (or for Mac, Cmd+F3) in the browser to launch an interactive
visualization of the reactivity that has occurred. See `?showReactLog` for
more information.
* Added `includeScript()` and `includeCSS()` functions.
* Reactive expressions now have class="reactive" attribute. Also added
`is.reactive()` and `is.reactivevalues()` functions.
* New `stopApp()` function, which stops an app and returns a value to the caller
of `runApp()`.
* Added the `shiny.usecairo` option, which can be used to tell Shiny not to use
Cairo for PNG output even when it is installed. (Defaults to `TRUE`.)
* Speed increases for `selectInput()` and `radioButtons()`, and their
corresponding updater functions, for when they have many options.
* Added `tagSetChildren()` and `tagAppendChildren()` functions.
* The HTTP request object that created the websocket is now accessible from the
`session` object, as `session$request`. This is a Rook-like request
environment that can be used to access HTTP headers, among other things.
(Note: When running in a Shiny Server environment, the request will reflect
the proxy HTTP request that was made from the Shiny Server process to the R
process, not the request that was made from the web browser to Shiny Server.)
* Fix `getComputedStyle` issue, for IE8 browser compatibility (#196). Note:
Shiny Server is still required for IE8/9 compatibility.
* Add shiny.sharedSecret option, to require the HTTP header Shiny-Shared-Secret
to be set to the given value.
shiny 0.6.0
--------------------------------------------------------------------------------
@@ -284,11 +375,11 @@ shiny 0.1.3
creating custom input controls
* Add `step` parameter to numericInput
* Read names of input using `names(input)`
* Access snapshot of input as a list using `as.list(input)`
* Access snapshot of input as a list using `as.list(input)`
* Fix issue #10: Plots in tabsets not rendered
shiny 0.1.2
--------------------------------------------------------------------------------
Initial private beta release!
Initial private beta release!

View File

@@ -255,6 +255,7 @@ conditionalPanel <- function(condition, ...) {
#' @param value Initial value
#' @return A text input control that can be added to a UI definition.
#'
#' @family input elements
#' @seealso \code{\link{updateTextInput}}
#'
#' @examples
@@ -279,6 +280,7 @@ textInput <- function(inputId, label, value = "") {
#' @param step Interval to use when stepping between min and max
#' @return A numeric input control that can be added to a UI definition.
#'
#' @family input elements
#' @seealso \code{\link{updateNumericInput}}
#'
#' @examples
@@ -326,6 +328,8 @@ numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA) {
#' operation.}
#' }
#'
#' @family input elements
#'
#' @param inputId Input variable to assign the control's value to.
#' @param label Display label for the control.
#' @param multiple Whether the user should be allowed to select and upload
@@ -363,6 +367,7 @@ fileInput <- function(inputId, label, multiple = FALSE, accept = NULL) {
#' @param value Initial value (\code{TRUE} or \code{FALSE}).
#' @return A checkbox control that can be added to a UI definition.
#'
#' @family input elements
#' @seealso \code{\link{checkboxGroupInput}}, \code{\link{updateCheckboxInput}}
#'
#' @examples
@@ -389,6 +394,7 @@ checkboxInput <- function(inputId, label, value = FALSE) {
#' @param selected Names of items that should be initially selected, if any.
#' @return A list of HTML elements that can be added to a UI definition.
#'
#' @family input elements
#' @seealso \code{\link{checkboxInput}}, \code{\link{updateCheckboxGroupInput}}
#'
#' @examples
@@ -401,25 +407,26 @@ checkboxInput <- function(inputId, label, value = FALSE) {
checkboxGroupInput <- function(inputId, label, choices, selected = NULL) {
# resolve names
choices <- choicesWithNames(choices)
checkboxes <- list()
for (i in seq_along(choices)) {
choiceName <- names(choices)[i]
inputTag <- tags$input(type = "checkbox",
name = inputId,
id = paste(inputId, i, sep=""),
value = choices[[i]])
# Create tags for each of the options
ids <- paste0(inputId, seq_along(choices))
if (choiceName %in% selected)
checkboxes <- mapply(ids, choices, names(choices),
SIMPLIFY = FALSE, USE.NAMES = FALSE,
FUN = function(id, value, name) {
inputTag <- tags$input(type = "checkbox",
name = inputId,
id = id,
value = value)
if (name %in% selected)
inputTag$attribs$checked <- "checked"
checkbox <- tags$label(class = "checkbox",
inputTag,
tags$span(choiceName))
checkboxes[[i]] <- checkbox
}
tags$label(class = "checkbox",
inputTag,
tags$span(name))
}
)
# return label and select tag
tags$div(id = inputId,
@@ -482,6 +489,7 @@ choicesWithNames <- function(choices) {
#' @param multiple Is selection of multiple items allowed?
#' @return A select list control that can be added to a UI definition.
#'
#' @family input elements
#' @seealso \code{\link{updateSelectInput}}
#'
#' @examples
@@ -507,16 +515,21 @@ selectInput <- function(inputId,
if (multiple)
selectTag$attribs$multiple <- "multiple"
for (i in seq_along(choices)) {
choiceName <- names(choices)[i]
optionTag <- tags$option(value = choices[[i]], choiceName)
# Create tags for each of the options
optionTags <- mapply(choices, names(choices),
SIMPLIFY = FALSE, USE.NAMES = FALSE,
FUN = function(choice, name) {
optionTag <- tags$option(value = choice, name)
if (choiceName %in% selected)
optionTag$attribs$selected = "selected"
if (name %in% selected)
optionTag$attribs$selected = "selected"
optionTag
}
)
selectTag <- tagSetChildren(selectTag, list = optionTags)
selectTag <- tagAppendChild(selectTag, optionTag)
}
# return label and select tag
tagList(controlLabel(inputId, label), selectTag)
}
@@ -533,6 +546,7 @@ selectInput <- function(inputId,
#' defaults to the first item)
#' @return A set of radio buttons that can be added to a UI definition.
#'
#' @family input elements
#' @seealso \code{\link{updateRadioButtons}}
#'
#' @examples
@@ -550,27 +564,28 @@ radioButtons <- function(inputId, label, choices, selected = NULL) {
if (is.null(selected))
selected <- names(choices)[[1]]
# build list of radio button tags
inputTags <- list()
for (i in seq_along(choices)) {
id <- paste(inputId, i, sep="")
name <- names(choices)[[i]]
value <- choices[[i]]
inputTag <- tags$input(type = "radio",
name = inputId,
id = id,
value = value)
if (identical(name, selected))
inputTag$attribs$checked = "checked"
# Create tags for each of the options
ids <- paste0(inputId, seq_along(choices))
inputTags <- mapply(ids, choices, names(choices),
SIMPLIFY = FALSE, USE.NAMES = FALSE,
FUN = function(id, value, name) {
inputTag <- tags$input(type = "radio",
name = inputId,
id = id,
value = value)
if (identical(name, selected))
inputTag$attribs$checked = "checked"
# Put the label text in a span
tags$label(class = "radio",
inputTag,
tags$span(name)
)
}
)
# Put the label text in a span
spanTag <- tags$span(name)
labelTag <- tags$label(class = "radio")
labelTag <- tagAppendChild(labelTag, inputTag)
labelTag <- tagAppendChild(labelTag, spanTag)
inputTags[[length(inputTags) + 1]] <- labelTag
}
tags$div(id = inputId,
class = 'control-group shiny-input-radiogroup',
tags$label(class = "control-label", `for` = inputId, label),
@@ -586,6 +601,8 @@ radioButtons <- function(inputId, label, choices, selected = NULL) {
#' @param text Button caption
#' @return A submit button that can be added to a UI definition.
#'
#' @family input elements
#'
#' @examples
#' submitButton("Update View")
#' @export
@@ -605,6 +622,8 @@ submitButton <- function(text = "Apply Changes") {
#' @param label The contents of the button--usually a text label, but you could
#' also use any other HTML, like an image.
#'
#' @family input elements
#'
#' @export
actionButton <- function(inputId, label) {
tags$button(id=inputId, type="button", class="btn action-button", label)
@@ -619,8 +638,10 @@ actionButton <- function(inputId, label) {
#' @param label A descriptive label to be displayed with the widget.
#' @param min The minimum value (inclusive) that can be selected.
#' @param max The maximum value (inclusive) that can be selected.
#' @param value The initial value of the slider. A warning will be issued if the
#' value doesn't fit between \code{min} and \code{max}.
#' @param value The initial value of the slider. A numeric vector of length
#' one will create a regular slider; a numeric vector of length two will
#' create a double-ended range slider. A warning will be issued if the
#' value doesn't fit between \code{min} and \code{max}.
#' @param step Specifies the interval between each selectable value on the
#' slider (\code{NULL} means no restriction).
#' @param round \code{TRUE} to round all values to the nearest integer;
@@ -638,6 +659,7 @@ actionButton <- function(inputId, label) {
#' settings; \code{FALSE} not to; or a custom settings list, such as those
#' created using \code{\link{animationOptions}}.
#'
#' @family input elements
#' @seealso \code{\link{updateSliderInput}}
#'
#' @details
@@ -726,6 +748,7 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
#' "nb", "nl", "pl", "pt", "pt", "ro", "rs", "rs-latin", "ru", "sk", "sl",
#' "sv", "sw", "th", "tr", "uk", "zh-CN", and "zh-TW".
#'
#' @family input elements
#' @seealso \code{\link{dateRangeInput}}, \code{\link{updateDateInput}}
#'
#' @examples
@@ -806,31 +829,16 @@ dateInput <- function(inputId, label, value = NULL, min = NULL, max = NULL,
#' \item \code{DD} Full weekday name
#' }
#'
#' @param inputId Input variable to assign the control's value to.
#' @param label Display label for the control.
#' @inheritParams dateInput
#' @param start The initial start date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
#' date in the client's time zone.
#' @param end The initial end date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
#' date in the client's time zone.
#' @param min The minimum allowed date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param max The maximum allowed date. Either a Date object, or a string in
#' \code{yyyy-mm-dd} format.
#' @param format The format of the date to display in the browser. Defaults to
#' \code{"yyyy-mm-dd"}.
#' @param startview The date range shown when the input object is first
#' clicked. Can be "month" (the default), "year", or "decade".
#' @param weekstart Which day is the start of the week. Should be an integer
#' from 0 (Sunday) to 6 (Saturday).
#' @param language The language used for month and day names. Default is "en".
#' Other valid values include "bg", "ca", "cs", "da", "de", "el", "es", "fi",
#' "fr", "he", "hr", "hu", "id", "is", "it", "ja", "kr", "lt", "lv", "ms",
#' "nb", "nl", "pl", "pt", "pt", "ro", "rs", "rs-latin", "ru", "sk", "sl",
#' "sv", "sw", "th", "tr", "uk", "zh-CN", and "zh-TW".
#' @param separator String to display between the start and end input boxes.
#'
#' @family input elements
#' @seealso \code{\link{dateInput}}, \code{\link{updateDateRangeInput}}
#'
#' @examples
@@ -1009,6 +1017,8 @@ tabsetPanel <- function(..., id = NULL, selected = NULL) {
firstTab = FALSE
}
divTag$attribs$title <- NULL
# append the elements to our lists
tabNavList <- tagAppendChild(tabNavList, liTag)
tabContent <- tagAppendChild(tabContent, divTag)
@@ -1084,6 +1094,24 @@ imageOutput <- function(outputId, width = "100%", height="400px") {
#' \code{"400px"}, \code{"auto"}) or a number, which will be coerced to a
#' string and have \code{"px"} appended.
#' @param height Plot height
#' @param clickId If not \code{NULL}, the plot will send coordinates to the
#' server whenever it is clicked. This information will be accessible on the
#' \code{input} object using \code{input$}\emph{\code{clickId}}. The value will be a
#' named list or vector with \code{x} and \code{y} elements indicating the
#' mouse position in user units.
#' @param hoverId If not \code{NULL}, the plot will send coordinates to the
#' server whenever the mouse pauses on the plot for more than the number of
#' milliseconds determined by \code{hoverTimeout}. This information will be
# accessible on the \code{input} object using \code{input$}\emph{\code{clickId}}.
#' The value will be \code{NULL} if the user is not hovering, and a named
#' list or vector with \code{x} and \code{y} elements indicating the mouse
#' position in user units.
#' @param hoverDelay The delay for hovering, in milliseconds.
#' @param hoverDelayType The type of algorithm for limiting the number of hover
#' events. Use \code{"throttle"} to limit the number of hover events to one
#' every \code{hoverDelay} milliseconds. Use \code{"debounce"} to suspend
#' events while the cursor is moving, and wait until the cursor has been at
#' rest for \code{hoverDelay} milliseconds before sending an event.
#' @return A plot output element that can be included in a panel
#' @examples
#' # Show a plot of the generated distribution
@@ -1091,10 +1119,23 @@ imageOutput <- function(outputId, width = "100%", height="400px") {
#' plotOutput("distPlot")
#' )
#' @export
plotOutput <- function(outputId, width = "100%", height="400px") {
plotOutput <- function(outputId, width = "100%", height="400px",
clickId = NULL, hoverId = NULL, hoverDelay = 300,
hoverDelayType = c("debounce", "throttle")) {
if (is.null(clickId) && is.null(hoverId)) {
hoverDelay <- NULL
hoverDelayType <- NULL
} else {
hoverDelayType <- match.arg(hoverDelayType)[[1]]
}
style <- paste("width:", validateCssUnit(width), ";",
"height:", validateCssUnit(height))
div(id = outputId, class = "shiny-plot-output", style = style)
div(id = outputId, class = "shiny-plot-output", style = style,
`data-click-id` = clickId,
`data-hover-id` = hoverId,
`data-hover-delay` = hoverDelay,
`data-hover-delay-type` = hoverDelayType)
}
#' Create a table output element
@@ -1111,6 +1152,20 @@ tableOutput <- function(outputId) {
div(id = outputId, class="shiny-html-output")
}
#' @rdname tableOutput
#' @export
dataTableOutput <- function(outputId) {
tagList(
singleton(tags$head(
tags$link(rel = "stylesheet", type = "text/css",
href = "shared/datatables/css/DT_bootstrap.css"),
tags$script(src = "shared/datatables/js/jquery.dataTables.min.js"),
tags$script(src = "shared/datatables/js/DT_bootstrap.js")
)),
div(id = outputId, class="shiny-datatable-output")
)
}
#' Create an HTML output element
#'
#' Render a reactive output variable as HTML within an application page. The
@@ -1184,6 +1239,16 @@ downloadLink <- function(outputId, label="Download", class=NULL) {
label)
}
#' Validate proper CSS formatting of a unit
#'
#' @param x The unit to validate. Will be treated as a number of pixels if a
#' unit is not specified.
#' @return A properly formatted CSS unit of length, if possible. Otherwise, will
#' throw an error.
#' @examples
#' validateCssUnit("10%")
#' validateCssUnit(400) #treated as '400px'
#' @export
validateCssUnit <- function(x) {
if (is.character(x) &&
!grepl("^(auto|((\\.\\d+)|(\\d+(\\.\\d+)?))(%|in|cm|mm|em|ex|pt|pc|px))$", x)) {

View File

@@ -71,7 +71,7 @@ CacheContext <- setRefClass(
# If NULL or NA is given as the argument, then ui.R will re-execute next time.
dependsOnFile <- function(filepath) {
if (is.null(.currentCacheContext$cc))
stop("addFileDependency was called at an unexpected time (no cache context found)")
return()
if (is.null(filepath) || is.na(filepath))
.currentCacheContext$cc$forceDirty()

105
R/graph.R Normal file
View File

@@ -0,0 +1,105 @@
writeReactLog <- function(file=stdout()) {
cat(RJSONIO::toJSON(.graphEnv$log, pretty=TRUE), file=file)
}
#' Reactive Log Visualizer
#'
#' Provides an interactive browser-based tool for visualizing reactive
#' dependencies and execution in your application.
#'
#' To use the reactive log visualizer, start with a fresh R session and
#' run the command \code{options(shiny.reactlog=TRUE)}; then launch your
#' application in the usual way (e.g. using \code{\link{runApp}}). At
#' any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your
#' web browser to launch the reactive log visualization.
#'
#' The reactive log visualization only includes reactive activity up
#' until the time the report was loaded. If you want to see more recent
#' activity, refresh the browser.
#'
#' Note that Shiny does not distinguish between reactive dependencies
#' that "belong" to one Shiny user session versus another, so the
#' visualization will include all reactive activity that has taken place
#' in the process, not just for a particular application or session.
#'
#' As an alternative to pressing Ctrl/Command+F3--for example, if you
#' are using reactives outside of the context of a Shiny
#' application--you can run the \code{showReactLog} function, which will
#' generate the reactive log visualization as a static HTML file and
#' launch it in your default browser. In this case, refreshing your
#' browser will not load new activity into the report; you will need to
#' call \code{showReactLog()} explicitly.
#'
#' For security and performance reasons, do not enable
#' \code{shiny.reactlog} in production environments. When the option is
#' enabled, it's possible for any user of your app to see at least some
#' of the source code of your reactive expressions and observers.
#'
#' @export
showReactLog <- function() {
browseURL(renderReactLog())
}
renderReactLog <- function() {
templateFile <- system.file('www/reactive-graph.html', package='shiny')
html <- paste(readLines(templateFile, warn=FALSE), collapse='\r\n')
tc <- textConnection(NULL, 'w')
on.exit(close(tc))
writeReactLog(tc)
cat('\n', file=tc)
flush(tc)
html <- sub('__DATA__', paste(textConnectionValue(tc), collapse='\r\n'), html, fixed=TRUE)
file <- tempfile(fileext = '.html')
writeLines(html, file)
return(file)
}
.graphAppend <- function(logEntry) {
if (isTRUE(getOption('shiny.reactlog', FALSE)))
.graphEnv$log <- c(.graphEnv$log, list(logEntry))
}
.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()

View File

@@ -11,6 +11,10 @@
#' 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}.
@@ -30,9 +34,8 @@ plotPNG <- function(func, filename=tempfile(fileext='.png'),
# Finally, if neither quartz nor Cairo, use png().
if (capabilities("aqua")) {
pngfun <- png
} else if (nchar(system.file(package = "Cairo"))) {
require(Cairo)
} 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) {
@@ -44,10 +47,9 @@ plotPNG <- function(func, filename=tempfile(fileext='.png'),
pngfun <- png
}
do.call(pngfun, c(filename=filename, width=width, height=height, res=res, list(...)))
tryCatch(
func(),
finally=dev.off())
pngfun(filename=filename, width=width, height=height, res=res, ...)
dv <- dev.cur()
tryCatch(shinyCallingHandlers(func()), finally = dev.off(dv))
filename
}

View File

@@ -8,16 +8,19 @@ Context <- setRefClass(
.flushCallbacks = 'list'
),
methods = list(
initialize = function(label='') {
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) {
"Run the provided function under this context."
env <- .getReactiveEnvironment()
.graphEnterContext(id)
on.exit(.graphExitContext(id))
env$runWith(.self, func)
},
invalidate = function() {
@@ -27,6 +30,7 @@ Context <- setRefClass(
return()
.invalidated <<- TRUE
.graphInvalidate(id)
lapply(.invalidateCallbacks, function(func) {
func()
})
@@ -86,17 +90,18 @@ ReactiveEnvironment <- setRefClass(
return(as.character(.nextId))
},
currentContext = function() {
if (is.null(.currentContext))
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)
},
runWith = function(ctx, func) {
old.ctx <- .currentContext
.currentContext <<- ctx
on.exit(.currentContext <<- old.ctx)
func()
shinyCallingHandlers(func())
},
addPendingFlush = function(ctx, priority) {
.pendingFlush$enqueue(ctx, priority)

View File

@@ -4,13 +4,18 @@ Dependents <- setRefClass(
.dependents = 'Map'
),
methods = list(
register = function() {
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() {
@@ -31,6 +36,8 @@ Dependents <- setRefClass(
ReactiveValues <- setRefClass(
'ReactiveValues',
fields = list(
# For debug purposes
.label = 'character',
.values = 'environment',
.dependents = 'environment',
# Dependents for the list of all names, including hidden
@@ -42,6 +49,8 @@ ReactiveValues <- setRefClass(
),
methods = list(
initialize = function() {
.label <<- paste('reactiveValues', runif(1, min=1000, max=9999),
sep="")
.values <<- new.env(parent=emptyenv())
.dependents <<- new.env(parent=emptyenv())
},
@@ -49,6 +58,7 @@ ReactiveValues <- setRefClass(
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)
@@ -76,8 +86,13 @@ ReactiveValues <- setRefClass(
.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=''),
@@ -99,16 +114,23 @@ ReactiveValues <- setRefClass(
})
},
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
}
)
)
@@ -151,7 +173,7 @@ ReactiveValues <- setRefClass(
#' @param ... Objects that will be added to the reactivevalues object. All of
#' these objects must be named.
#'
#' @seealso \code{\link{isolate}}.
#' @seealso \code{\link{isolate}} and \code{\link{is.reactivevalues}}.
#'
#' @export
reactiveValues <- function(...) {
@@ -177,6 +199,15 @@ setOldClass("reactivevalues")
structure(list(impl=values), class='reactivevalues', readonly=readonly)
}
#' Checks whether an object is a reactivevalues object
#'
#' Checks whether its argument is a reactivevalues object.
#'
#' @param x The object to test.
#' @seealso \code{\link{reactiveValues}}.
#' @export
is.reactivevalues <- function(x) inherits(x, 'reactivevalues')
#' @S3method $ reactivevalues
`$.reactivevalues` <- function(x, name) {
.subset2(x, 'impl')$get(name)
@@ -231,6 +262,11 @@ as.list.reactivevalues <- function(x, all.names=FALSE, ...) {
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}}
@@ -269,7 +305,8 @@ Observable <- setRefClass(
.running = 'logical',
.value = 'ANY',
.visible = 'logical',
.execCount = 'integer'
.execCount = 'integer',
.mostRecentCtxId = 'character'
),
methods = list(
initialize = function(func, label=deparse(substitute(func))) {
@@ -282,6 +319,7 @@ Observable <- setRefClass(
.running <<- FALSE
.label <<- label
.execCount <<- 0L
.mostRecentCtxId <<- ""
},
getValue = function() {
.dependents$register()
@@ -289,6 +327,8 @@ Observable <- setRefClass(
if (.invalidated || .running) {
.self$.updateValue()
}
.graphDependsOnId(getCurrentContext()$id, .mostRecentCtxId)
if (identical(class(.value), 'try-error'))
stop(attr(.value, 'condition'))
@@ -299,7 +339,8 @@ Observable <- setRefClass(
invisible(.value)
},
.updateValue = function() {
ctx <- Context$new(.label)
ctx <- Context$new(.label, type='observable', prevId=.mostRecentCtxId)
.mostRecentCtxId <<- ctx$id
ctx$onInvalidate(function() {
.invalidated <<- TRUE
.dependents$invalidate()
@@ -313,7 +354,7 @@ Observable <- setRefClass(
on.exit(.running <<- wasRunning)
ctx$run(function() {
result <- withVisible(try(.func(), silent=FALSE))
result <- withVisible(try(shinyCallingHandlers(.func()), silent=FALSE))
.visible <<- result$visible
.value <<- result$value
})
@@ -337,7 +378,8 @@ Observable <- setRefClass(
#' 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 x For \code{reactive}, an expression (quoted or unquoted). For
#' \code{is.reactive}, an object to test.
#' @param env The parent environment for the reactive expression. By default, this
#' is the calling environment, the same as when defining an ordinary
#' non-reactive expression.
@@ -345,6 +387,7 @@ Observable <- setRefClass(
#' This is useful when you want to use an expression that is stored in a
#' variable; to do so, it must be quoted with `quote()`.
#' @param label A label for the reactive expression, useful for debugging.
#' @return a function, wrapped in a S3 class "reactive"
#'
#' @examples
#' values <- reactiveValues(A=1)
@@ -369,11 +412,23 @@ Observable <- setRefClass(
reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL) {
fun <- exprToFunction(x, env, quoted)
if (is.null(label))
label <- deparse(body(fun))
label <- sprintf('reactive(%s)', paste(deparse(body(fun)), collapse='\n'))
Observable$new(fun, label)$getValue
o <- Observable$new(fun, label)
registerDebugHook(".func", o, "Reactive")
structure(o$getValue@.Data, observable = o, class = "reactive")
}
#' @S3method print reactive
print.reactive <- function(x, ...) {
label <- attr(x, "observable")$.label
cat(label, "\n")
}
#' @export
#' @rdname reactive
is.reactive <- function(x) inherits(x, "reactive")
# Return the number of times that a reactive expression or observer has been run
execCount <- function(x) {
if (is.function(x))
@@ -395,7 +450,8 @@ Observer <- setRefClass(
.invalidateCallbacks = 'list',
.execCount = 'integer',
.onResume = 'function',
.suspended = 'logical'
.suspended = 'logical',
.prevId = 'character'
),
methods = list(
initialize = function(func, label, suspended = FALSE, priority = 0) {
@@ -409,12 +465,14 @@ Observer <- setRefClass(
.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)
ctx <- Context$new(.label, type='observer', prevId=.prevId)
.prevId <<- ctx$id
ctx$onInvalidate(function() {
lapply(.invalidateCallbacks, function(func) {
@@ -443,12 +501,14 @@ Observer <- setRefClass(
.execCount <<- .execCount + 1L
ctx$run(.func)
},
onInvalidate = function(func) {
"Register a function to run when this observer is invalidated"
.invalidateCallbacks <<- c(.invalidateCallbacks, 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 the observer's priority. Note that if the observer is currently
"Change this observer's priority. Note that if the observer is currently
invalidated, then the change in priority will not take effect until the
next invalidation--unless the observer is also currently suspended, in
which case the priority change will be effective upon resume."
@@ -458,7 +518,7 @@ Observer <- setRefClass(
"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, becasue the flush is
called) then that re-execution will still occur, because the flush is
already scheduled."
.suspended <<- TRUE
},
@@ -478,10 +538,12 @@ Observer <- setRefClass(
#' Create a reactive observer
#'
#' Creates an observer from the given expression An observer is like a reactive
#' 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 expression, it doesn't yield a result and can't be used as an input
#' 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).
#'
@@ -506,6 +568,32 @@ Observer <- setRefClass(
#' 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)
@@ -531,10 +619,11 @@ observe <- function(x, env=parent.frame(), quoted=FALSE, label=NULL,
fun <- exprToFunction(x, env, quoted)
if (is.null(label))
label <- deparse(body(fun))
label <- sprintf('observe(%s)', paste(deparse(body(fun)), collapse='\n'))
invisible(Observer$new(
fun, label=label, suspended=suspended, priority=priority))
o <- Observer$new(fun, label=label, suspended=suspended, priority=priority)
registerDebugHook(".func", o, "Observer")
invisible(o)
}
# ---------------------------------------------------------------------------
@@ -678,6 +767,168 @@ invalidateLater <- function(millis, session) {
invisible()
}
coerceToFunc <- function(x) {
force(x);
if (is.function(x))
return(x)
else
return(function() x)
}
#' Reactive polling
#'
#' Used to create a reactive data source, which works by periodically polling a
#' non-reactive data source.
#'
#' \code{reactivePoll} works by pairing a relatively cheap "check" function with
#' a more expensive value retrieval function. The check function will be
#' executed periodically and should always return a consistent value until the
#' data changes. When the check function returns a different value, then the
#' value retrieval function will be used to re-populate the data.
#'
#' Note that the check function doesn't return \code{TRUE} or \code{FALSE} to
#' indicate whether the underlying data has changed. Rather, the check function
#' indicates change by returning a different value from the previous time it was
#' called.
#'
#' For example, \code{reactivePoll} is used to implement
#' \code{reactiveFileReader} by pairing a check function that simply returns the
#' last modified timestamp of a file, and a value retrieval function that
#' actually reads the contents of the file.
#'
#' As another example, one might read a relational database table reactively by
#' using a check function that does \code{SELECT MAX(timestamp) FROM table} and
#' a value retrieval function that does \code{SELECT * FROM table}.
#'
#' The \code{intervalMillis}, \code{checkFunc}, and \code{valueFunc} functions
#' will be executed in a reactive context; therefore, they may read reactive
#' values and reactive expressions.
#'
#' @param intervalMillis Approximate number of milliseconds to wait between
#' calls to \code{checkFunc}. This can be either a numeric value, or a
#' function that returns a numeric value.
#' @param session The user session to associate this file reader with, or
#' \code{NULL} if none. If non-null, the reader will automatically stop when
#' the session ends.
#' @param checkFunc A relatively cheap function whose values over time will be
#' tested for equality; inequality indicates that the underlying value has
#' changed and needs to be invalidated and re-read using \code{valueFunc}. See
#' Details.
#' @param valueFunc A function that calculates the underlying value. See
#' Details.
#'
#' @return A reactive expression that returns the result of \code{valueFunc},
#' and invalidates when \code{checkFunc} changes.
#'
#' @seealso \code{\link{reactiveFileReader}}
#'
#' @examples
#' \dontrun{
#' # Assume the existence of readTimestamp and readValue functions
#' shinyServer(function(input, output, session) {
#' data <- reactivePoll(1000, session, readTimestamp, readValue)
#' output$dataTable <- renderTable({
#' data()
#' })
#' })
#' }
#'
#' @export
reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
intervalMillis <- coerceToFunc(intervalMillis)
rv <- reactiveValues(cookie = isolate(checkFunc()))
observe({
rv$cookie <- checkFunc()
invalidateLater(intervalMillis(), session)
})
# TODO: what to use for a label?
re <- reactive({
rv$cookie
valueFunc()
}, label = NULL)
return(re)
}
#' Reactive file reader
#'
#' Given a file path and read function, returns a reactive data source for the
#' contents of the file.
#'
#' \code{reactiveFileReader} works by periodically checking the file's last
#' modified time; if it has changed, then the file is re-read and any reactive
#' dependents are invalidated.
#'
#' The \code{intervalMillis}, \code{filePath}, and \code{readFunc} functions
#' will each be executed in a reactive context; therefore, they may read
#' reactive values and reactive expressions.
#'
#' @param intervalMillis Approximate number of milliseconds to wait between
#' checks of the file's last modified time. This can be a numeric value, or a
#' function that returns a numeric value.
#' @param session The user session to associate this file reader with, or
#' \code{NULL} if none. If non-null, the reader will automatically stop when
#' the session ends.
#' @param filePath The file path to poll against and to pass to \code{readFunc}.
#' This can either be a single-element character vector, or a function that
#' returns one.
#' @param readFunc The function to use to read the file; must expect the first
#' argument to be the file path to read. The return value of this function is
#' used as the value of the reactive file reader.
#' @param ... Any additional arguments to pass to \code{readFunc} whenever it is
#' invoked.
#'
#' @return A reactive expression that returns the contents of the file, and
#' automatically invalidates when the file changes on disk (as determined by
#' last modified time).
#'
#' @seealso \code{\link{reactivePoll}}
#'
#' @examples
#' \dontrun{
#' # Per-session reactive file reader
#' shinyServer(function(input, output, session)) {
#' fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
#'
#' output$data <- renderTable({
#' fileData()
#' })
#' }
#'
#' # Cross-session reactive file reader. In this example, all sessions share
#' # the same reader, so read.csv only gets executed once no matter how many
#' # user sessions are connected.
#' fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
#' shinyServer(function(input, output, session)) {
#' output$data <- renderTable({
#' fileData()
#' })
#' }
#' }
#'
#' @export
reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...) {
filePath <- coerceToFunc(filePath)
extraArgs <- list(...)
reactivePoll(
intervalMillis, session,
function() {
path <- filePath()
info <- file.info(path)
return(paste(path, info$mtime, info$size))
},
function() {
do.call(readFunc, c(filePath(), extraArgs))
}
)
}
#' Create a non-reactive scope for an expression
#'
#' Executes the given expression in a scope where reactive values or expression
@@ -750,7 +1001,8 @@ invalidateLater <- function(millis, session) {
#'
#' @export
isolate <- function(expr) {
ctx <- Context$new('[isolate]')
ctx <- Context$new('[isolate]', type='isolate')
on.exit(ctx$invalidate())
ctx$run(function() {
expr
})

View File

@@ -7,7 +7,7 @@
#' \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.
#' choosing a random port.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
@@ -23,7 +23,7 @@
#'
#' @export
runGist <- function(gist,
port=8100L,
port=NULL,
launch.browser=getOption('shiny.launch.browser',
interactive())) {
@@ -52,7 +52,7 @@ runGist <- function(gist,
#' 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.
#' choosing a random port.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
@@ -67,7 +67,7 @@ runGist <- function(gist,
#'
#' @export
runGitHub <- function(repo, username = getOption("github.user"),
ref = "master", subdir = NULL, port = 8100,
ref = "master", subdir = NULL, port = NULL,
launch.browser = getOption('shiny.launch.browser', interactive())) {
if (is.null(ref)) {
@@ -102,7 +102,7 @@ runGitHub <- function(repo, username = getOption("github.user"),
#' 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.
#' choosing a random port.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
@@ -117,7 +117,7 @@ runGitHub <- function(repo, username = getOption("github.user"),
#' }
#'
#' @export
runUrl <- function(url, filetype = NULL, subdir = NULL, port = 8100,
runUrl <- function(url, filetype = NULL, subdir = NULL, port = NULL,
launch.browser = getOption('shiny.launch.browser', interactive())) {
if (!is.null(subdir) && ".." %in% strsplit(subdir, '/')[[1]])

297
R/shiny.R
View File

@@ -1,5 +1,18 @@
#' Web Application Framework for R
#'
#' 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.
#'
#' The Shiny tutorial at \url{http://rstudio.github.com/shiny/tutorial} explains
#' the framework in depth, walks you through building a simple application, and
#' includes extensive annotated examples.
#'
#' @name shiny-package
#' @aliases shiny
#' @docType package
#' @import httpuv caTools RJSONIO xtable digest
#' @import httpuv caTools RJSONIO xtable digest methods
NULL
suppressPackageStartupMessages({
@@ -26,17 +39,22 @@ ShinySession <- setRefClass(
.input = 'ReactiveValues', # Internal object for normal input sent from client
.clientData = 'ReactiveValues', # Internal object for other data sent from the client
.closedCallbacks = 'Callbacks',
.flushCallbacks = 'Callbacks',
.flushedCallbacks = 'Callbacks',
input = 'reactivevalues', # Externally-usable S3 wrapper object for .input
output = 'ANY', # Externally-usable S3 wrapper object for .outputs
clientData = 'reactivevalues', # Externally-usable S3 wrapper object for .clientData
token = 'character', # Used to identify this instance in URLs
files = 'Map', # For keeping track of files sent to client
downloads = 'Map',
closed = 'logical',
session = 'list' # Object for the server app to access session stuff
session = 'environment', # Object for the server app to access session stuff
.workerId = 'character'
),
methods = list(
initialize = function(websocket) {
initialize = function(websocket, workerId) {
.websocket <<- websocket
.workerId <<- workerId
.invalidatedOutputValues <<- Map$new()
.invalidatedOutputErrors <<- Map$new()
.inputMessageQueue <<- list()
@@ -49,17 +67,45 @@ ShinySession <- setRefClass(
.clientData <<- ReactiveValues$new()
input <<- .createReactiveValues(.input, readonly=TRUE)
.setLabel(input, 'input')
clientData <<- .createReactiveValues(.clientData, readonly=TRUE)
.setLabel(clientData, 'clientData')
output <<- .createOutputWriter(.self)
token <<- createUniqueId(16)
.outputs <<- list()
.outputOptions <<- list()
session <<- list(clientData = clientData,
sendCustomMessage = .self$.sendCustomMessage,
sendInputMessage = .self$.sendInputMessage,
onSessionEnded = .self$onSessionEnded,
isClosed = .self$isClosed)
session <<- new.env(parent=emptyenv())
session$clientData <<- clientData
session$sendCustomMessage <<- .self$.sendCustomMessage
session$sendInputMessage <<- .self$.sendInputMessage
session$onSessionEnded <<- .self$onSessionEnded
session$onFlush <<- .self$onFlush
session$onFlushed <<- .self$onFlushed
session$isClosed <<- .self$isClosed
session$input <<- .self$input
session$output <<- .self$output
session$.impl <<- .self
if (!is.null(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)) {
try({
creds <- fromJSON(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)
session$user <<- creds$user
session$groups <<- creds$groups
}, silent=FALSE)
}
# session$request should throw an error if httpuv doesn't have
# websocket$request, but don't throw it until a caller actually
# tries to access session$request
delayedAssign('request', websocket$request, assign.env = session)
.write(toJSON(list(config = list(
workerId = .workerId,
sessionId = token
))))
},
onSessionEnded = function(callback) {
"Registers the given callback to be invoked when the session is closed
@@ -109,7 +155,7 @@ ShinySession <- setRefClass(
obs <- observe({
value <- try(func(), silent=FALSE)
value <- try(shinyCallingHandlers(func()), silent=FALSE)
.invalidatedOutputErrors$remove(name)
.invalidatedOutputValues$remove(name)
@@ -123,7 +169,7 @@ ShinySession <- setRefClass(
}
else
.invalidatedOutputValues$set(name, value)
}, label=label, suspended=.shouldSuspend(name))
}, suspended=.shouldSuspend(name), label=sprintf('output$%s <- %s', name, paste(label, collapse='\n')))
obs$onInvalidate(function() {
showProgress(name)
@@ -139,6 +185,9 @@ ShinySession <- setRefClass(
},
flushOutput = function() {
.flushCallbacks$invoke()
on.exit(.flushedCallbacks$invoke())
if (length(.progressKeys) == 0
&& length(.invalidatedOutputValues) == 0
&& length(.invalidatedOutputErrors) == 0
@@ -183,6 +232,8 @@ ShinySession <- setRefClass(
},
dispatch = function(msg) {
method <- paste('@', msg$method, sep='')
# we must use $ instead of [[ here at the moment; see
# https://github.com/rstudio/shiny/issues/274
func <- try(do.call(`$`, list(.self, method)), silent=TRUE)
if (inherits(func, 'try-error')) {
.sendErrorResponse(msg, paste('Unknown method', msg$method))
@@ -221,6 +272,28 @@ ShinySession <- setRefClass(
# Add to input message queue
.inputMessageQueue[[length(.inputMessageQueue) + 1]] <<- data
},
onFlush = function(func, once = TRUE) {
if (!isTRUE(once)) {
return(.flushCallbacks$register(func))
} else {
dereg <- .flushCallbacks$register(function() {
dereg()
func()
})
return(dereg)
}
},
onFlushed = function(func, once = TRUE) {
if (!isTRUE(once)) {
return(.flushedCallbacks$register(func))
} else {
dereg <- .flushedCallbacks$register(function() {
dereg()
func()
})
return(dereg)
}
},
.write = function(json) {
if (getOption('shiny.trace', FALSE))
message('SEND ',
@@ -245,7 +318,9 @@ ShinySession <- setRefClass(
jobId <- .fileUploadContext$createUploadOperation(fileInfos)
return(list(jobId=jobId,
uploadUrl=paste('session', token, 'upload', jobId, sep='/')))
uploadUrl=paste('session', token, 'upload',
paste(jobId, "?w=", .workerId,sep=""),
sep='/')))
},
`@uploadEnd` = function(jobId, inputId) {
fileData <- .fileUploadContext$getUploadOperation(jobId)$finish()
@@ -357,6 +432,18 @@ ShinySession <- setRefClass(
),
'Cache-Control'='no-cache')))
}
if (matches[2] == 'datatable') {
# /session/$TOKEN/datatable/$NAME
dlmatches <- regmatches(matches[3],
regexec("^([^/]+)(/[^/]+)?$",
matches[3]))[[1]]
dlname <- utils::URLdecode(dlmatches[2])
download <- downloads$get(dlname)
return(httpResponse(
200, 'application/json', dataTablesJSON(download$data, req$QUERY_STRING)
))
}
return(httpResponse(404, 'text/html', '<h1>Not Found</h1>'))
},
@@ -364,9 +451,10 @@ ShinySession <- setRefClass(
"Creates an entry in the file map for the data, and returns a URL pointing
to the file."
files$set(name, list(data=data, contentType=contentType))
return(sprintf('session/%s/file/%s?%s',
return(sprintf('session/%s/file/%s?w=%s&r=%s',
URLencode(token, TRUE),
URLencode(name, TRUE),
.workerId,
createUniqueId(8)))
},
# Send a file to the client
@@ -379,7 +467,7 @@ ShinySession <- setRefClass(
return(NULL)
fileData <- readBin(file, 'raw', n=bytes)
if (isTRUE(.clientData$.values$allowDataUriScheme)) {
b64 <- base64encode(fileData)
return(paste('data:', contentType, ';base64,', b64, sep=''))
@@ -392,9 +480,19 @@ ShinySession <- setRefClass(
downloads$set(name, list(filename = filename,
contentType = contentType,
func = func))
return(sprintf('session/%s/download/%s',
return(sprintf('session/%s/download/%s?w=%s',
URLencode(token, TRUE),
URLencode(name, TRUE)))
URLencode(name, TRUE),
.workerId))
},
# this can be more general registrations; not limited to data tables
registerDataTable = function(name, data) {
# abusing downloads at the moment
downloads$set(name, list(data = data))
return(sprintf('session/%s/datatable/%s?w=%s',
URLencode(token, TRUE),
URLencode(name, TRUE),
.workerId))
},
.getOutputOption = function(outputName, propertyName, defaultValue) {
opts <- .outputOptions[[outputName]]
@@ -583,7 +681,7 @@ httpResponse <- function(status = 200,
return(resp)
}
httpServer <- function(handlers) {
httpServer <- function(handlers, sharedSecret) {
handler <- joinHandlers(handlers)
# TODO: Figure out what this means after httpuv migration
@@ -592,6 +690,13 @@ httpServer <- function(handlers) {
filter <- function(req, response) response
function(req) {
if (!is.null(sharedSecret)
&& !identical(sharedSecret, req$HTTP_SHINY_SHARED_SECRET)) {
return(list(status=403,
body='<h1>403 Forbidden</h1><p>Shared secret mismatch</p>',
headers=list('Content-Type' = 'text/html')))
}
response <- handler(req)
if (is.null(response))
response <- httpResponse(404, content="<h1>Not Found</h1>")
@@ -632,6 +737,20 @@ joinHandlers <- function(handlers) {
}
}
reactLogHandler <- function(req) {
if (!identical(req$PATH_INFO, '/reactlog'))
return(NULL)
if (!getOption('shiny.reactlog', FALSE)) {
return(NULL)
}
return(httpResponse(
status=200,
content=list(file=renderReactLog(), owned=TRUE)
))
}
sessionHandler <- function(req) {
path <- req$PATH_INFO
if (is.null(path))
@@ -850,6 +969,13 @@ resourcePathHandler <- function(req) {
#' @export
shinyServer <- function(func) {
.globals$server <- func
if (!is.null(func))
{
# Tag this function as the Shiny server function. A debugger may use this
# tag to give this function special treatment.
attr(.globals$server, "shinyServerFunction") <- TRUE
registerDebugHook("server", .globals, "Server Function")
}
invisible()
}
@@ -910,7 +1036,7 @@ file.path.ci <- function(dir, name) {
# Instantiates the app in the current working directory.
# port - The TCP port that the application should listen on.
startAppDir <- function(port=8101L) {
startAppDir <- function(port=8101L, workerId) {
globalR <- file.path.ci(getwd(), 'global.R')
uiR <- file.path.ci(getwd(), 'ui.R')
serverR <- file.path.ci(getwd(), 'server.R')
@@ -946,11 +1072,12 @@ startAppDir <- function(port=8101L) {
startApp(
c(dynamicHandler(uiR), wwwDir),
serverFuncSource,
port
port,
workerId
)
}
startAppObj <- function(ui, serverFunc, port) {
startAppObj <- function(ui, serverFunc, port, workerId) {
uiHandler <- function(req) {
if (!identical(req$REQUEST_METHOD, 'GET'))
return(NULL)
@@ -968,13 +1095,18 @@ startAppObj <- function(ui, serverFunc, port) {
startApp(uiHandler,
function() { serverFunc },
port)
port, workerId)
}
startApp <- function(httpHandlers, serverFuncSource, port) {
startApp <- function(httpHandlers, serverFuncSource, port, workerId) {
sys.www.root <- system.file('www', package='shiny')
# This value, if non-NULL, must be present on all HTTP and WebSocket
# requests as the Shiny-Shared-Secret header or else access will be
# denied (403 response for HTTP, and instant close for websocket).
sharedSecret <- getOption('shiny.sharedSecret', NULL)
httpuvCallbacks <- list(
onHeaders = function(req) {
maxSize <- getOption('shiny.maxRequestSize', 5 * 1024 * 1024)
@@ -1001,9 +1133,15 @@ startApp <- function(httpHandlers, serverFuncSource, port) {
call = httpServer(c(sessionHandler,
httpHandlers,
sys.www.root,
resourcePathHandler)),
resourcePathHandler,
reactLogHandler), sharedSecret),
onWSOpen = function(ws) {
shinysession <- ShinySession$new(ws)
if (!is.null(sharedSecret)
&& !identical(sharedSecret, ws$request$HTTP_SHINY_SHARED_SECRET)) {
ws$close()
}
shinysession <- ShinySession$new(ws, workerId)
appsByToken$set(shinysession$token, shinysession)
ws$onMessage(function(binary, msg) {
@@ -1089,7 +1227,28 @@ startApp <- function(httpHandlers, serverFuncSource, port) {
shinysession$dispatch(msg)
)
shinysession$manageHiddenOutputs()
flushReact()
if (exists(".shiny__stdout", globalenv()) &&
exists("HTTP_GUID", ws$request)) {
# safe to assume we're in shiny-server
shiny_stdout <- get(".shiny__stdout", globalenv())
# eNter a flushReact
writeLines(paste("_n_flushReact ", get("HTTP_GUID", ws$request),
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
sep=""), con=shiny_stdout)
flush(shiny_stdout)
flushReact()
# eXit a flushReact
writeLines(paste("_x_flushReact ", get("HTTP_GUID", ws$request),
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
sep=""), con=shiny_stdout)
flush(shiny_stdout)
} else {
flushReact()
}
lapply(appsByToken$values(), function(shinysession) {
shinysession$flushOutput()
NULL
@@ -1113,12 +1272,9 @@ startApp <- function(httpHandlers, serverFuncSource, port) {
}
}
# NOTE: we de-roxygenized this comment because the function isn't exported
# Run an application that was created by \code{\link{startApp}}. This
# function should normally be called in a \code{while(TRUE)} loop.
#
# @param ws_env The return value from \code{\link{startApp}}.
serviceApp <- function(ws_env) {
serviceApp <- function() {
if (timerCallbacks$executeElapsed()) {
for (shinysession in appsByToken$values()) {
shinysession$manageHiddenOutputs()
@@ -1150,10 +1306,13 @@ serviceApp <- function(ws_env) {
#' \code{server.R}, plus, either \code{ui.R} or a \code{www} directory that
#' contains the file \code{index.html}. Defaults to the working directory.
#' @param port The TCP port that the application should listen on. Defaults to
#' port 8100.
#' choosing a random port.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
#' interactive sessions only. This value of this parameter can also be a
#' function to call with the application's URL.
#' @param workerId Can generally be ignored. Exists to help some editions of
#' Shiny Server Pro route requests to the correct process.
#'
#' @examples
#' \dontrun{
@@ -1177,14 +1336,15 @@ serviceApp <- function(ws_env) {
#' }
#' @export
runApp <- function(appDir=getwd(),
port=8100L,
port=NULL,
launch.browser=getOption('shiny.launch.browser',
interactive())) {
interactive()),
workerId="") {
# Make warnings print immediately
ops <- options(warn = 1)
on.exit(options(ops))
if (nzchar(Sys.getenv('SHINY_PORT'))) {
# If SHINY_PORT is set, we're running under Shiny Server. Check the version
# to make sure it is compatible. Older versions of Shiny Server don't set
@@ -1198,34 +1358,83 @@ runApp <- function(appDir=getwd(),
}
require(shiny)
# determine port if we need to
if (is.null(port)) {
# Try up to 20 random ports. If we don't succeed just plow ahead
# with the final value we tried, and let the "real" startServer
# somewhere down the line fail and throw the error to the user.
#
# If we (think we) succeed, save the value as .globals$lastPort,
# and try that first next time the user wants a random port.
for (i in 1:20) {
if (!is.null(.globals$lastPort)) {
port <- .globals$lastPort
.globals$lastPort <- NULL
}
else {
# Try up to 20 random ports
port <- round(runif(1, min=3000, max=8000))
}
# Test port to see if we can use it
tmp <- try(startServer('0.0.0.0', port, list()), silent=TRUE)
if (!is(tmp, 'try-error')) {
stopServer(tmp)
.globals$lastPort <- port
break
}
}
}
if (is.character(appDir)) {
orig.wd <- getwd()
setwd(appDir)
on.exit(setwd(orig.wd), add = TRUE)
server <- startAppDir(port=port)
server <- startAppDir(port=port, workerId)
} else {
server <- startAppObj(appDir$ui, appDir$server, port=port)
server <- startAppObj(appDir$ui, appDir$server, port=port, workerId)
}
on.exit({
stopServer(server)
}, add = TRUE)
if (launch.browser && !is.character(port)) {
if (!is.character(port)) {
appUrl <- paste("http://localhost:", port, sep="")
utils::browseURL(appUrl)
if (is.function(launch.browser))
launch.browser(appUrl)
else if (launch.browser)
utils::browseURL(appUrl)
}
tryCatch(
while (TRUE) {
.globals$retval <- NULL
.globals$stopped <- FALSE
shinyCallingHandlers(
while (!.globals$stopped) {
serviceApp()
Sys.sleep(0.001)
},
finally = {
timerCallbacks$clear()
}
)
return(.globals$retval)
}
#' Stop the currently running Shiny app
#'
#' Stops the currently running Shiny app, returning control to the caller of
#' \code{\link{runApp}}.
#'
#' @param returnValue The value that should be returned from
#' \code{\link{runApp}}.
#'
#' @export
stopApp <- function(returnValue = NULL) {
.globals$retval <- returnValue
.globals$stopped <- TRUE
httpuv::interrupt()
}
#' Run Shiny Example Applications
@@ -1235,7 +1444,7 @@ runApp <- function(appDir=getwd(),
#' @param example The name of the example to run, or \code{NA} (the default) to
#' list the available examples.
#' @param port The TCP port that the application should listen on. Defaults to
#' port 8100.
#' choosing a random port.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
@@ -1253,7 +1462,7 @@ runApp <- function(appDir=getwd(),
#' }
#' @export
runExample <- function(example=NA,
port=8100L,
port=NULL,
launch.browser=getOption('shiny.launch.browser',
interactive())) {
examplesDir <- system.file('examples', package='shiny')

View File

@@ -47,6 +47,28 @@ 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)
@@ -54,13 +76,15 @@ includeHTML <- function(path) {
return(HTML(paste(lines, collapse='\r\n')))
}
#' @rdname include
#' @export
includeText <- function(path) {
dependsOnFile(path)
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
return(HTML(paste(lines, collapse='\r\n')))
return(paste(lines, collapse='\r\n'))
}
#' @rdname include
#' @export
includeMarkdown <- function(path) {
if (!require(markdown))
@@ -72,6 +96,27 @@ includeMarkdown <- function(path) {
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
#'

View File

@@ -2,6 +2,7 @@ suppressPackageStartupMessages({
library(caTools)
library(xtable)
})
globalVariables('func')
#' Plot Output
#'
@@ -11,6 +12,9 @@ suppressPackageStartupMessages({
#' 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.
@@ -39,10 +43,9 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
if (!is.null(func)) {
shinyDeprecated(msg="renderPlot: argument 'func' is deprecated. Please use 'expr' instead.")
} else {
func <- exprToFunction(expr, env, quoted)
installExprFunction(expr, "func", env, quoted)
}
args <- list(...)
if (is.function(width))
@@ -77,15 +80,53 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
pixelratio <- shinysession$clientData$pixelratio
if (is.null(pixelratio))
pixelratio <- 1
coordmap <- NULL
plotFunc <- function() {
# Actually perform the plotting
func()
outfile <- do.call(plotPNG, c(func, width=width*pixelratio,
# Now capture some graphics device info before we close it
usrCoords <- par('usr')
usrBounds <- usrCoords
if (par('xlog')) {
usrBounds[c(1,2)] <- 10 ^ usrBounds[c(1,2)]
}
if (par('ylog')) {
usrBounds[c(3,4)] <- 10 ^ usrBounds[c(3,4)]
}
coordmap <<- list(
usr = c(
left = usrCoords[1],
right = usrCoords[2],
bottom = usrCoords[3],
top = usrCoords[4]
),
# The bounds of the plot area, in DOM pixels
bounds = c(
left = grconvertX(usrBounds[1], 'user', 'nfc') * width,
right = grconvertX(usrBounds[2], 'user', 'nfc') * width,
bottom = (1-grconvertY(usrBounds[3], 'user', 'nfc')) * height,
top = (1-grconvertY(usrBounds[4], 'user', 'nfc')) * height
),
log = c(
x = par('xlog'),
y = par('ylog')
),
pixelratio = pixelratio
)
}
outfile <- do.call(plotPNG, c(plotFunc, width=width*pixelratio,
height=height*pixelratio, res=res*pixelratio, args))
on.exit(unlink(outfile))
# Return a list of attributes for the img
return(list(
src=shinysession$fileUrl(name, outfile, contentType='image/png'),
width=width, height=height))
width=width, height=height, coordmap=coordmap
))
})
}
@@ -109,12 +150,15 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
#' 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
#' it is sent to the client browser? Generally speaking, if the image is a
#' temp file generated within \code{func}, then this should be \code{TRUE};
#' if the image is not a temp file, this should be \code{FALSE}.
#'
@@ -176,8 +220,8 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
#' }
renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
deleteFile=TRUE) {
func <- exprToFunction(expr, env, quoted)
installExprFunction(expr, "func", env, quoted)
return(function(shinysession, name, ...) {
imageinfo <- func()
# Should the file be deleted after being sent? If .deleteFile not set or if
@@ -226,7 +270,7 @@ 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)
installExprFunction(expr, "func", env, quoted)
}
function() {
@@ -283,7 +327,7 @@ 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)
installExprFunction(expr, "func", env, quoted)
}
function() {
@@ -326,7 +370,7 @@ 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)
installExprFunction(expr, "func", env, quoted)
}
function() {
@@ -366,7 +410,7 @@ 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)
installExprFunction(expr, "func", env, quoted)
}
function() {
@@ -425,6 +469,32 @@ downloadHandler <- function(filename, content, contentType=NA) {
})
}
#' Table output with the JavaScript library DataTables
#'
#' Makes a reactive version of the given function that returns a data frame (or
#' matrix), which will be rendered with the DataTables library. Paging,
#' searching, filtering, and sorting can be done on the R side using Shiny as
#' the server infrastructure.
#' @param expr An expression that returns a data frame or a matrix.
#' @param options A list of initialization options to be passed to DataTables.
#' @param searchDelay The delay for searching, in milliseconds (to avoid too
#' frequent search requests).
#' @references \url{http://datatables.net}
#' @export
#' @inheritParams renderPlot
renderDataTable <- function(expr, options = NULL, searchDelay = 500,
env=parent.frame(), quoted=FALSE) {
installExprFunction(expr, "func", env, quoted)
function(shinysession, name, ...) {
data <- func()
if (length(dim(data)) != 2) return() # expects a rectangular data object
action <- shinysession$registerDataTable(name, data)
list(colnames = colnames(data), action = action, options = options,
searchDelay = searchDelay)
}
}
# Deprecated functions ------------------------------------------------------

View File

@@ -43,7 +43,7 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
# validate numeric inputs
if (!is.numeric(value) || !is.numeric(min) || !is.numeric(max))
stop("min, max, amd value must all be numeric values")
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"))

115
R/tags.R
View File

@@ -94,70 +94,41 @@ tagAppendChild <- function(tag, 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) {
# create basic tag data structure
tag <- list()
class(tag) <- "shiny.tag"
tag$name <- `_tag_name`
tag$attribs <- list()
tag$children <- list()
# process varArgs
# Get arg names; if not a named list, use vector of empty strings
varArgsNames <- names(varArgs)
if (is.null(varArgsNames))
varArgsNames <- character(length=length(varArgs))
# Named arguments become attribs, dropping NULL values
named_idx <- nzchar(varArgsNames)
attribs <- dropNulls(varArgs[named_idx])
if (length(varArgsNames) > 0) {
for (i in 1:length(varArgsNames)) {
# save name and value
name <- varArgsNames[[i]]
value <- varArgs[[i]]
# process attribs
if (nzchar(name))
tag$attribs[[name]] <- value
# process child tags
else if (isTag(value)) {
tag$children[[length(tag$children)+1]] <- value
}
# recursively process lists of children
else if (is.list(value)) {
tagAppendChildren <- function(tag, children) {
for(child in children) {
if (isTag(child))
tag <- tagAppendChild(tag, child)
else if (is.list(child))
tag <- tagAppendChildren(tag, child)
else if (is.character(child))
tag <- tagAppendChild(tag, child)
else
tag <- tagAppendChild(tag, as.character(child))
}
return (tag)
}
tag <- tagAppendChildren(tag, value)
}
# add text
else if (is.character(value)) {
tag <- tagAppendChild(tag, value)
}
# everything else treated as text
else {
tag <- tagAppendChild(tag, as.character(value))
}
}
}
# return the tag
return (tag)
# 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") {
@@ -401,3 +372,31 @@ HTML <- function(text, ...) {
withTags <- function(code) {
eval(substitute(code), envir = as.list(tags), enclos = parent.frame())
}
# Given a list of tags, lists, and other items, return a flat list, where the
# items from the inner, nested lists are pulled to the top level, recursively.
flattenTags <- function(x) {
if (isTag(x)) {
# For tags, wrap them into a list (which will be unwrapped by caller)
list(x)
} else if (is.list(x)) {
if (length(x) == 0) {
# Empty lists are simply returned
x
} else {
# For items that are lists (but not tags), recurse
unlist(lapply(x, flattenTags), recursive = FALSE)
}
} else if (is.character(x)){
# This will preserve attributes if x is a character with attribute,
# like what HTML() produces
list(x)
} else {
# For other items, coerce to character and wrap them into a list (which
# will be unwrapped by caller). Note that this will strip attributes.
list(as.character(x))
}
}

View File

@@ -293,17 +293,15 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL,
choices = NULL, selected = NULL) {
choices <- choicesWithNames(choices)
options <- list()
for (i in seq_along(choices)) {
choiceName <- names(choices)[i]
opt <- list(value = choices[[i]],
label = choiceName,
checked = choiceName %in% selected)
options[[i]] <- opt
}
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))
@@ -391,17 +389,15 @@ updateSelectInput <- function(session, inputId, label = NULL, choices = NULL,
selected = NULL) {
choices <- choicesWithNames(choices)
options <- list()
for (i in seq_along(choices)) {
choiceName <- names(choices)[i]
opt <- list(value = choices[[i]],
label = choiceName,
selected = choiceName %in% selected)
options[[i]] <- opt
}
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))

145
R/utils.R
View File

@@ -36,7 +36,7 @@ repeatable <- function(rngfunc, seed = runif(1, 0, .Machine$integer.max)) {
set.seed(seed)
do.call(rngfunc, list(...))
rngfunc(...)
}
}
@@ -122,7 +122,7 @@ makeFunction <- function(args = pairlist(), body, env = parent.frame()) {
#'
#' 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.
#' 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.
#
@@ -130,6 +130,8 @@ makeFunction <- function(args = pairlist(), body, env = parent.frame()) {
#' @param env The desired environment for the function. Defaults to the
#' calling environment two steps back.
#' @param quoted Is the expression quoted?
#' @param caller_offset If specified, the offset in the callstack of the
#' functiont to be treated as the caller.
#'
#' @examples
#' # Example of a new renderer, similar to renderText
@@ -165,9 +167,10 @@ makeFunction <- function(args = pairlist(), body, env = parent.frame()) {
#' # "text, text, text"
#'
#' @export
exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE) {
exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE,
caller_offset=1) {
# Get the quoted expr from two calls back
expr_sub <- eval(substitute(substitute(expr)), parent.frame())
expr_sub <- eval(substitute(substitute(expr)), parent.frame(caller_offset))
# Check if expr is a function, making sure not to evaluate expr, in case it
# is actually an unquoted expression.
@@ -176,8 +179,8 @@ exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE) {
# 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]]
called_fun <- sys.call(-1 * caller_offset)[[1]]
shinyDeprecated(msg = paste("Passing functions to '", called_fun,
"' is deprecated. Please use expressions instead. See ?", called_fun,
" for more information.", sep=""))
@@ -193,6 +196,36 @@ exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE) {
}
}
#' Installs an expression in the given environment as a function, and registers
#' debug hooks so that breakpoints may be set in the function.
#'
#' This function can replace \code{exprToFunction} as follows: we may use
#' \code{func <- exprToFunction(expr)} if we do not want the debug hooks, or
#' \code{installExprFunction(expr, "func")} if we do. Both approaches create a
#' function named \code{func} in the current environment.
#'
#' @seealso Wraps \code{exprToFunction}; see that method's documentation for
#' more documentation and examples.
#'
#' @param expr A quoted or unquoted expression
#' @param name The name the function should be given
#' @param eval.env The desired environment for the function. Defaults to the
#' calling environment two steps back.
#' @param quoted Is the expression quoted?
#' @param assign.env The environment in which the function should be assigned.
#' @param label A label for the object to be shown in the debugger. Defaults
#' to the name of the calling function.
#'
#' @export
installExprFunction <- function(expr, name, eval.env = parent.frame(2),
quoted = FALSE,
assign.env = parent.frame(1),
label = as.character(sys.call(-1)[[1]])) {
func <- exprToFunction(expr, eval.env, quoted, 2)
assign(name, func, envir = assign.env)
registerDebugHook(name, assign.env, label)
}
#' Parse a GET query string from a URL
#'
#' Returns a named character vector of key-value pairs.
@@ -249,6 +282,15 @@ parseQueryString <- function(str) {
setNames(as.list(values), keys)
}
# decide what to do in case of errors; it is customizable using the shiny.error
# option (e.g. we can set options(shiny.error = recover))
shinyCallingHandlers <- function(expr) {
withCallingHandlers(expr, error = function(e) {
handle <- getOption('shiny.error')
if (is.function(handle)) handle()
})
}
#' Print message for deprecated functions in Shiny
#'
#' To disable these messages, use \code{options(shiny.deprecation.messages=FALSE)}.
@@ -272,6 +314,28 @@ shinyDeprecated <- function(new=NULL, msg=NULL,
message(msg)
}
#' Register a function with the debugger (if one is active).
#'
#' Call this function after exprToFunction to give any active debugger a hook
#' to set and clear breakpoints in the function. A debugger may implement
#' registerShinyDebugHook to receive callbacks when Shiny functions are
#' instantiated at runtime.
#'
#' @param name Name of the field or object containing the function.
#' @param where The reference object or environment containing the function.
#' @param label A label to display on the function in the debugger.
#' @noRd
registerDebugHook <- function(name, where, label) {
if (exists("registerShinyDebugHook", mode = "function")) {
registerShinyDebugHook <- get("registerShinyDebugHook", mode = "function")
params <- new.env(parent = emptyenv())
params$name <- name
params$where <- where
params$label <- label
registerShinyDebugHook(params)
}
}
Callbacks <- setRefClass(
'Callbacks',
fields = list(
@@ -292,19 +356,68 @@ Callbacks <- setRefClass(
},
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)
}
)
if (is.null(onError)) {
callback(...)
} else {
tryCatch(callback(...), error = onError)
}
}
},
count = function() {
.callbacks$size()
}
)
)
)
# convert a data frame to JSON as required by DataTables request
dataTablesJSON <- function(data, query) {
n <- nrow(data)
with(parseQueryString(query), {
# global searching
i <- seq_len(n)
if (nzchar(sSearch)) {
i0 <- apply(data, 2, function(x) grep(sSearch, as.character(x)))
i <- intersect(i, unique(unlist(i0)))
}
# search by columns
if (length(i)) for (j in seq_len(as.integer(iColumns)) - 1) {
if (is.null(k <- get_exists(sprintf('sSearch_%d', j), 'character'))) next
if (nzchar(k)) i <- intersect(grep(k, as.character(data[, j + 1])), i)
if (length(i) == 0) break
}
if (length(i) != n) data <- data[i, , drop = FALSE]
# sorting
oList <- list()
for (j in seq_len(as.integer(iSortingCols)) - 1) {
if (is.null(k <- get_exists(sprintf('iSortCol_%d', j), 'character'))) break
desc = get_exists(sprintf('sSortDir_%d', j), 'character')
if (is.character(desc)) {
col <- data[, as.integer(k) + 1]
oList[[length(oList) + 1]] <- (if (desc == 'asc') identity else `-`)(
if (is.numeric(col)) col else xtfrm(col)
)
}
}
if (length(oList)) {
i <- do.call(order, oList)
data <- data[i, , drop = FALSE]
}
# paging
i <- seq(as.integer(iDisplayStart) + 1L, length.out = as.integer(iDisplayLength))
i <- i[i <= n]
fdata <- data[i, , drop = FALSE] # filtered data
fdata <- unname(as.matrix(fdata))
toJSON(list(
sEcho = as.integer(sEcho),
iTotalRecords = n,
iTotalDisplayRecords = nrow(data),
aaData = fdata
))
})
}
get_exists = function(x, mode) {
if (exists(x, envir = parent.frame(), mode = mode, inherits = FALSE))
get(x, envir = parent.frame(), mode = mode, inherits = FALSE)
}

View File

@@ -13,23 +13,31 @@ For an introduction and examples, visit the [Shiny homepage](http://www.rstudio.
* 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.
* Fast bidirectional communication between the web browser and R using the [httpuv](https://github.com/rstudio/httpuv) package.
* Uses a [reactive](http://en.wikipedia.org/wiki/Reactive_programming) programming model that eliminates messy event handling code, so you can focus on the code that really matters.
* Develop and redistribute your own Shiny widgets that other developers can easily drop into their own applications (coming soon!).
## Installation
From an R console:
To install the stable version from CRAN, simply run the following from an R console:
```r
install.packages("shiny")
```
To install the latest development builds directly from GitHub, run this instead:
```r
if (!require("devtools"))
install.packages("devtools")
devtools::install_github("shiny", "rstudio")
```
## Getting Started
To learn more we highly recommend you check out the [Shiny Tutorial](http://rstudio.github.com/shiny/tutorial). The tutorial explains the framework in-depth, walks you through building a simple application, and includes extensive annotated examples.
We hope you enjoy using Shiny. As you learn more and work with the package please [let us know](https://github.com/rstudio/shiny/issues) what problems you encounter and how you'd like to see Shiny evolve.
We hope you enjoy using Shiny. If you have general questions about using Shiny, please use the Shiny [mailing list](https://groups.google.com/forum/#!forum/shiny-discuss). For bug reports, please use the [issue tracker](https://github.com/rstudio/shiny/issues).
## License

View File

@@ -6,12 +6,12 @@ these components are included below):
- Bootstrap
- bootstrap-datepicker, from https://github.com/eternicode/bootstrap-datepicker
- jslider
- DataTables
jQuery License
----------------------------------------------------------------------
----------------------------------------------------------------------
Copyright (c) 2012 jQuery Foundation and other contributors,
Copyright (c) 2012 jQuery Foundation and other contributors,
http://jquery.com/
Permission is hereby granted, free of charge, to any person obtaining
@@ -35,7 +35,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Bootstrap and bootstrap-datepicker License
----------------------------------------------------------------------
----------------------------------------------------------------------
Apache License
Version 2.0, January 2004
@@ -241,7 +241,7 @@ Bootstrap and bootstrap-datepicker License
jslider License
----------------------------------------------------------------------
----------------------------------------------------------------------
The MIT License (MIT)
Copyright (c) 2012 Egor Khmelev
@@ -263,3 +263,35 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
DataTables License
----------------------------------------------------------------------
Copyright (c) 2008-2010, Allan Jardine
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Allan Jardine nor SpryMedia UK may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -10,7 +10,7 @@ shinyUI(pageWithSidebar(
sidebarPanel(
sliderInput("obs",
"Number of observations:",
min = 0,
min = 1,
max = 1000,
value = 500)
),

View File

@@ -4,7 +4,7 @@ shinyUI(pageWithSidebar(
headerPanel("Uploading Files"),
sidebarPanel(
fileInput('file1', 'Choose CSV File',
accept=c('text/csv', 'text/comma-separated-values,text/plain')),
accept=c('text/csv', 'text/comma-separated-values,text/plain', '.csv')),
tags$hr(),
checkboxInput('header', 'Header', TRUE),
radioButtons('sep', 'Separator',

View File

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

View File

@@ -663,3 +663,35 @@ test_that("Observer priorities are respected", {
expect_identical(results, c(30, 20, 21, 22, 10))
})
test_that("reactivePoll and reactiveFileReader", {
path <- tempfile('file')
on.exit(unlink(path))
write.csv(cars, file=path, row.names=FALSE)
rfr <- reactiveFileReader(100, NULL, path, read.csv)
expect_equal(isolate(rfr()), cars)
write.csv(rbind(cars, cars), file=path, row.names=FALSE)
Sys.sleep(0.15)
timerCallbacks$executeElapsed()
expect_equal(isolate(rfr()), cars)
flushReact()
expect_equal(isolate(rfr()), rbind(cars, cars))
})
test_that("classes of reactive object", {
v <- reactiveValues(a = 1)
r <- reactive({ v$a + 1 })
o <- observe({ print(r()) })
expect_false(is.reactivevalues(12))
expect_true(is.reactivevalues(v))
expect_false(is.reactivevalues(r))
expect_false(is.reactivevalues(o))
expect_false(is.reactive(12))
expect_false(is.reactive(v))
expect_true(is.reactive(r))
expect_false(is.reactive(o))
})

View File

@@ -48,3 +48,253 @@ test_that("withTags works", {
}
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)
})

View File

@@ -0,0 +1,818 @@
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400,600' rel='stylesheet' type='text/css'>
<style type="text/css">
html, body {
font-family: 'Source Sans Pro', sans-serif;
font-weight: 400;
overflow: hidden;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
div {
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
cursor: default;
}
#instructions, #ended {
position: relative;
font-weight: 200;
color: #444;
top: 20px;
font-size: 30px;
text-align: center;
}
#ended strong {
font-weight: 600;
}
svg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.node {
cursor: pointer;
}
.node text {
font-family: 'Source Code Pro', monospace;
font-weight: normal;
text-anchor: start;
fill: #999;
user-select: none;
transition: fill 0.75s ease;
}
.node.running text {
fill: black;
}
.node.changed text {
fill: red;
}
.node text tspan {
white-space: pre;
}
.node path {
fill: white;
stroke: #777;
stroke-width: 7.5px;
transition: fill 0.75s ease;
}
.node.observer path {
}
.node.observable path {
}
.node.value path {
}
.node.invalidated path {
fill: #E0E0E0;
/*fill: url(#diagonalHatch);*/
}
.node.running path {
fill: #61B97E;
}
#legend {
font-size: 22px;
position: fixed;
bottom: 10px;
right: 20px;
}
.color {
display: inline-block;
border: 1px solid #777;
height: 14px;
width: 14px;
}
.color.normal {
background-color: #white;
}
.color.invalidated {
background-color: #E0E0E0;
}
.color.running {
background-color: #61B97E;
}
#triangle {
fill: #CCC;
}
.link {
fill: none;
stroke: #CCC;
stroke-width: 0.5px;
}
#description {
position: fixed;
width: 300px;
left: 630px;
top: 36px;
height: auto;
display: none;
}
#timeline {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 20px;
transition: height 500ms;
}
#timeline, #timeline * {
cursor: pointer;
}
#timeline:hover {
height: 32px;
}
#timeline-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 12px;
background-color: silver;
}
#timeline-fill {
background-color: #28A3F2;
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 0;
transition: width 500ms;
}
</style>
<script>
var log = [
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr \"dataset\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 1\n $ dataset: chr \"rock\"" },
{ "action" : "valueChange", "id" : "input$dataset", "value" : " chr \"rock\"" },
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:2] \"caption\" \"dataset\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 2\n $ caption: chr \"Data Summary\"\n $ dataset: chr \"rock\"" },
{ "action" : "valueChange", "id" : "input$caption", "value" : " chr \"Data Summary\"" },
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Data Summary\"\n $ obs : num 10\n $ dataset: chr \"rock\"" },
{ "action" : "valueChange", "id" : "input$obs", "value" : " num 10" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr \"output_caption_hidden\"" },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 1\n $ output_caption_hidden: logi FALSE" },
{ "action" : "valueChange", "id" : "clientData$output_caption_hidden", "value" : " logi FALSE" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:2] \"output_caption_hidden\" \"output_summary_hidden\"" },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 2\n $ output_caption_hidden: logi FALSE\n $ output_summary_hidden: logi FALSE" },
{ "action" : "valueChange", "id" : "clientData$output_summary_hidden", "value" : " logi FALSE" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:3] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\"" },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 3\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ output_summary_hidden: logi FALSE" },
{ "action" : "valueChange", "id" : "clientData$output_view_hidden", "value" : " logi FALSE" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:4] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 4\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ output_summary_hidden: logi FALSE" },
{ "action" : "valueChange", "id" : "clientData$pixelratio", "value" : " num 2" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:5] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 5\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_protocol", "value" : " chr \"http:\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:6] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 6\n $ output_view_hidden : logi FALSE\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_hostname", "value" : " chr \"localhost\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:7] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 7\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_port", "value" : " chr \"8100\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:8] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 8\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_pathname", "value" : " chr \"/\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:9] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 9\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_search : chr \"\"\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_search", "value" : " chr \"\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:10] \"output_caption_hidden\" \"output_summary_hidden\" \"output_view_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 10\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ url_hash_initial : chr \"\"\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_search : chr \"\"\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$url_hash_initial", "value" : " chr \"\"" },
{ "action" : "valueChange", "id" : "names(clientData)", "value" : " chr [1:11] \"allowDataUriScheme\" \"output_caption_hidden\" \"output_summary_hidden\" ..." },
{ "action" : "valueChange", "id" : "clientData (all)", "value" : "List of 11\n $ allowDataUriScheme : logi TRUE\n $ url_pathname : chr \"/\"\n $ output_view_hidden : logi FALSE\n $ url_port : chr \"8100\"\n $ output_caption_hidden: logi FALSE\n $ url_hash_initial : chr \"\"\n $ pixelratio : num 2\n $ url_hostname : chr \"localhost\"\n $ output_summary_hidden: logi FALSE\n $ url_search : chr \"\"\n $ url_protocol : chr \"http:\"" },
{ "action" : "valueChange", "id" : "clientData$allowDataUriScheme", "value" : " logi TRUE" },
{ "action" : "ctx", "id" : "1", "label" : "output$caption <- renderText({ \n input$caption\n})", "type" : "observer", "prevId" : "" },
{ "action" : "invalidate", "id" : "1" },
{ "action" : "ctx", "id" : "2", "label" : "output$summary <- renderPrint({ \n dataset <- datasetInput()\n summary(dataset)\n})", "type" : "observer", "prevId" : "" },
{ "action" : "invalidate", "id" : "2" },
{ "action" : "ctx", "id" : "3", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "" },
{ "action" : "invalidate", "id" : "3" },
{ "action" : "ctx", "id" : "4", "label" : "output$caption <- renderText({ \n input$caption\n})", "type" : "observer", "prevId" : "1" },
{ "action" : "enter", "id" : "4" },
{ "action" : "dep", "id" : "4", "dependsOn" : "input$caption" },
{ "action" : "exit", "id" : "4" },
{ "action" : "ctx", "id" : "5", "label" : "output$summary <- renderPrint({ \n dataset <- datasetInput()\n summary(dataset)\n})", "type" : "observer", "prevId" : "2" },
{ "action" : "enter", "id" : "5" },
{ "action" : "ctx", "id" : "6", "label" : "reactive({ \n switch(input$dataset, rock = rock, pressure = pressure, cars = cars)\n})", "type" : "observable", "prevId" : "" },
{ "action" : "enter", "id" : "6" },
{ "action" : "dep", "id" : "6", "dependsOn" : "input$dataset" },
{ "action" : "exit", "id" : "6" },
{ "action" : "depId", "id" : "5", "dependsOn" : "6" },
{ "action" : "exit", "id" : "5" },
{ "action" : "ctx", "id" : "7", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "3" },
{ "action" : "enter", "id" : "7" },
{ "action" : "depId", "id" : "7", "dependsOn" : "6" },
{ "action" : "dep", "id" : "7", "dependsOn" : "input$obs" },
{ "action" : "exit", "id" : "7" },
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Pressure Summary\"\n $ obs : num 10\n $ dataset: chr \"rock\"" },
{ "action" : "valueChange", "id" : "input$caption", "value" : " chr \"Pressure Summary\"" },
{ "action" : "invalidate", "id" : "4" },
{ "action" : "ctx", "id" : "8", "label" : "output$caption <- renderText({ \n input$caption\n})", "type" : "observer", "prevId" : "4" },
{ "action" : "enter", "id" : "8" },
{ "action" : "dep", "id" : "8", "dependsOn" : "input$caption" },
{ "action" : "exit", "id" : "8" },
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Pressure Summary\"\n $ obs : num 10\n $ dataset: chr \"pressure\"" },
{ "action" : "valueChange", "id" : "input$dataset", "value" : " chr \"pressure\"" },
{ "action" : "invalidate", "id" : "6" },
{ "action" : "invalidate", "id" : "5" },
{ "action" : "invalidate", "id" : "7" },
{ "action" : "ctx", "id" : "9", "label" : "output$summary <- renderPrint({ \n dataset <- datasetInput()\n summary(dataset)\n})", "type" : "observer", "prevId" : "5" },
{ "action" : "enter", "id" : "9" },
{ "action" : "ctx", "id" : "10", "label" : "reactive({ \n switch(input$dataset, rock = rock, pressure = pressure, cars = cars)\n})", "type" : "observable", "prevId" : "6" },
{ "action" : "enter", "id" : "10" },
{ "action" : "dep", "id" : "10", "dependsOn" : "input$dataset" },
{ "action" : "exit", "id" : "10" },
{ "action" : "depId", "id" : "9", "dependsOn" : "10" },
{ "action" : "exit", "id" : "9" },
{ "action" : "ctx", "id" : "11", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "7" },
{ "action" : "enter", "id" : "11" },
{ "action" : "depId", "id" : "11", "dependsOn" : "10" },
{ "action" : "dep", "id" : "11", "dependsOn" : "input$obs" },
{ "action" : "exit", "id" : "11" },
{ "action" : "valueChange", "id" : "names(input)", "value" : " chr [1:3] \"caption\" \"dataset\" \"obs\"" },
{ "action" : "valueChange", "id" : "input (all)", "value" : "List of 3\n $ caption: chr \"Pressure Summary\"\n $ obs : num 15\n $ dataset: chr \"pressure\"" },
{ "action" : "valueChange", "id" : "input$obs", "value" : " num 15" },
{ "action" : "invalidate", "id" : "11" },
{ "action" : "ctx", "id" : "12", "label" : "output$view <- renderTable({ \n head(datasetInput(), n = input$obs)\n})", "type" : "observer", "prevId" : "11" },
{ "action" : "enter", "id" : "12" },
{ "action" : "depId", "id" : "12", "dependsOn" : "10" },
{ "action" : "dep", "id" : "12", "dependsOn" : "input$obs" },
{ "action" : "exit", "id" : "12" }
];
try {
log = __DATA__;
} catch (e) {}
var nodes = {};
var nodeList = [];
var nodeSelection = null;
var links = [];
var linkSelection = null;
var node, link; // d3 selections
var MAX_LINES = 6;
var force = d3.layout.force()
.charge(-100)
.nodes(nodeList)
.links(links);
force.on('tick', onTick);
function pathDataForNode(node) {
switch (node.type) {
case 'observer':
return 'M -25,-50 c -75,0 -75,100 0,100 l 100,0 l 0,-100 Z';
case 'observable':
return 'M -25,-50 c -75,0 -75,100 0,100 l 60,0 l 50,-50 l -50,-50 Z';
case 'value':
return 'M -50,-50 l 0,100 l 100,0 l 50,-50 l -50,-50 Z';
}
}
function getSourceCoords(node) {
switch (node.type) {
case 'observer':
case 'observable':
return {x: node.x - 5, y: node.y};
default:
return {x: node.x, y: node.y};
}
}
function getTargetCoords(node) {
switch (node.type) {
case 'observable':
return {x: node.x + 7, y: node.y};
case 'value':
return {x: node.x + 8, y: node.y};
default:
return {x: node.x, y: node.y};
}
}
function multilineTextNode(node) {
var MAX_LINES = 6;
var fade = false;
var el = d3.select(this);
var lines = el.text().split('\n');
if (lines.length > MAX_LINES) {
lines.splice(MAX_LINES);
fade = true;
}
el.text('');
var tspan = el.selectAll('tspan').data(lines);
tspan.enter().append('tspan');
tspan
.attr('x', 8)
.attr('dy', function(line, i) { return i > 0 ? '1em' : 0})
.attr('opacity', function(line, i) {
if (!fade)
return 1;
return Math.min(1, (MAX_LINES - i) * 0.25 - 0.15);
})
.text(function(line) { return line; });
}
function update() {
force.size([document.documentElement.clientWidth / 4,
document.documentElement.clientHeight / 4]);
var layoutDirty = false;
node = d3.select('#nodes').selectAll('.node').data(nodeList);
layoutDirty = layoutDirty || !node.enter().empty() || !node.exit().empty();
var newG = node.enter().append('g')
.attr('class', function(n) {return 'node ' + n.type;})
.attr('r', 5)
// don't show until next tick
.style('display', 'none')
.on('mousedown', function() {
d3.event.stopPropagation();
})
.on('mouseover', function(n) {
$('#description').text(n.label);
})
.on('mouseout', function(d, i) {
$('#description').html('');
})
.call(force.drag);
newG.append('path')
.attr('transform', 'scale(0.08)')
.attr('stroke', 'black')
.attr('stroke-width', 4)
.attr('fill', 'white')
.attr('d', pathDataForNode);
newG.append('text')
.attr('x', 3)
.attr('y', 0)
.attr('font-size', 3.25)
.attr('transform', function(n) {
if (n.type !== 'observer')
return 'translate(1.5, 0)';
else
return null;
})
node.exit().remove();
node
.classed('invalidated', function(n) { return n.invalidated; })
.classed('running', function(n) { return n.running; })
.classed('changed', function(n) { return n.changed; })
.attr('fill', function(n) {
if (n.invalidated)
return "url(#diagonalHatch)";
else
return null;
});
var tspan = node.selectAll('text').filter(function(n) {
// This filter is used to disregard all nodes whose labels have
// not changed since the last time we updated them.
var changed = n.label !== this.label;
this.label = n.label;
return changed;
}).selectAll('tspan')
.data(function(n) {
var lines = n.label.replace(/ /g, '\xA0').split('\n');
if (lines.length > MAX_LINES) {
lines.splice(MAX_LINES);
}
return lines;
});
tspan.enter().append('tspan');
tspan.exit().remove();
tspan
.attr('x', 8)
.attr('dy', function(line, i) { return i > 0 ? '1em' : 0})
.attr('opacity', function(line, i) {
return Math.min(1, (MAX_LINES - i) * 0.25 - 0.15);
})
.text(function(line) { return line; });
link = d3.select('#links').selectAll('.link').data(links);
layoutDirty = layoutDirty || !link.enter().empty() || !link.exit().empty();
link.enter().append('path')
.attr('class', 'link')
.attr('marker-mid', 'url(#triangle)');
link.exit().remove();
if (layoutDirty) {
force
.nodes(nodeList.filter(function(n) {return !n.hide;}))
.start();
layoutDirty = false;
}
}
function onTick() {
node
.style('display', null)
.attr('transform', function(n) {
return 'translate(' + n.x + ' ' + n.y + ')';
});
link
.attr('d', function(link) {
var source = getSourceCoords(link.source);
var target = getTargetCoords(link.target)
var mid = {
x: (source.x + target.x) / 2,
y: (source.y + target.y) / 2
}
return 'M' + source.x + ',' + source.y +
' L' + mid.x + ',' + mid.y +
' L' + target.x + ',' + target.y;
});
}
function createNodeWithUndo(data) {
var node;
if (!data.prevId) {
node = {
label: data.label,
type: data.type,
hide: data.hide
};
nodes[data.id] = node;
pushUndo(function() {
delete nodes[data.id];
});
if (!node.hide) {
nodeList.push(node);
pushUndo(function() {
nodeList.pop();
});
}
} else {
node = nodes[data.prevId];
var oldLabel = node.label;
var oldInvalidated = node.invalidated;
delete nodes[data.prevId];
nodes[data.id] = node;
node.label = data.label;
node.invalidated = false;
pushUndo(function() {
node.label = oldLabel;
node.invalidated = oldInvalidated;
delete nodes[data.id];
nodes[data.prevId] = node;
});
}
}
Array.prototype.pushWithUndo = function(value) {
var self = this;
this.push(value);
pushUndo(function() {
self.pop();
});
}
Array.prototype.shiftWithUndo = function(value) {
var self = this;
var value = this.shift();
pushUndo(function() {
self.unshift(value);
});
return value;
}
var undoStack = [];
var currentUndos = null;
function startUndoScope() {
if (currentUndos !== null)
throw new Error('Illegal state');
currentUndos = [];
}
function pushUndo(func) {
currentUndos.push(func);
}
function endUndoScope() {
var localUndos = currentUndos;
undoStack.push(function() {
while (localUndos.length) {
localUndos.pop()();
}
});
currentUndos = null;
}
function undo() {
if (undoStack.length) {
undoStack.pop()();
update();
return true;
}
return false;
}
function undoAll() {
while (undo()) {}
}
// Here we monkeypatch Math.random to take part in the undo mechanism.
// This allows "random" d3 force-layout decisions to be reproducible.
// If we don't do this, then doing/undoing/redoing a node creation step
// looks very confusing, as the node comes flying in from a different
// direction each time.
var trueRandom = Math.random;
Math.random = (function() {
var randomStack = [];
return function() {
if (!currentUndos)
return trueRandom();
var value;
if (randomStack.length > 0) {
value = randomStack.pop();
}
else {
value = trueRandom();
}
pushUndo(function() {
randomStack.push(value);
});
return value;
};
})();
var callbacks = {
ctx: function(data) {
createNodeWithUndo(data);
return true;
},
dep: function(data) {
var dependsOn = nodes[data.dependsOn];
if (!dependsOn) {
createNodeWithUndo({id: data.dependsOn, label: data.dependsOn, type: 'value'});
dependsOn = nodes[data.dependsOn];
}
if (dependsOn.hide) {
dependsOn.hide = false;
nodeList.push(dependsOn);
pushUndo(function() {
dependsOn.hide = true;
nodeList.pop();
});
}
links.push({
source: nodes[data.id],
target: nodes[data.dependsOn]
});
pushUndo(function() {
links.pop();
});
},
depId: function(data) {
links.push({
source: nodes[data.id],
target: nodes[data.dependsOn]
});
pushUndo(function() {
links.pop();
});
},
invalidate: function(data) {
var node = nodes[data.id];
if (node.invalidated)
throw new Error('Illegal sequence');
node.invalidated = true;
pushUndo(function() {
node.invalidated = false;
});
var origLinks = links;
links = links.filter(function(link) {
return link.source !== node;
});
pushUndo(function() {
links = origLinks;
});
},
valueChange: function(data) {
var existed = !!nodes[data.id];
createNodeWithUndo({
id: data.id,
label: data.id + ' = ' + data.value,
type: 'value',
prevId: nodes[data.id] ? data.id : null,
hide: existed ? nodes[data.id].hide : true
});
if (!existed || nodes[data.id].hide)
return true;
nodes[data.id].changed = true;
pushUndo(function() {
nodes[data.id].changed = false;
});
executeBeforeNextCommand.pushWithUndo(function() {
nodes[data.id].changed = false;
pushUndo(function() {
nodes[data.id].changed = true;
});
});
},
enter: function(data) {
var node = nodes[data.id];
node.running = true;
pushUndo(function() {
node.running = false;
});
},
exit: function(data) {
var node = nodes[data.id];
node.running = false;
pushUndo(function() {
node.running = true;
});
}
};
function processMessage(data, suppressUpdate) {
console.log(JSON.stringify(data));
if (!callbacks.hasOwnProperty(data.action))
throw new Error('Unknown action ' + data.action);
var result = callbacks[data.action].call(callbacks, data);
if (!suppressUpdate)
update();
return result;
}
var executeBeforeNextCommand = [];
function doNext(suppressUpdate) {
if (!log.length)
return;
startUndoScope();
while (executeBeforeNextCommand.length) {
executeBeforeNextCommand.shiftWithUndo()();
}
while (log.length) {
var result = (function() {
var message = log.shift();
pushUndo(function() {
log.unshift(message);
})
return processMessage(message, suppressUpdate);
})();
if (!result)
break;
}
if (!log.length) {
$('#ended').fadeIn(1500);
pushUndo(function() {
$('#ended').hide();
});
}
step++;
updateTimeline();
pushUndo(function() {
step--;
updateTimeline();
});
endUndoScope();
}
function countSteps() {
if (undoStack.length !== 0) {
throw new Error(
'Illegal state; must call countSteps before execution begins');
}
var steps = 0;
while (log.length) {
doNext();
steps++;
}
while (undoStack.length)
undoStack.pop()();
return steps;
}
function updateTimeline() {
$('#timeline-fill').width((step/totalSteps*100) + '%');
}
function zoom() {
var scale = d3.event.scale;
var x = d3.event.translate[0];
var y = d3.event.translate[1];
d3.select('#viz').attr('transform', 'scale(' + scale + ') translate(' + x/scale + ' ' + y/scale + ')');
}
// The total number of steps, as far as the user is concerned, in the log.
// This may/will be different than the number of log entries, since each
// step may include more than one log entry.
var totalSteps;
// The current step we're on.
var step;
$(function() {
d3.select('svg').call(d3.behavior.zoom().scale(4).on('zoom', zoom));
$(document.body).on('keydown', function(e) {
if (e.which === 39 || e.which === 32) { // space, right
// Move one step ahead
doNext();
}
if (e.which === 37) { // left
// Move one step back
undo();
}
if (e.which === 35) { // end
// Seek to end
while (log.length) {
doNext();
}
}
if (e.which === 36) { // home
// Seek to beginning
undoAll();
}
});
// Timeline click and scrub
$('#timeline').on('click mousemove', function(e) {
// Make sure left mouse button is down.
// Firefox is stupid; e.which is always 1 on mousemove events,
// even when button is not down!! So read e.originalEvent.buttons.
if (typeof(e.originalEvent.buttons) !== 'undefined') {
if (e.originalEvent.buttons !== 1)
return;
} else if (e.which !== 1) {
return;
}
var timeline = e.currentTarget;
var pos = e.offsetX || e.originalEvent.layerX;
var width = timeline.offsetWidth;
var targetStep = Math.round((pos/width) * totalSteps);
while (step < targetStep) {
doNext();
}
while (step > targetStep && step != 1) {
undo();
}
});
totalSteps = countSteps();
step = 0;
doNext();
// don't allow undoing past initial state
while (undoStack.length)
undoStack.pop();
executeBeforeNextCommand.push(function() {
$('#instructions').fadeOut(1000);
// It's weird for the instructions to fade back in, so no pushUndo here
});
});
</script>
<body>
<svg>
<defs>
<marker id="triangle"
viewBox="0 0 10 10"
refX="5" refY="5"
markerWidth="6"
markerHeight="6"
orient="auto">
<path d="M 10 0 L 0 5 L 10 10 z" />
</marker>
<pattern id="diagonalHatch" patternUnits="userSpaceOnUse" width="1" height="1">
<path stroke="black" stroke-width="0.25" fill="none"
d="M-1,1 l2,-2
M0,4 l4,-4
M3,5 l2,-2" />
</pattern>
</defs>
<g id="viz" transform="scale(4)">
<g id="links"></g>
<g id="nodes"></g>
</g>
</svg>
<div id="instructions">
Press right-arrow to advance
</div>
<div id="ended" style="display: none;">
<strong>You&rsquo;ve reached the end</strong><br/>Press the Home key to start over
</div>
<div id="legend">
<div class="color normal"></div> Normal<br/>
<div class="color invalidated"></div> Invalidated<br/>
<div class="color running"></div> Running<br/>
</div>
<br/>
<pre id="description"><br/></pre>
<div id="timeline">
<div id="timeline-bg">
<div id="timeline-fill"></div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,123 @@
div.dataTables_length label {
float: left;
text-align: left;
}
div.dataTables_length select {
width: 75px;
}
div.dataTables_filter label {
float: right;
}
div.dataTables_info {
padding-top: 8px;
}
div.dataTables_paginate {
float: right;
margin: 0;
}
table.table {
clear: both;
margin-bottom: 6px !important;
max-width: none !important;
}
table.table thead .sorting,
table.table thead .sorting_asc,
table.table thead .sorting_desc,
table.table thead .sorting_asc_disabled,
table.table thead .sorting_desc_disabled {
cursor: pointer;
*cursor: hand;
}
table.table thead .sorting { background: url('images/sort_both.png') no-repeat center right; }
table.table thead .sorting_asc { background: url('images/sort_asc.png') no-repeat center right; }
table.table thead .sorting_desc { background: url('images/sort_desc.png') no-repeat center right; }
table.table thead .sorting_asc_disabled { background: url('images/sort_asc_disabled.png') no-repeat center right; }
table.table thead .sorting_desc_disabled { background: url('images/sort_desc_disabled.png') no-repeat center right; }
table.dataTable th:active {
outline: none;
}
table.dataTable tr.odd td.sorting_1 { background-color: #D3D6FF; }
table.dataTable tr.odd td.sorting_2 { background-color: #DADCFF; }
table.dataTable tr.odd td.sorting_3 { background-color: #E0E2FF; }
table.dataTable tr.even td.sorting_1 { background-color: #EAEBFF; }
table.dataTable tr.even td.sorting_2 { background-color: #F2F3FF; }
table.dataTable tr.even td.sorting_3 { background-color: #F9F9FF; }
/* Scrolling */
div.dataTables_scrollHead table {
margin-bottom: 0 !important;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
div.dataTables_scrollHead table thead tr:last-child th:first-child,
div.dataTables_scrollHead table thead tr:last-child td:first-child {
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
div.dataTables_scrollBody table {
border-top: none;
margin-bottom: 0 !important;
}
div.dataTables_scrollBody tbody tr:first-child th,
div.dataTables_scrollBody tbody tr:first-child td {
border-top: none;
}
div.dataTables_scrollFoot table {
border-top: none;
}
/* Active rows */
.table tbody tr.active td,
.table tbody tr.active th {
background-color: #08C;
color: white;
}
.table tbody tr.active:hover td,
.table tbody tr.active:hover th {
background-color: #0075b0 !important;
}
.table-striped tbody tr.active:nth-child(odd) td,
.table-striped tbody tr.active:nth-child(odd) th {
background-color: #017ebc;
}
/* Processing indicator */
.dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 250px;
height: 30px;
margin-left: -125px;
margin-top: -15px;
padding: 14px 0 2px 0;
border: 1px solid #ddd;
text-align: center;
color: #999;
font-size: 14px;
background-color: white;
}
/* Search boxes in the footer */
table tfoot input {
width: 100%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,109 @@
/* Set the defaults for DataTables initialisation */
$.extend( true, $.fn.dataTable.defaults, {
"sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>",
"sPaginationType": "bootstrap",
"oLanguage": {
"sLengthMenu": "_MENU_ records per page"
}
} );
/* Default class modification */
$.extend( $.fn.dataTableExt.oStdClasses, {
"sWrapper": "dataTables_wrapper form-inline"
} );
/* API method to get paging information */
$.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
{
return {
"iStart": oSettings._iDisplayStart,
"iEnd": oSettings.fnDisplayEnd(),
"iLength": oSettings._iDisplayLength,
"iTotal": oSettings.fnRecordsTotal(),
"iFilteredTotal": oSettings.fnRecordsDisplay(),
"iPage": oSettings._iDisplayLength === -1 ?
0 : Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ),
"iTotalPages": oSettings._iDisplayLength === -1 ?
0 : Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength )
};
};
/* Bootstrap style pagination control */
$.extend( $.fn.dataTableExt.oPagination, {
"bootstrap": {
"fnInit": function( oSettings, nPaging, fnDraw ) {
var oLang = oSettings.oLanguage.oPaginate;
var fnClickHandler = function ( e ) {
e.preventDefault();
if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) {
fnDraw( oSettings );
}
};
$(nPaging).addClass('pagination').append(
'<ul>'+
'<li class="prev disabled"><a href="#">&larr; '+oLang.sPrevious+'</a></li>'+
'<li class="next disabled"><a href="#">'+oLang.sNext+' &rarr; </a></li>'+
'</ul>'
);
var els = $('a', nPaging);
$(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler );
$(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler );
},
"fnUpdate": function ( oSettings, fnDraw ) {
var iListLength = 5;
var oPaging = oSettings.oInstance.fnPagingInfo();
var an = oSettings.aanFeatures.p;
var i, ien, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2);
if ( oPaging.iTotalPages < iListLength) {
iStart = 1;
iEnd = oPaging.iTotalPages;
}
else if ( oPaging.iPage <= iHalf ) {
iStart = 1;
iEnd = iListLength;
} else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) {
iStart = oPaging.iTotalPages - iListLength + 1;
iEnd = oPaging.iTotalPages;
} else {
iStart = oPaging.iPage - iHalf + 1;
iEnd = iStart + iListLength - 1;
}
for ( i=0, ien=an.length ; i<ien ; i++ ) {
// Remove the middle elements
$('li:gt(0)', an[i]).filter(':not(:last)').remove();
// Add the new list items and their event handlers
for ( j=iStart ; j<=iEnd ; j++ ) {
sClass = (j==oPaging.iPage+1) ? 'class="active"' : '';
$('<li '+sClass+'><a href="#">'+j+'</a></li>')
.insertBefore( $('li:last', an[i])[0] )
.bind('click', function (e) {
e.preventDefault();
oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength;
fnDraw( oSettings );
} );
}
// Add / remove disabled classes from the static elements
if ( oPaging.iPage === 0 ) {
$('li:first', an[i]).addClass('disabled');
} else {
$('li:first', an[i]).removeClass('disabled');
}
if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
$('li:last', an[i]).addClass('disabled');
} else {
$('li:last', an[i]).removeClass('disabled');
}
}
}
}
} );

View File

@@ -0,0 +1,155 @@
/*
* File: jquery.dataTables.min.js
* Version: 1.9.4
* Author: Allan Jardine (www.sprymedia.co.uk)
* Info: www.datatables.net
*
* Copyright 2008-2012 Allan Jardine, all rights reserved.
*
* This source file is free software, under either the GPL v2 license or a
* BSD style license, available at:
* http://datatables.net/license_gpl2
* http://datatables.net/license_bsd
*
* This source file 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 license files for details.
*/
(function(X,l,n){var L=function(h){var j=function(e){function o(a,b){var c=j.defaults.columns,d=a.aoColumns.length,c=h.extend({},j.models.oColumn,c,{sSortingClass:a.oClasses.sSortable,sSortingClassJUI:a.oClasses.sSortJUI,nTh:b?b:l.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.oDefaults:d});a.aoColumns.push(c);if(a.aoPreSearchCols[d]===n||null===a.aoPreSearchCols[d])a.aoPreSearchCols[d]=h.extend({},j.models.oSearch);else if(c=a.aoPreSearchCols[d],
c.bRegex===n&&(c.bRegex=!0),c.bSmart===n&&(c.bSmart=!0),c.bCaseInsensitive===n)c.bCaseInsensitive=!0;m(a,d,null)}function m(a,b,c){var d=a.aoColumns[b];c!==n&&null!==c&&(c.mDataProp&&!c.mData&&(c.mData=c.mDataProp),c.sType!==n&&(d.sType=c.sType,d._bAutoType=!1),h.extend(d,c),p(d,c,"sWidth","sWidthOrig"),c.iDataSort!==n&&(d.aDataSort=[c.iDataSort]),p(d,c,"aDataSort"));var i=d.mRender?Q(d.mRender):null,f=Q(d.mData);d.fnGetData=function(a,b){var c=f(a,b);return d.mRender&&b&&""!==b?i(c,b,a):c};d.fnSetData=
L(d.mData);a.oFeatures.bSort||(d.bSortable=!1);!d.bSortable||-1==h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortableNone,d.sSortingClassJUI=""):-1==h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortable,d.sSortingClassJUI=a.oClasses.sSortJUI):-1!=h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortableAsc,d.sSortingClassJUI=a.oClasses.sSortJUIAscAllowed):-1==
h.inArray("asc",d.asSorting)&&-1!=h.inArray("desc",d.asSorting)&&(d.sSortingClass=a.oClasses.sSortableDesc,d.sSortingClassJUI=a.oClasses.sSortJUIDescAllowed)}function k(a){if(!1===a.oFeatures.bAutoWidth)return!1;da(a);for(var b=0,c=a.aoColumns.length;b<c;b++)a.aoColumns[b].nTh.style.width=a.aoColumns[b].sWidth}function G(a,b){var c=r(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function R(a,b){var c=r(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function t(a){return r(a,"bVisible").length}
function r(a,b){var c=[];h.map(a.aoColumns,function(a,i){a[b]&&c.push(i)});return c}function B(a){for(var b=j.ext.aTypes,c=b.length,d=0;d<c;d++){var i=b[d](a);if(null!==i)return i}return"string"}function u(a,b){for(var c=b.split(","),d=[],i=0,f=a.aoColumns.length;i<f;i++)for(var g=0;g<f;g++)if(a.aoColumns[i].sName==c[g]){d.push(g);break}return d}function M(a){for(var b="",c=0,d=a.aoColumns.length;c<d;c++)b+=a.aoColumns[c].sName+",";return b.length==d?"":b.slice(0,-1)}function ta(a,b,c,d){var i,f,
g,e,w;if(b)for(i=b.length-1;0<=i;i--){var j=b[i].aTargets;h.isArray(j)||D(a,1,"aTargets must be an array of targets, not a "+typeof j);f=0;for(g=j.length;f<g;f++)if("number"===typeof j[f]&&0<=j[f]){for(;a.aoColumns.length<=j[f];)o(a);d(j[f],b[i])}else if("number"===typeof j[f]&&0>j[f])d(a.aoColumns.length+j[f],b[i]);else if("string"===typeof j[f]){e=0;for(w=a.aoColumns.length;e<w;e++)("_all"==j[f]||h(a.aoColumns[e].nTh).hasClass(j[f]))&&d(e,b[i])}}if(c){i=0;for(a=c.length;i<a;i++)d(i,c[i])}}function H(a,
b){var c;c=h.isArray(b)?b.slice():h.extend(!0,{},b);var d=a.aoData.length,i=h.extend(!0,{},j.models.oRow);i._aData=c;a.aoData.push(i);for(var f,i=0,g=a.aoColumns.length;i<g;i++)c=a.aoColumns[i],"function"===typeof c.fnRender&&c.bUseRendered&&null!==c.mData?F(a,d,i,S(a,d,i)):F(a,d,i,v(a,d,i)),c._bAutoType&&"string"!=c.sType&&(f=v(a,d,i,"type"),null!==f&&""!==f&&(f=B(f),null===c.sType?c.sType=f:c.sType!=f&&"html"!=c.sType&&(c.sType="string")));a.aiDisplayMaster.push(d);a.oFeatures.bDeferRender||ea(a,
d);return d}function ua(a){var b,c,d,i,f,g,e;if(a.bDeferLoading||null===a.sAjaxSource)for(b=a.nTBody.firstChild;b;){if("TR"==b.nodeName.toUpperCase()){c=a.aoData.length;b._DT_RowIndex=c;a.aoData.push(h.extend(!0,{},j.models.oRow,{nTr:b}));a.aiDisplayMaster.push(c);f=b.firstChild;for(d=0;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)F(a,c,d,h.trim(f.innerHTML)),d++;f=f.nextSibling}}b=b.nextSibling}i=T(a);d=[];b=0;for(c=i.length;b<c;b++)for(f=i[b].firstChild;f;)g=f.nodeName.toUpperCase(),("TD"==
g||"TH"==g)&&d.push(f),f=f.nextSibling;c=0;for(i=a.aoColumns.length;c<i;c++){e=a.aoColumns[c];null===e.sTitle&&(e.sTitle=e.nTh.innerHTML);var w=e._bAutoType,o="function"===typeof e.fnRender,k=null!==e.sClass,n=e.bVisible,m,p;if(w||o||k||!n){g=0;for(b=a.aoData.length;g<b;g++)f=a.aoData[g],m=d[g*i+c],w&&"string"!=e.sType&&(p=v(a,g,c,"type"),""!==p&&(p=B(p),null===e.sType?e.sType=p:e.sType!=p&&"html"!=e.sType&&(e.sType="string"))),e.mRender?m.innerHTML=v(a,g,c,"display"):e.mData!==c&&(m.innerHTML=v(a,
g,c,"display")),o&&(p=S(a,g,c),m.innerHTML=p,e.bUseRendered&&F(a,g,c,p)),k&&(m.className+=" "+e.sClass),n?f._anHidden[c]=null:(f._anHidden[c]=m,m.parentNode.removeChild(m)),e.fnCreatedCell&&e.fnCreatedCell.call(a.oInstance,m,v(a,g,c,"display"),f._aData,g,c)}}if(0!==a.aoRowCreatedCallback.length){b=0;for(c=a.aoData.length;b<c;b++)f=a.aoData[b],A(a,"aoRowCreatedCallback",null,[f.nTr,f._aData,b])}}function I(a,b){return b._DT_RowIndex!==n?b._DT_RowIndex:null}function fa(a,b,c){for(var b=J(a,b),d=0,a=
a.aoColumns.length;d<a;d++)if(b[d]===c)return d;return-1}function Y(a,b,c,d){for(var i=[],f=0,g=d.length;f<g;f++)i.push(v(a,b,d[f],c));return i}function v(a,b,c,d){var i=a.aoColumns[c];if((c=i.fnGetData(a.aoData[b]._aData,d))===n)return a.iDrawError!=a.iDraw&&null===i.sDefaultContent&&(D(a,0,"Requested unknown parameter "+("function"==typeof i.mData?"{mData function}":"'"+i.mData+"'")+" from the data source for row "+b),a.iDrawError=a.iDraw),i.sDefaultContent;if(null===c&&null!==i.sDefaultContent)c=
i.sDefaultContent;else if("function"===typeof c)return c();return"display"==d&&null===c?"":c}function F(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d)}function Q(a){if(null===a)return function(){return null};if("function"===typeof a)return function(b,d,i){return a(b,d,i)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("["))){var b=function(a,d,i){var f=i.split("."),g;if(""!==i){var e=0;for(g=f.length;e<g;e++){if(i=f[e].match(U)){f[e]=f[e].replace(U,"");""!==f[e]&&(a=a[f[e]]);
g=[];f.splice(0,e+1);for(var f=f.join("."),e=0,h=a.length;e<h;e++)g.push(b(a[e],d,f));a=i[0].substring(1,i[0].length-1);a=""===a?g:g.join(a);break}if(null===a||a[f[e]]===n)return n;a=a[f[e]]}}return a};return function(c,d){return b(c,d,a)}}return function(b){return b[a]}}function L(a){if(null===a)return function(){};if("function"===typeof a)return function(b,d){a(b,"set",d)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("["))){var b=function(a,d,i){var i=i.split("."),f,g,e=0;for(g=
i.length-1;e<g;e++){if(f=i[e].match(U)){i[e]=i[e].replace(U,"");a[i[e]]=[];f=i.slice();f.splice(0,e+1);g=f.join(".");for(var h=0,j=d.length;h<j;h++)f={},b(f,d[h],g),a[i[e]].push(f);return}if(null===a[i[e]]||a[i[e]]===n)a[i[e]]={};a=a[i[e]]}a[i[i.length-1].replace(U,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Z(a){for(var b=[],c=a.aoData.length,d=0;d<c;d++)b.push(a.aoData[d]._aData);return b}function ga(a){a.aoData.splice(0,a.aoData.length);a.aiDisplayMaster.splice(0,
a.aiDisplayMaster.length);a.aiDisplay.splice(0,a.aiDisplay.length);y(a)}function ha(a,b){for(var c=-1,d=0,i=a.length;d<i;d++)a[d]==b?c=d:a[d]>b&&a[d]--; -1!=c&&a.splice(c,1)}function S(a,b,c){var d=a.aoColumns[c];return d.fnRender({iDataRow:b,iDataColumn:c,oSettings:a,aData:a.aoData[b]._aData,mDataProp:d.mData},v(a,b,c,"display"))}function ea(a,b){var c=a.aoData[b],d;if(null===c.nTr){c.nTr=l.createElement("tr");c.nTr._DT_RowIndex=b;c._aData.DT_RowId&&(c.nTr.id=c._aData.DT_RowId);c._aData.DT_RowClass&&
(c.nTr.className=c._aData.DT_RowClass);for(var i=0,f=a.aoColumns.length;i<f;i++){var g=a.aoColumns[i];d=l.createElement(g.sCellType);d.innerHTML="function"===typeof g.fnRender&&(!g.bUseRendered||null===g.mData)?S(a,b,i):v(a,b,i,"display");null!==g.sClass&&(d.className=g.sClass);g.bVisible?(c.nTr.appendChild(d),c._anHidden[i]=null):c._anHidden[i]=d;g.fnCreatedCell&&g.fnCreatedCell.call(a.oInstance,d,v(a,b,i,"display"),c._aData,b,i)}A(a,"aoRowCreatedCallback",null,[c.nTr,c._aData,b])}}function va(a){var b,
c,d;if(0!==h("th, td",a.nTHead).length){b=0;for(d=a.aoColumns.length;b<d;b++)if(c=a.aoColumns[b].nTh,c.setAttribute("role","columnheader"),a.aoColumns[b].bSortable&&(c.setAttribute("tabindex",a.iTabIndex),c.setAttribute("aria-controls",a.sTableId)),null!==a.aoColumns[b].sClass&&h(c).addClass(a.aoColumns[b].sClass),a.aoColumns[b].sTitle!=c.innerHTML)c.innerHTML=a.aoColumns[b].sTitle}else{var i=l.createElement("tr");b=0;for(d=a.aoColumns.length;b<d;b++)c=a.aoColumns[b].nTh,c.innerHTML=a.aoColumns[b].sTitle,
c.setAttribute("tabindex","0"),null!==a.aoColumns[b].sClass&&h(c).addClass(a.aoColumns[b].sClass),i.appendChild(c);h(a.nTHead).html("")[0].appendChild(i);V(a.aoHeader,a.nTHead)}h(a.nTHead).children("tr").attr("role","row");if(a.bJUI){b=0;for(d=a.aoColumns.length;b<d;b++){c=a.aoColumns[b].nTh;i=l.createElement("div");i.className=a.oClasses.sSortJUIWrapper;h(c).contents().appendTo(i);var f=l.createElement("span");f.className=a.oClasses.sSortIcon;i.appendChild(f);c.appendChild(i)}}if(a.oFeatures.bSort)for(b=
0;b<a.aoColumns.length;b++)!1!==a.aoColumns[b].bSortable?ia(a,a.aoColumns[b].nTh,b):h(a.aoColumns[b].nTh).addClass(a.oClasses.sSortableNone);""!==a.oClasses.sFooterTH&&h(a.nTFoot).children("tr").children("th").addClass(a.oClasses.sFooterTH);if(null!==a.nTFoot){c=N(a,null,a.aoFooter);b=0;for(d=a.aoColumns.length;b<d;b++)c[b]&&(a.aoColumns[b].nTf=c[b],a.aoColumns[b].sClass&&h(c[b]).addClass(a.aoColumns[b].sClass))}}function W(a,b,c){var d,i,f,g=[],e=[],h=a.aoColumns.length,j;c===n&&(c=!1);d=0;for(i=
b.length;d<i;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=h-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);e.push([])}d=0;for(i=g.length;d<i;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(j=h=1,e[d][f]===n){a.appendChild(g[d][f].cell);for(e[d][f]=1;g[d+h]!==n&&g[d][f].cell==g[d+h][f].cell;)e[d+h][f]=1,h++;for(;g[d][f+j]!==n&&g[d][f].cell==g[d][f+j].cell;){for(c=0;c<h;c++)e[d+c][f+j]=1;j++}g[d][f].cell.rowSpan=h;g[d][f].cell.colSpan=j}}}function x(a){var b=
A(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))E(a,!1);else{var c,d,b=[],i=0,f=a.asStripeClasses.length;c=a.aoOpenRows.length;a.bDrawing=!0;a.iInitDisplayStart!==n&&-1!=a.iInitDisplayStart&&(a._iDisplayStart=a.oFeatures.bServerSide?a.iInitDisplayStart:a.iInitDisplayStart>=a.fnRecordsDisplay()?0:a.iInitDisplayStart,a.iInitDisplayStart=-1,y(a));if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++;else if(a.oFeatures.bServerSide){if(!a.bDestroying&&!wa(a))return}else a.iDraw++;if(0!==a.aiDisplay.length){var g=
a._iDisplayStart;d=a._iDisplayEnd;a.oFeatures.bServerSide&&(g=0,d=a.aoData.length);for(;g<d;g++){var e=a.aoData[a.aiDisplay[g]];null===e.nTr&&ea(a,a.aiDisplay[g]);var j=e.nTr;if(0!==f){var o=a.asStripeClasses[i%f];e._sRowStripe!=o&&(h(j).removeClass(e._sRowStripe).addClass(o),e._sRowStripe=o)}A(a,"aoRowCallback",null,[j,a.aoData[a.aiDisplay[g]]._aData,i,g]);b.push(j);i++;if(0!==c)for(e=0;e<c;e++)if(j==a.aoOpenRows[e].nParent){b.push(a.aoOpenRows[e].nTr);break}}}else b[0]=l.createElement("tr"),a.asStripeClasses[0]&&
(b[0].className=a.asStripeClasses[0]),c=a.oLanguage,f=c.sZeroRecords,1==a.iDraw&&null!==a.sAjaxSource&&!a.oFeatures.bServerSide?f=c.sLoadingRecords:c.sEmptyTable&&0===a.fnRecordsTotal()&&(f=c.sEmptyTable),c=l.createElement("td"),c.setAttribute("valign","top"),c.colSpan=t(a),c.className=a.oClasses.sRowEmpty,c.innerHTML=ja(a,f),b[i].appendChild(c);A(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Z(a),a._iDisplayStart,a.fnDisplayEnd(),a.aiDisplay]);A(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],
Z(a),a._iDisplayStart,a.fnDisplayEnd(),a.aiDisplay]);i=l.createDocumentFragment();c=l.createDocumentFragment();if(a.nTBody){f=a.nTBody.parentNode;c.appendChild(a.nTBody);if(!a.oScroll.bInfinite||!a._bInitComplete||a.bSorted||a.bFiltered)for(;c=a.nTBody.firstChild;)a.nTBody.removeChild(c);c=0;for(d=b.length;c<d;c++)i.appendChild(b[c]);a.nTBody.appendChild(i);null!==f&&f.appendChild(a.nTBody)}A(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1;a.oFeatures.bServerSide&&(E(a,!1),
a._bInitComplete||$(a))}}function aa(a){a.oFeatures.bSort?O(a,a.oPreviousSearch):a.oFeatures.bFilter?K(a,a.oPreviousSearch):(y(a),x(a))}function xa(a){var b=h("<div></div>")[0];a.nTable.parentNode.insertBefore(b,a.nTable);a.nTableWrapper=h('<div id="'+a.sTableId+'_wrapper" class="'+a.oClasses.sWrapper+'" role="grid"></div>')[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var c=a.nTableWrapper,d=a.sDom.split(""),i,f,g,e,w,o,k,m=0;m<d.length;m++){f=0;g=d[m];if("<"==g){e=h("<div></div>")[0];w=d[m+
1];if("'"==w||'"'==w){o="";for(k=2;d[m+k]!=w;)o+=d[m+k],k++;"H"==o?o=a.oClasses.sJUIHeader:"F"==o&&(o=a.oClasses.sJUIFooter);-1!=o.indexOf(".")?(w=o.split("."),e.id=w[0].substr(1,w[0].length-1),e.className=w[1]):"#"==o.charAt(0)?e.id=o.substr(1,o.length-1):e.className=o;m+=k}c.appendChild(e);c=e}else if(">"==g)c=c.parentNode;else if("l"==g&&a.oFeatures.bPaginate&&a.oFeatures.bLengthChange)i=ya(a),f=1;else if("f"==g&&a.oFeatures.bFilter)i=za(a),f=1;else if("r"==g&&a.oFeatures.bProcessing)i=Aa(a),f=
1;else if("t"==g)i=Ba(a),f=1;else if("i"==g&&a.oFeatures.bInfo)i=Ca(a),f=1;else if("p"==g&&a.oFeatures.bPaginate)i=Da(a),f=1;else if(0!==j.ext.aoFeatures.length){e=j.ext.aoFeatures;k=0;for(w=e.length;k<w;k++)if(g==e[k].cFeature){(i=e[k].fnInit(a))&&(f=1);break}}1==f&&null!==i&&("object"!==typeof a.aanFeatures[g]&&(a.aanFeatures[g]=[]),a.aanFeatures[g].push(i),c.appendChild(i))}b.parentNode.replaceChild(a.nTableWrapper,b)}function V(a,b){var c=h(b).children("tr"),d,i,f,g,e,j,o,k,m,p;a.splice(0,a.length);
f=0;for(j=c.length;f<j;f++)a.push([]);f=0;for(j=c.length;f<j;f++){d=c[f];for(i=d.firstChild;i;){if("TD"==i.nodeName.toUpperCase()||"TH"==i.nodeName.toUpperCase()){k=1*i.getAttribute("colspan");m=1*i.getAttribute("rowspan");k=!k||0===k||1===k?1:k;m=!m||0===m||1===m?1:m;g=0;for(e=a[f];e[g];)g++;o=g;p=1===k?!0:!1;for(e=0;e<k;e++)for(g=0;g<m;g++)a[f+g][o+e]={cell:i,unique:p},a[f+g].nTr=d}i=i.nextSibling}}}function N(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],V(c,b)));for(var b=0,i=c.length;b<i;b++)for(var f=
0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function wa(a){if(a.bAjaxDataGet){a.iDraw++;E(a,!0);var b=Ea(a);ka(a,b);a.fnServerData.call(a.oInstance,a.sAjaxSource,b,function(b){Fa(a,b)},a);return!1}return!0}function Ea(a){var b=a.aoColumns.length,c=[],d,i,f,g;c.push({name:"sEcho",value:a.iDraw});c.push({name:"iColumns",value:b});c.push({name:"sColumns",value:M(a)});c.push({name:"iDisplayStart",value:a._iDisplayStart});c.push({name:"iDisplayLength",
value:!1!==a.oFeatures.bPaginate?a._iDisplayLength:-1});for(f=0;f<b;f++)d=a.aoColumns[f].mData,c.push({name:"mDataProp_"+f,value:"function"===typeof d?"function":d});if(!1!==a.oFeatures.bFilter){c.push({name:"sSearch",value:a.oPreviousSearch.sSearch});c.push({name:"bRegex",value:a.oPreviousSearch.bRegex});for(f=0;f<b;f++)c.push({name:"sSearch_"+f,value:a.aoPreSearchCols[f].sSearch}),c.push({name:"bRegex_"+f,value:a.aoPreSearchCols[f].bRegex}),c.push({name:"bSearchable_"+f,value:a.aoColumns[f].bSearchable})}if(!1!==
a.oFeatures.bSort){var e=0;d=null!==a.aaSortingFixed?a.aaSortingFixed.concat(a.aaSorting):a.aaSorting.slice();for(f=0;f<d.length;f++){i=a.aoColumns[d[f][0]].aDataSort;for(g=0;g<i.length;g++)c.push({name:"iSortCol_"+e,value:i[g]}),c.push({name:"sSortDir_"+e,value:d[f][1]}),e++}c.push({name:"iSortingCols",value:e});for(f=0;f<b;f++)c.push({name:"bSortable_"+f,value:a.aoColumns[f].bSortable})}return c}function ka(a,b){A(a,"aoServerParams","serverParams",[b])}function Fa(a,b){if(b.sEcho!==n){if(1*b.sEcho<
a.iDraw)return;a.iDraw=1*b.sEcho}(!a.oScroll.bInfinite||a.oScroll.bInfinite&&(a.bSorted||a.bFiltered))&&ga(a);a._iRecordsTotal=parseInt(b.iTotalRecords,10);a._iRecordsDisplay=parseInt(b.iTotalDisplayRecords,10);var c=M(a),c=b.sColumns!==n&&""!==c&&b.sColumns!=c,d;c&&(d=u(a,b.sColumns));for(var i=Q(a.sAjaxDataProp)(b),f=0,g=i.length;f<g;f++)if(c){for(var e=[],h=0,j=a.aoColumns.length;h<j;h++)e.push(i[f][d[h]]);H(a,e)}else H(a,i[f]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;x(a);a.bAjaxDataGet=
!0;E(a,!1)}function za(a){var b=a.oPreviousSearch,c=a.oLanguage.sSearch,c=-1!==c.indexOf("_INPUT_")?c.replace("_INPUT_",'<input type="text" />'):""===c?'<input type="text" />':c+' <input type="text" />',d=l.createElement("div");d.className=a.oClasses.sFilter;d.innerHTML="<label>"+c+"</label>";a.aanFeatures.f||(d.id=a.sTableId+"_filter");c=h('input[type="text"]',d);d._DT_Input=c[0];c.val(b.sSearch.replace('"',"&quot;"));c.bind("keyup.DT",function(){for(var c=a.aanFeatures.f,d=this.value===""?"":this.value,
g=0,e=c.length;g<e;g++)c[g]!=h(this).parents("div.dataTables_filter")[0]&&h(c[g]._DT_Input).val(d);d!=b.sSearch&&K(a,{sSearch:d,bRegex:b.bRegex,bSmart:b.bSmart,bCaseInsensitive:b.bCaseInsensitive})});c.attr("aria-controls",a.sTableId).bind("keypress.DT",function(a){if(a.keyCode==13)return false});return d}function K(a,b,c){var d=a.oPreviousSearch,i=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};if(a.oFeatures.bServerSide)f(b);
else{Ga(a,b.sSearch,c,b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<a.aoPreSearchCols.length;b++)Ha(a,i[b].sSearch,b,i[b].bRegex,i[b].bSmart,i[b].bCaseInsensitive);Ia(a)}a.bFiltered=!0;h(a.oInstance).trigger("filter",a);a._iDisplayStart=0;y(a);x(a);la(a,0)}function Ia(a){for(var b=j.ext.afnFiltering,c=r(a,"bSearchable"),d=0,i=b.length;d<i;d++)for(var f=0,g=0,e=a.aiDisplay.length;g<e;g++){var h=a.aiDisplay[g-f];b[d](a,Y(a,h,"filter",c),h)||(a.aiDisplay.splice(g-f,1),f++)}}function Ha(a,b,c,
d,i,f){if(""!==b)for(var g=0,b=ma(b,d,i,f),d=a.aiDisplay.length-1;0<=d;d--)i=Ja(v(a,a.aiDisplay[d],c,"filter"),a.aoColumns[c].sType),b.test(i)||(a.aiDisplay.splice(d,1),g++)}function Ga(a,b,c,d,i,f){d=ma(b,d,i,f);i=a.oPreviousSearch;c||(c=0);0!==j.ext.afnFiltering.length&&(c=1);if(0>=b.length)a.aiDisplay.splice(0,a.aiDisplay.length),a.aiDisplay=a.aiDisplayMaster.slice();else if(a.aiDisplay.length==a.aiDisplayMaster.length||i.sSearch.length>b.length||1==c||0!==b.indexOf(i.sSearch)){a.aiDisplay.splice(0,
a.aiDisplay.length);la(a,1);for(b=0;b<a.aiDisplayMaster.length;b++)d.test(a.asDataSearch[b])&&a.aiDisplay.push(a.aiDisplayMaster[b])}else for(b=c=0;b<a.asDataSearch.length;b++)d.test(a.asDataSearch[b])||(a.aiDisplay.splice(b-c,1),c++)}function la(a,b){if(!a.oFeatures.bServerSide){a.asDataSearch=[];for(var c=r(a,"bSearchable"),d=1===b?a.aiDisplayMaster:a.aiDisplay,i=0,f=d.length;i<f;i++)a.asDataSearch[i]=na(a,Y(a,d[i],"filter",c))}}function na(a,b){var c=b.join(" ");-1!==c.indexOf("&")&&(c=h("<div>").html(c).text());
return c.replace(/[\n\r]/g," ")}function ma(a,b,c,d){if(c)return a=b?a.split(" "):oa(a).split(" "),a="^(?=.*?"+a.join(")(?=.*?")+").*$",RegExp(a,d?"i":"");a=b?a:oa(a);return RegExp(a,d?"i":"")}function Ja(a,b){return"function"===typeof j.ext.ofnSearch[b]?j.ext.ofnSearch[b](a):null===a?"":"html"==b?a.replace(/[\r\n]/g," ").replace(/<.*?>/g,""):"string"===typeof a?a.replace(/[\r\n]/g," "):a}function oa(a){return a.replace(RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),
"\\$1")}function Ca(a){var b=l.createElement("div");b.className=a.oClasses.sInfo;a.aanFeatures.i||(a.aoDrawCallback.push({fn:Ka,sName:"information"}),b.id=a.sTableId+"_info");a.nTable.setAttribute("aria-describedby",a.sTableId+"_info");return b}function Ka(a){if(a.oFeatures.bInfo&&0!==a.aanFeatures.i.length){var b=a.oLanguage,c=a._iDisplayStart+1,d=a.fnDisplayEnd(),i=a.fnRecordsTotal(),f=a.fnRecordsDisplay(),g;g=0===f?b.sInfoEmpty:b.sInfo;f!=i&&(g+=" "+b.sInfoFiltered);g+=b.sInfoPostFix;g=ja(a,g);
null!==b.fnInfoCallback&&(g=b.fnInfoCallback.call(a.oInstance,a,c,d,i,f,g));a=a.aanFeatures.i;b=0;for(c=a.length;b<c;b++)h(a[b]).html(g)}}function ja(a,b){var c=a.fnFormatNumber(a._iDisplayStart+1),d=a.fnDisplayEnd(),d=a.fnFormatNumber(d),i=a.fnRecordsDisplay(),i=a.fnFormatNumber(i),f=a.fnRecordsTotal(),f=a.fnFormatNumber(f);a.oScroll.bInfinite&&(c=a.fnFormatNumber(1));return b.replace(/_START_/g,c).replace(/_END_/g,d).replace(/_TOTAL_/g,i).replace(/_MAX_/g,f)}function ba(a){var b,c,d=a.iInitDisplayStart;
if(!1===a.bInitialised)setTimeout(function(){ba(a)},200);else{xa(a);va(a);W(a,a.aoHeader);a.nTFoot&&W(a,a.aoFooter);E(a,!0);a.oFeatures.bAutoWidth&&da(a);b=0;for(c=a.aoColumns.length;b<c;b++)null!==a.aoColumns[b].sWidth&&(a.aoColumns[b].nTh.style.width=q(a.aoColumns[b].sWidth));a.oFeatures.bSort?O(a):a.oFeatures.bFilter?K(a,a.oPreviousSearch):(a.aiDisplay=a.aiDisplayMaster.slice(),y(a),x(a));null!==a.sAjaxSource&&!a.oFeatures.bServerSide?(c=[],ka(a,c),a.fnServerData.call(a.oInstance,a.sAjaxSource,
c,function(c){var f=a.sAjaxDataProp!==""?Q(a.sAjaxDataProp)(c):c;for(b=0;b<f.length;b++)H(a,f[b]);a.iInitDisplayStart=d;if(a.oFeatures.bSort)O(a);else{a.aiDisplay=a.aiDisplayMaster.slice();y(a);x(a)}E(a,false);$(a,c)},a)):a.oFeatures.bServerSide||(E(a,!1),$(a))}}function $(a,b){a._bInitComplete=!0;A(a,"aoInitComplete","init",[a,b])}function pa(a){var b=j.defaults.oLanguage;!a.sEmptyTable&&(a.sZeroRecords&&"No data available in table"===b.sEmptyTable)&&p(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&
(a.sZeroRecords&&"Loading..."===b.sLoadingRecords)&&p(a,a,"sZeroRecords","sLoadingRecords")}function ya(a){if(a.oScroll.bInfinite)return null;var b='<select size="1" '+('name="'+a.sTableId+'_length"')+">",c,d,i=a.aLengthMenu;if(2==i.length&&"object"===typeof i[0]&&"object"===typeof i[1]){c=0;for(d=i[0].length;c<d;c++)b+='<option value="'+i[0][c]+'">'+i[1][c]+"</option>"}else{c=0;for(d=i.length;c<d;c++)b+='<option value="'+i[c]+'">'+i[c]+"</option>"}b+="</select>";i=l.createElement("div");a.aanFeatures.l||
(i.id=a.sTableId+"_length");i.className=a.oClasses.sLength;i.innerHTML="<label>"+a.oLanguage.sLengthMenu.replace("_MENU_",b)+"</label>";h('select option[value="'+a._iDisplayLength+'"]',i).attr("selected",!0);h("select",i).bind("change.DT",function(){var b=h(this).val(),i=a.aanFeatures.l;c=0;for(d=i.length;c<d;c++)i[c]!=this.parentNode&&h("select",i[c]).val(b);a._iDisplayLength=parseInt(b,10);y(a);if(a.fnDisplayEnd()==a.fnRecordsDisplay()){a._iDisplayStart=a.fnDisplayEnd()-a._iDisplayLength;if(a._iDisplayStart<
0)a._iDisplayStart=0}if(a._iDisplayLength==-1)a._iDisplayStart=0;x(a)});h("select",i).attr("aria-controls",a.sTableId);return i}function y(a){a._iDisplayEnd=!1===a.oFeatures.bPaginate?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength>a.aiDisplay.length||-1==a._iDisplayLength?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength}function Da(a){if(a.oScroll.bInfinite)return null;var b=l.createElement("div");b.className=a.oClasses.sPaging+a.sPaginationType;j.ext.oPagination[a.sPaginationType].fnInit(a,
b,function(a){y(a);x(a)});a.aanFeatures.p||a.aoDrawCallback.push({fn:function(a){j.ext.oPagination[a.sPaginationType].fnUpdate(a,function(a){y(a);x(a)})},sName:"pagination"});return b}function qa(a,b){var c=a._iDisplayStart;if("number"===typeof b)a._iDisplayStart=b*a._iDisplayLength,a._iDisplayStart>a.fnRecordsDisplay()&&(a._iDisplayStart=0);else if("first"==b)a._iDisplayStart=0;else if("previous"==b)a._iDisplayStart=0<=a._iDisplayLength?a._iDisplayStart-a._iDisplayLength:0,0>a._iDisplayStart&&(a._iDisplayStart=
0);else if("next"==b)0<=a._iDisplayLength?a._iDisplayStart+a._iDisplayLength<a.fnRecordsDisplay()&&(a._iDisplayStart+=a._iDisplayLength):a._iDisplayStart=0;else if("last"==b)if(0<=a._iDisplayLength){var d=parseInt((a.fnRecordsDisplay()-1)/a._iDisplayLength,10)+1;a._iDisplayStart=(d-1)*a._iDisplayLength}else a._iDisplayStart=0;else D(a,0,"Unknown paging action: "+b);h(a.oInstance).trigger("page",a);return c!=a._iDisplayStart}function Aa(a){var b=l.createElement("div");a.aanFeatures.r||(b.id=a.sTableId+
"_processing");b.innerHTML=a.oLanguage.sProcessing;b.className=a.oClasses.sProcessing;a.nTable.parentNode.insertBefore(b,a.nTable);return b}function E(a,b){if(a.oFeatures.bProcessing)for(var c=a.aanFeatures.r,d=0,i=c.length;d<i;d++)c[d].style.visibility=b?"visible":"hidden";h(a.oInstance).trigger("processing",[a,b])}function Ba(a){if(""===a.oScroll.sX&&""===a.oScroll.sY)return a.nTable;var b=l.createElement("div"),c=l.createElement("div"),d=l.createElement("div"),i=l.createElement("div"),f=l.createElement("div"),
g=l.createElement("div"),e=a.nTable.cloneNode(!1),j=a.nTable.cloneNode(!1),o=a.nTable.getElementsByTagName("thead")[0],k=0===a.nTable.getElementsByTagName("tfoot").length?null:a.nTable.getElementsByTagName("tfoot")[0],m=a.oClasses;c.appendChild(d);f.appendChild(g);i.appendChild(a.nTable);b.appendChild(c);b.appendChild(i);d.appendChild(e);e.appendChild(o);null!==k&&(b.appendChild(f),g.appendChild(j),j.appendChild(k));b.className=m.sScrollWrapper;c.className=m.sScrollHead;d.className=m.sScrollHeadInner;
i.className=m.sScrollBody;f.className=m.sScrollFoot;g.className=m.sScrollFootInner;a.oScroll.bAutoCss&&(c.style.overflow="hidden",c.style.position="relative",f.style.overflow="hidden",i.style.overflow="auto");c.style.border="0";c.style.width="100%";f.style.border="0";d.style.width=""!==a.oScroll.sXInner?a.oScroll.sXInner:"100%";e.removeAttribute("id");e.style.marginLeft="0";a.nTable.style.marginLeft="0";null!==k&&(j.removeAttribute("id"),j.style.marginLeft="0");d=h(a.nTable).children("caption");0<
d.length&&(d=d[0],"top"===d._captionSide?e.appendChild(d):"bottom"===d._captionSide&&k&&j.appendChild(d));""!==a.oScroll.sX&&(c.style.width=q(a.oScroll.sX),i.style.width=q(a.oScroll.sX),null!==k&&(f.style.width=q(a.oScroll.sX)),h(i).scroll(function(){c.scrollLeft=this.scrollLeft;if(k!==null)f.scrollLeft=this.scrollLeft}));""!==a.oScroll.sY&&(i.style.height=q(a.oScroll.sY));a.aoDrawCallback.push({fn:La,sName:"scrolling"});a.oScroll.bInfinite&&h(i).scroll(function(){if(!a.bDrawing&&h(this).scrollTop()!==
0&&h(this).scrollTop()+h(this).height()>h(a.nTable).height()-a.oScroll.iLoadGap&&a.fnDisplayEnd()<a.fnRecordsDisplay()){qa(a,"next");y(a);x(a)}});a.nScrollHead=c;a.nScrollFoot=f;return b}function La(a){var b=a.nScrollHead.getElementsByTagName("div")[0],c=b.getElementsByTagName("table")[0],d=a.nTable.parentNode,i,f,g,e,j,o,k,m,p=[],n=[],l=null!==a.nTFoot?a.nScrollFoot.getElementsByTagName("div")[0]:null,R=null!==a.nTFoot?l.getElementsByTagName("table")[0]:null,r=a.oBrowser.bScrollOversize,s=function(a){k=
a.style;k.paddingTop="0";k.paddingBottom="0";k.borderTopWidth="0";k.borderBottomWidth="0";k.height=0};h(a.nTable).children("thead, tfoot").remove();i=h(a.nTHead).clone()[0];a.nTable.insertBefore(i,a.nTable.childNodes[0]);g=a.nTHead.getElementsByTagName("tr");e=i.getElementsByTagName("tr");null!==a.nTFoot&&(j=h(a.nTFoot).clone()[0],a.nTable.insertBefore(j,a.nTable.childNodes[1]),o=a.nTFoot.getElementsByTagName("tr"),j=j.getElementsByTagName("tr"));""===a.oScroll.sX&&(d.style.width="100%",b.parentNode.style.width=
"100%");var t=N(a,i);i=0;for(f=t.length;i<f;i++)m=G(a,i),t[i].style.width=a.aoColumns[m].sWidth;null!==a.nTFoot&&C(function(a){a.style.width=""},j);a.oScroll.bCollapse&&""!==a.oScroll.sY&&(d.style.height=d.offsetHeight+a.nTHead.offsetHeight+"px");i=h(a.nTable).outerWidth();if(""===a.oScroll.sX){if(a.nTable.style.width="100%",r&&(h("tbody",d).height()>d.offsetHeight||"scroll"==h(d).css("overflow-y")))a.nTable.style.width=q(h(a.nTable).outerWidth()-a.oScroll.iBarWidth)}else""!==a.oScroll.sXInner?a.nTable.style.width=
q(a.oScroll.sXInner):i==h(d).width()&&h(d).height()<h(a.nTable).height()?(a.nTable.style.width=q(i-a.oScroll.iBarWidth),h(a.nTable).outerWidth()>i-a.oScroll.iBarWidth&&(a.nTable.style.width=q(i))):a.nTable.style.width=q(i);i=h(a.nTable).outerWidth();C(s,e);C(function(a){p.push(q(h(a).width()))},e);C(function(a,b){a.style.width=p[b]},g);h(e).height(0);null!==a.nTFoot&&(C(s,j),C(function(a){n.push(q(h(a).width()))},j),C(function(a,b){a.style.width=n[b]},o),h(j).height(0));C(function(a,b){a.innerHTML=
"";a.style.width=p[b]},e);null!==a.nTFoot&&C(function(a,b){a.innerHTML="";a.style.width=n[b]},j);if(h(a.nTable).outerWidth()<i){g=d.scrollHeight>d.offsetHeight||"scroll"==h(d).css("overflow-y")?i+a.oScroll.iBarWidth:i;if(r&&(d.scrollHeight>d.offsetHeight||"scroll"==h(d).css("overflow-y")))a.nTable.style.width=q(g-a.oScroll.iBarWidth);d.style.width=q(g);a.nScrollHead.style.width=q(g);null!==a.nTFoot&&(a.nScrollFoot.style.width=q(g));""===a.oScroll.sX?D(a,1,"The table cannot fit into the current element which will cause column misalignment. The table has been drawn at its minimum possible width."):
""!==a.oScroll.sXInner&&D(a,1,"The table cannot fit into the current element which will cause column misalignment. Increase the sScrollXInner value or remove it to allow automatic calculation")}else d.style.width=q("100%"),a.nScrollHead.style.width=q("100%"),null!==a.nTFoot&&(a.nScrollFoot.style.width=q("100%"));""===a.oScroll.sY&&r&&(d.style.height=q(a.nTable.offsetHeight+a.oScroll.iBarWidth));""!==a.oScroll.sY&&a.oScroll.bCollapse&&(d.style.height=q(a.oScroll.sY),r=""!==a.oScroll.sX&&a.nTable.offsetWidth>
d.offsetWidth?a.oScroll.iBarWidth:0,a.nTable.offsetHeight<d.offsetHeight&&(d.style.height=q(a.nTable.offsetHeight+r)));r=h(a.nTable).outerWidth();c.style.width=q(r);b.style.width=q(r);c=h(a.nTable).height()>d.clientHeight||"scroll"==h(d).css("overflow-y");b.style.paddingRight=c?a.oScroll.iBarWidth+"px":"0px";null!==a.nTFoot&&(R.style.width=q(r),l.style.width=q(r),l.style.paddingRight=c?a.oScroll.iBarWidth+"px":"0px");h(d).scroll();if(a.bSorted||a.bFiltered)d.scrollTop=0}function C(a,b,c){for(var d=
0,i=0,f=b.length,g,e;i<f;){g=b[i].firstChild;for(e=c?c[i].firstChild:null;g;)1===g.nodeType&&(c?a(g,e,d):a(g,d),d++),g=g.nextSibling,e=c?e.nextSibling:null;i++}}function Ma(a,b){if(!a||null===a||""===a)return 0;b||(b=l.body);var c,d=l.createElement("div");d.style.width=q(a);b.appendChild(d);c=d.offsetWidth;b.removeChild(d);return c}function da(a){var b=0,c,d=0,i=a.aoColumns.length,f,e,j=h("th",a.nTHead),o=a.nTable.getAttribute("width");e=a.nTable.parentNode;for(f=0;f<i;f++)a.aoColumns[f].bVisible&&
(d++,null!==a.aoColumns[f].sWidth&&(c=Ma(a.aoColumns[f].sWidthOrig,e),null!==c&&(a.aoColumns[f].sWidth=q(c)),b++));if(i==j.length&&0===b&&d==i&&""===a.oScroll.sX&&""===a.oScroll.sY)for(f=0;f<a.aoColumns.length;f++)c=h(j[f]).width(),null!==c&&(a.aoColumns[f].sWidth=q(c));else{b=a.nTable.cloneNode(!1);f=a.nTHead.cloneNode(!0);d=l.createElement("tbody");c=l.createElement("tr");b.removeAttribute("id");b.appendChild(f);null!==a.nTFoot&&(b.appendChild(a.nTFoot.cloneNode(!0)),C(function(a){a.style.width=
""},b.getElementsByTagName("tr")));b.appendChild(d);d.appendChild(c);d=h("thead th",b);0===d.length&&(d=h("tbody tr:eq(0)>td",b));j=N(a,f);for(f=d=0;f<i;f++){var k=a.aoColumns[f];k.bVisible&&null!==k.sWidthOrig&&""!==k.sWidthOrig?j[f-d].style.width=q(k.sWidthOrig):k.bVisible?j[f-d].style.width="":d++}for(f=0;f<i;f++)a.aoColumns[f].bVisible&&(d=Na(a,f),null!==d&&(d=d.cloneNode(!0),""!==a.aoColumns[f].sContentPadding&&(d.innerHTML+=a.aoColumns[f].sContentPadding),c.appendChild(d)));e.appendChild(b);
""!==a.oScroll.sX&&""!==a.oScroll.sXInner?b.style.width=q(a.oScroll.sXInner):""!==a.oScroll.sX?(b.style.width="",h(b).width()<e.offsetWidth&&(b.style.width=q(e.offsetWidth))):""!==a.oScroll.sY?b.style.width=q(e.offsetWidth):o&&(b.style.width=q(o));b.style.visibility="hidden";Oa(a,b);i=h("tbody tr:eq(0)",b).children();0===i.length&&(i=N(a,h("thead",b)[0]));if(""!==a.oScroll.sX){for(f=d=e=0;f<a.aoColumns.length;f++)a.aoColumns[f].bVisible&&(e=null===a.aoColumns[f].sWidthOrig?e+h(i[d]).outerWidth():
e+(parseInt(a.aoColumns[f].sWidth.replace("px",""),10)+(h(i[d]).outerWidth()-h(i[d]).width())),d++);b.style.width=q(e);a.nTable.style.width=q(e)}for(f=d=0;f<a.aoColumns.length;f++)a.aoColumns[f].bVisible&&(e=h(i[d]).width(),null!==e&&0<e&&(a.aoColumns[f].sWidth=q(e)),d++);i=h(b).css("width");a.nTable.style.width=-1!==i.indexOf("%")?i:q(h(b).outerWidth());b.parentNode.removeChild(b)}o&&(a.nTable.style.width=q(o))}function Oa(a,b){""===a.oScroll.sX&&""!==a.oScroll.sY?(h(b).width(),b.style.width=q(h(b).outerWidth()-
a.oScroll.iBarWidth)):""!==a.oScroll.sX&&(b.style.width=q(h(b).outerWidth()))}function Na(a,b){var c=Pa(a,b);if(0>c)return null;if(null===a.aoData[c].nTr){var d=l.createElement("td");d.innerHTML=v(a,c,b,"");return d}return J(a,c)[b]}function Pa(a,b){for(var c=-1,d=-1,i=0;i<a.aoData.length;i++){var e=v(a,i,b,"display")+"",e=e.replace(/<.*?>/g,"");e.length>c&&(c=e.length,d=i)}return d}function q(a){if(null===a)return"0px";if("number"==typeof a)return 0>a?"0px":a+"px";var b=a.charCodeAt(a.length-1);
return 48>b||57<b?a:a+"px"}function Qa(){var a=l.createElement("p"),b=a.style;b.width="100%";b.height="200px";b.padding="0px";var c=l.createElement("div"),b=c.style;b.position="absolute";b.top="0px";b.left="0px";b.visibility="hidden";b.width="200px";b.height="150px";b.padding="0px";b.overflow="hidden";c.appendChild(a);l.body.appendChild(c);b=a.offsetWidth;c.style.overflow="scroll";a=a.offsetWidth;b==a&&(a=c.clientWidth);l.body.removeChild(c);return b-a}function O(a,b){var c,d,i,e,g,k,o=[],m=[],p=
j.ext.oSort,l=a.aoData,q=a.aoColumns,G=a.oLanguage.oAria;if(!a.oFeatures.bServerSide&&(0!==a.aaSorting.length||null!==a.aaSortingFixed)){o=null!==a.aaSortingFixed?a.aaSortingFixed.concat(a.aaSorting):a.aaSorting.slice();for(c=0;c<o.length;c++)if(d=o[c][0],i=R(a,d),e=a.aoColumns[d].sSortDataType,j.ext.afnSortData[e])if(g=j.ext.afnSortData[e].call(a.oInstance,a,d,i),g.length===l.length){i=0;for(e=l.length;i<e;i++)F(a,i,d,g[i])}else D(a,0,"Returned data sort array (col "+d+") is the wrong length");c=
0;for(d=a.aiDisplayMaster.length;c<d;c++)m[a.aiDisplayMaster[c]]=c;var r=o.length,s;c=0;for(d=l.length;c<d;c++)for(i=0;i<r;i++){s=q[o[i][0]].aDataSort;g=0;for(k=s.length;g<k;g++)e=q[s[g]].sType,e=p[(e?e:"string")+"-pre"],l[c]._aSortData[s[g]]=e?e(v(a,c,s[g],"sort")):v(a,c,s[g],"sort")}a.aiDisplayMaster.sort(function(a,b){var c,d,e,i,f;for(c=0;c<r;c++){f=q[o[c][0]].aDataSort;d=0;for(e=f.length;d<e;d++)if(i=q[f[d]].sType,i=p[(i?i:"string")+"-"+o[c][1]](l[a]._aSortData[f[d]],l[b]._aSortData[f[d]]),0!==
i)return i}return p["numeric-asc"](m[a],m[b])})}(b===n||b)&&!a.oFeatures.bDeferRender&&P(a);c=0;for(d=a.aoColumns.length;c<d;c++)e=q[c].sTitle.replace(/<.*?>/g,""),i=q[c].nTh,i.removeAttribute("aria-sort"),i.removeAttribute("aria-label"),q[c].bSortable?0<o.length&&o[0][0]==c?(i.setAttribute("aria-sort","asc"==o[0][1]?"ascending":"descending"),i.setAttribute("aria-label",e+("asc"==(q[c].asSorting[o[0][2]+1]?q[c].asSorting[o[0][2]+1]:q[c].asSorting[0])?G.sSortAscending:G.sSortDescending))):i.setAttribute("aria-label",
e+("asc"==q[c].asSorting[0]?G.sSortAscending:G.sSortDescending)):i.setAttribute("aria-label",e);a.bSorted=!0;h(a.oInstance).trigger("sort",a);a.oFeatures.bFilter?K(a,a.oPreviousSearch,1):(a.aiDisplay=a.aiDisplayMaster.slice(),a._iDisplayStart=0,y(a),x(a))}function ia(a,b,c,d){Ra(b,{},function(b){if(!1!==a.aoColumns[c].bSortable){var e=function(){var d,e;if(b.shiftKey){for(var f=!1,h=0;h<a.aaSorting.length;h++)if(a.aaSorting[h][0]==c){f=!0;d=a.aaSorting[h][0];e=a.aaSorting[h][2]+1;a.aoColumns[d].asSorting[e]?
(a.aaSorting[h][1]=a.aoColumns[d].asSorting[e],a.aaSorting[h][2]=e):a.aaSorting.splice(h,1);break}!1===f&&a.aaSorting.push([c,a.aoColumns[c].asSorting[0],0])}else 1==a.aaSorting.length&&a.aaSorting[0][0]==c?(d=a.aaSorting[0][0],e=a.aaSorting[0][2]+1,a.aoColumns[d].asSorting[e]||(e=0),a.aaSorting[0][1]=a.aoColumns[d].asSorting[e],a.aaSorting[0][2]=e):(a.aaSorting.splice(0,a.aaSorting.length),a.aaSorting.push([c,a.aoColumns[c].asSorting[0],0]));O(a)};a.oFeatures.bProcessing?(E(a,!0),setTimeout(function(){e();
a.oFeatures.bServerSide||E(a,!1)},0)):e();"function"==typeof d&&d(a)}})}function P(a){var b,c,d,e,f,g=a.aoColumns.length,j=a.oClasses;for(b=0;b<g;b++)a.aoColumns[b].bSortable&&h(a.aoColumns[b].nTh).removeClass(j.sSortAsc+" "+j.sSortDesc+" "+a.aoColumns[b].sSortingClass);c=null!==a.aaSortingFixed?a.aaSortingFixed.concat(a.aaSorting):a.aaSorting.slice();for(b=0;b<a.aoColumns.length;b++)if(a.aoColumns[b].bSortable){f=a.aoColumns[b].sSortingClass;e=-1;for(d=0;d<c.length;d++)if(c[d][0]==b){f="asc"==c[d][1]?
j.sSortAsc:j.sSortDesc;e=d;break}h(a.aoColumns[b].nTh).addClass(f);a.bJUI&&(f=h("span."+j.sSortIcon,a.aoColumns[b].nTh),f.removeClass(j.sSortJUIAsc+" "+j.sSortJUIDesc+" "+j.sSortJUI+" "+j.sSortJUIAscAllowed+" "+j.sSortJUIDescAllowed),f.addClass(-1==e?a.aoColumns[b].sSortingClassJUI:"asc"==c[e][1]?j.sSortJUIAsc:j.sSortJUIDesc))}else h(a.aoColumns[b].nTh).addClass(a.aoColumns[b].sSortingClass);f=j.sSortColumn;if(a.oFeatures.bSort&&a.oFeatures.bSortClasses){a=J(a);e=[];for(b=0;b<g;b++)e.push("");b=0;
for(d=1;b<c.length;b++)j=parseInt(c[b][0],10),e[j]=f+d,3>d&&d++;f=RegExp(f+"[123]");var o;b=0;for(c=a.length;b<c;b++)j=b%g,d=a[b].className,o=e[j],j=d.replace(f,o),j!=d?a[b].className=h.trim(j):0<o.length&&-1==d.indexOf(o)&&(a[b].className=d+" "+o)}}function ra(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b,c;b=a.oScroll.bInfinite;var d={iCreate:(new Date).getTime(),iStart:b?0:a._iDisplayStart,iEnd:b?a._iDisplayLength:a._iDisplayEnd,iLength:a._iDisplayLength,aaSorting:h.extend(!0,[],a.aaSorting),
oSearch:h.extend(!0,{},a.oPreviousSearch),aoSearchCols:h.extend(!0,[],a.aoPreSearchCols),abVisCols:[]};b=0;for(c=a.aoColumns.length;b<c;b++)d.abVisCols.push(a.aoColumns[b].bVisible);A(a,"aoStateSaveParams","stateSaveParams",[a,d]);a.fnStateSave.call(a.oInstance,a,d)}}function Sa(a,b){if(a.oFeatures.bStateSave){var c=a.fnStateLoad.call(a.oInstance,a);if(c){var d=A(a,"aoStateLoadParams","stateLoadParams",[a,c]);if(-1===h.inArray(!1,d)){a.oLoadedState=h.extend(!0,{},c);a._iDisplayStart=c.iStart;a.iInitDisplayStart=
c.iStart;a._iDisplayEnd=c.iEnd;a._iDisplayLength=c.iLength;a.aaSorting=c.aaSorting.slice();a.saved_aaSorting=c.aaSorting.slice();h.extend(a.oPreviousSearch,c.oSearch);h.extend(!0,a.aoPreSearchCols,c.aoSearchCols);b.saved_aoColumns=[];for(d=0;d<c.abVisCols.length;d++)b.saved_aoColumns[d]={},b.saved_aoColumns[d].bVisible=c.abVisCols[d];A(a,"aoStateLoaded","stateLoaded",[a,c])}}}}function s(a){for(var b=0;b<j.settings.length;b++)if(j.settings[b].nTable===a)return j.settings[b];return null}function T(a){for(var b=
[],a=a.aoData,c=0,d=a.length;c<d;c++)null!==a[c].nTr&&b.push(a[c].nTr);return b}function J(a,b){var c=[],d,e,f,g,h,j;e=0;var o=a.aoData.length;b!==n&&(e=b,o=b+1);for(f=e;f<o;f++)if(j=a.aoData[f],null!==j.nTr){e=[];for(d=j.nTr.firstChild;d;)g=d.nodeName.toLowerCase(),("td"==g||"th"==g)&&e.push(d),d=d.nextSibling;g=d=0;for(h=a.aoColumns.length;g<h;g++)a.aoColumns[g].bVisible?c.push(e[g-d]):(c.push(j._anHidden[g]),d++)}return c}function D(a,b,c){a=null===a?"DataTables warning: "+c:"DataTables warning (table id = '"+
a.sTableId+"'): "+c;if(0===b)if("alert"==j.ext.sErrMode)alert(a);else throw Error(a);else X.console&&console.log&&console.log(a)}function p(a,b,c,d){d===n&&(d=c);b[c]!==n&&(a[d]=b[c])}function Ta(a,b){var c,d;for(d in b)b.hasOwnProperty(d)&&(c=b[d],"object"===typeof e[d]&&null!==c&&!1===h.isArray(c)?h.extend(!0,a[d],c):a[d]=c);return a}function Ra(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&c(a)}).bind("selectstart.DT",function(){return!1})}
function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function A(a,b,c,d){for(var b=a[b],e=[],f=b.length-1;0<=f;f--)e.push(b[f].fn.apply(a.oInstance,d));null!==c&&h(a.oInstance).trigger(c,d);return e}function Ua(a){var b=h('<div style="position:absolute; top:0; left:0; height:1px; width:1px; overflow:hidden"><div style="position:absolute; top:1px; left:1px; width:100px; overflow:scroll;"><div id="DT_BrowserTest" style="width:100%; height:10px;"></div></div></div>')[0];l.body.appendChild(b);a.oBrowser.bScrollOversize=
100===h("#DT_BrowserTest",b)[0].offsetWidth?!0:!1;l.body.removeChild(b)}function Va(a){return function(){var b=[s(this[j.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return j.ext.oApi[a].apply(this,b)}}var U=/\[.*?\]$/,Wa=X.JSON?JSON.stringify:function(a){var b=typeof a;if("object"!==b||null===a)return"string"===b&&(a='"'+a+'"'),a+"";var c,d,e=[],f=h.isArray(a);for(c in a)d=a[c],b=typeof d,"string"===b?d='"'+d+'"':"object"===b&&null!==d&&(d=Wa(d)),e.push((f?"":'"'+c+'":')+d);return(f?
"[":"{")+e+(f?"]":"}")};this.$=function(a,b){var c,d,e=[],f;d=s(this[j.ext.iApiIndex]);var g=d.aoData,o=d.aiDisplay,k=d.aiDisplayMaster;b||(b={});b=h.extend({},{filter:"none",order:"current",page:"all"},b);if("current"==b.page){c=d._iDisplayStart;for(d=d.fnDisplayEnd();c<d;c++)(f=g[o[c]].nTr)&&e.push(f)}else if("current"==b.order&&"none"==b.filter){c=0;for(d=k.length;c<d;c++)(f=g[k[c]].nTr)&&e.push(f)}else if("current"==b.order&&"applied"==b.filter){c=0;for(d=o.length;c<d;c++)(f=g[o[c]].nTr)&&e.push(f)}else if("original"==
b.order&&"none"==b.filter){c=0;for(d=g.length;c<d;c++)(f=g[c].nTr)&&e.push(f)}else if("original"==b.order&&"applied"==b.filter){c=0;for(d=g.length;c<d;c++)f=g[c].nTr,-1!==h.inArray(c,o)&&f&&e.push(f)}else D(d,1,"Unknown selection options");e=h(e);c=e.filter(a);e=e.find(a);return h([].concat(h.makeArray(c),h.makeArray(e)))};this._=function(a,b){var c=[],d,e,f=this.$(a,b);d=0;for(e=f.length;d<e;d++)c.push(this.fnGetData(f[d]));return c};this.fnAddData=function(a,b){if(0===a.length)return[];var c=[],
d,e=s(this[j.ext.iApiIndex]);if("object"===typeof a[0]&&null!==a[0])for(var f=0;f<a.length;f++){d=H(e,a[f]);if(-1==d)return c;c.push(d)}else{d=H(e,a);if(-1==d)return c;c.push(d)}e.aiDisplay=e.aiDisplayMaster.slice();(b===n||b)&&aa(e);return c};this.fnAdjustColumnSizing=function(a){var b=s(this[j.ext.iApiIndex]);k(b);a===n||a?this.fnDraw(!1):(""!==b.oScroll.sX||""!==b.oScroll.sY)&&this.oApi._fnScrollDraw(b)};this.fnClearTable=function(a){var b=s(this[j.ext.iApiIndex]);ga(b);(a===n||a)&&x(b)};this.fnClose=
function(a){for(var b=s(this[j.ext.iApiIndex]),c=0;c<b.aoOpenRows.length;c++)if(b.aoOpenRows[c].nParent==a)return(a=b.aoOpenRows[c].nTr.parentNode)&&a.removeChild(b.aoOpenRows[c].nTr),b.aoOpenRows.splice(c,1),0;return 1};this.fnDeleteRow=function(a,b,c){var d=s(this[j.ext.iApiIndex]),e,f,a="object"===typeof a?I(d,a):a,g=d.aoData.splice(a,1);e=0;for(f=d.aoData.length;e<f;e++)null!==d.aoData[e].nTr&&(d.aoData[e].nTr._DT_RowIndex=e);e=h.inArray(a,d.aiDisplay);d.asDataSearch.splice(e,1);ha(d.aiDisplayMaster,
a);ha(d.aiDisplay,a);"function"===typeof b&&b.call(this,d,g);d._iDisplayStart>=d.fnRecordsDisplay()&&(d._iDisplayStart-=d._iDisplayLength,0>d._iDisplayStart&&(d._iDisplayStart=0));if(c===n||c)y(d),x(d);return g};this.fnDestroy=function(a){var b=s(this[j.ext.iApiIndex]),c=b.nTableWrapper.parentNode,d=b.nTBody,i,f,a=a===n?!1:a;b.bDestroying=!0;A(b,"aoDestroyCallback","destroy",[b]);if(!a){i=0;for(f=b.aoColumns.length;i<f;i++)!1===b.aoColumns[i].bVisible&&this.fnSetColumnVis(i,!0)}h(b.nTableWrapper).find("*").andSelf().unbind(".DT");
h("tbody>tr>td."+b.oClasses.sRowEmpty,b.nTable).parent().remove();b.nTable!=b.nTHead.parentNode&&(h(b.nTable).children("thead").remove(),b.nTable.appendChild(b.nTHead));b.nTFoot&&b.nTable!=b.nTFoot.parentNode&&(h(b.nTable).children("tfoot").remove(),b.nTable.appendChild(b.nTFoot));b.nTable.parentNode.removeChild(b.nTable);h(b.nTableWrapper).remove();b.aaSorting=[];b.aaSortingFixed=[];P(b);h(T(b)).removeClass(b.asStripeClasses.join(" "));h("th, td",b.nTHead).removeClass([b.oClasses.sSortable,b.oClasses.sSortableAsc,
b.oClasses.sSortableDesc,b.oClasses.sSortableNone].join(" "));b.bJUI&&(h("th span."+b.oClasses.sSortIcon+", td span."+b.oClasses.sSortIcon,b.nTHead).remove(),h("th, td",b.nTHead).each(function(){var a=h("div."+b.oClasses.sSortJUIWrapper,this),c=a.contents();h(this).append(c);a.remove()}));!a&&b.nTableReinsertBefore?c.insertBefore(b.nTable,b.nTableReinsertBefore):a||c.appendChild(b.nTable);i=0;for(f=b.aoData.length;i<f;i++)null!==b.aoData[i].nTr&&d.appendChild(b.aoData[i].nTr);!0===b.oFeatures.bAutoWidth&&
(b.nTable.style.width=q(b.sDestroyWidth));if(f=b.asDestroyStripes.length){a=h(d).children("tr");for(i=0;i<f;i++)a.filter(":nth-child("+f+"n + "+i+")").addClass(b.asDestroyStripes[i])}i=0;for(f=j.settings.length;i<f;i++)j.settings[i]==b&&j.settings.splice(i,1);e=b=null};this.fnDraw=function(a){var b=s(this[j.ext.iApiIndex]);!1===a?(y(b),x(b)):aa(b)};this.fnFilter=function(a,b,c,d,e,f){var g=s(this[j.ext.iApiIndex]);if(g.oFeatures.bFilter){if(c===n||null===c)c=!1;if(d===n||null===d)d=!0;if(e===n||null===
e)e=!0;if(f===n||null===f)f=!0;if(b===n||null===b){if(K(g,{sSearch:a+"",bRegex:c,bSmart:d,bCaseInsensitive:f},1),e&&g.aanFeatures.f){b=g.aanFeatures.f;c=0;for(d=b.length;c<d;c++)try{b[c]._DT_Input!=l.activeElement&&h(b[c]._DT_Input).val(a)}catch(o){h(b[c]._DT_Input).val(a)}}}else h.extend(g.aoPreSearchCols[b],{sSearch:a+"",bRegex:c,bSmart:d,bCaseInsensitive:f}),K(g,g.oPreviousSearch,1)}};this.fnGetData=function(a,b){var c=s(this[j.ext.iApiIndex]);if(a!==n){var d=a;if("object"===typeof a){var e=a.nodeName.toLowerCase();
"tr"===e?d=I(c,a):"td"===e&&(d=I(c,a.parentNode),b=fa(c,d,a))}return b!==n?v(c,d,b,""):c.aoData[d]!==n?c.aoData[d]._aData:null}return Z(c)};this.fnGetNodes=function(a){var b=s(this[j.ext.iApiIndex]);return a!==n?b.aoData[a]!==n?b.aoData[a].nTr:null:T(b)};this.fnGetPosition=function(a){var b=s(this[j.ext.iApiIndex]),c=a.nodeName.toUpperCase();return"TR"==c?I(b,a):"TD"==c||"TH"==c?(c=I(b,a.parentNode),a=fa(b,c,a),[c,R(b,a),a]):null};this.fnIsOpen=function(a){for(var b=s(this[j.ext.iApiIndex]),c=0;c<
b.aoOpenRows.length;c++)if(b.aoOpenRows[c].nParent==a)return!0;return!1};this.fnOpen=function(a,b,c){var d=s(this[j.ext.iApiIndex]),e=T(d);if(-1!==h.inArray(a,e)){this.fnClose(a);var e=l.createElement("tr"),f=l.createElement("td");e.appendChild(f);f.className=c;f.colSpan=t(d);"string"===typeof b?f.innerHTML=b:h(f).html(b);b=h("tr",d.nTBody);-1!=h.inArray(a,b)&&h(e).insertAfter(a);d.aoOpenRows.push({nTr:e,nParent:a});return e}};this.fnPageChange=function(a,b){var c=s(this[j.ext.iApiIndex]);qa(c,a);
y(c);(b===n||b)&&x(c)};this.fnSetColumnVis=function(a,b,c){var d=s(this[j.ext.iApiIndex]),e,f,g=d.aoColumns,h=d.aoData,o,m;if(g[a].bVisible!=b){if(b){for(e=f=0;e<a;e++)g[e].bVisible&&f++;m=f>=t(d);if(!m)for(e=a;e<g.length;e++)if(g[e].bVisible){o=e;break}e=0;for(f=h.length;e<f;e++)null!==h[e].nTr&&(m?h[e].nTr.appendChild(h[e]._anHidden[a]):h[e].nTr.insertBefore(h[e]._anHidden[a],J(d,e)[o]))}else{e=0;for(f=h.length;e<f;e++)null!==h[e].nTr&&(o=J(d,e)[a],h[e]._anHidden[a]=o,o.parentNode.removeChild(o))}g[a].bVisible=
b;W(d,d.aoHeader);d.nTFoot&&W(d,d.aoFooter);e=0;for(f=d.aoOpenRows.length;e<f;e++)d.aoOpenRows[e].nTr.colSpan=t(d);if(c===n||c)k(d),x(d);ra(d)}};this.fnSettings=function(){return s(this[j.ext.iApiIndex])};this.fnSort=function(a){var b=s(this[j.ext.iApiIndex]);b.aaSorting=a;O(b)};this.fnSortListener=function(a,b,c){ia(s(this[j.ext.iApiIndex]),a,b,c)};this.fnUpdate=function(a,b,c,d,e){var f=s(this[j.ext.iApiIndex]),b="object"===typeof b?I(f,b):b;if(h.isArray(a)&&c===n){f.aoData[b]._aData=a.slice();
for(c=0;c<f.aoColumns.length;c++)this.fnUpdate(v(f,b,c),b,c,!1,!1)}else if(h.isPlainObject(a)&&c===n){f.aoData[b]._aData=h.extend(!0,{},a);for(c=0;c<f.aoColumns.length;c++)this.fnUpdate(v(f,b,c),b,c,!1,!1)}else{F(f,b,c,a);var a=v(f,b,c,"display"),g=f.aoColumns[c];null!==g.fnRender&&(a=S(f,b,c),g.bUseRendered&&F(f,b,c,a));null!==f.aoData[b].nTr&&(J(f,b)[c].innerHTML=a)}c=h.inArray(b,f.aiDisplay);f.asDataSearch[c]=na(f,Y(f,b,"filter",r(f,"bSearchable")));(e===n||e)&&k(f);(d===n||d)&&aa(f);return 0};
this.fnVersionCheck=j.ext.fnVersionCheck;this.oApi={_fnExternApiFunc:Va,_fnInitialise:ba,_fnInitComplete:$,_fnLanguageCompat:pa,_fnAddColumn:o,_fnColumnOptions:m,_fnAddData:H,_fnCreateTr:ea,_fnGatherData:ua,_fnBuildHead:va,_fnDrawHead:W,_fnDraw:x,_fnReDraw:aa,_fnAjaxUpdate:wa,_fnAjaxParameters:Ea,_fnAjaxUpdateDraw:Fa,_fnServerParams:ka,_fnAddOptionsHtml:xa,_fnFeatureHtmlTable:Ba,_fnScrollDraw:La,_fnAdjustColumnSizing:k,_fnFeatureHtmlFilter:za,_fnFilterComplete:K,_fnFilterCustom:Ia,_fnFilterColumn:Ha,
_fnFilter:Ga,_fnBuildSearchArray:la,_fnBuildSearchRow:na,_fnFilterCreateSearch:ma,_fnDataToSearch:Ja,_fnSort:O,_fnSortAttachListener:ia,_fnSortingClasses:P,_fnFeatureHtmlPaginate:Da,_fnPageChange:qa,_fnFeatureHtmlInfo:Ca,_fnUpdateInfo:Ka,_fnFeatureHtmlLength:ya,_fnFeatureHtmlProcessing:Aa,_fnProcessingDisplay:E,_fnVisibleToColumnIndex:G,_fnColumnIndexToVisible:R,_fnNodeToDataIndex:I,_fnVisbleColumns:t,_fnCalculateEnd:y,_fnConvertToWidth:Ma,_fnCalculateColumnWidths:da,_fnScrollingWidthAdjust:Oa,_fnGetWidestNode:Na,
_fnGetMaxLenString:Pa,_fnStringToCss:q,_fnDetectType:B,_fnSettingsFromNode:s,_fnGetDataMaster:Z,_fnGetTrNodes:T,_fnGetTdNodes:J,_fnEscapeRegex:oa,_fnDeleteIndex:ha,_fnReOrderIndex:u,_fnColumnOrdering:M,_fnLog:D,_fnClearTable:ga,_fnSaveState:ra,_fnLoadState:Sa,_fnCreateCookie:function(a,b,c,d,e){var f=new Date;f.setTime(f.getTime()+1E3*c);var c=X.location.pathname.split("/"),a=a+"_"+c.pop().replace(/[\/:]/g,"").toLowerCase(),g;null!==e?(g="function"===typeof h.parseJSON?h.parseJSON(b):eval("("+b+")"),
b=e(a,g,f.toGMTString(),c.join("/")+"/")):b=a+"="+encodeURIComponent(b)+"; expires="+f.toGMTString()+"; path="+c.join("/")+"/";a=l.cookie.split(";");e=b.split(";")[0].length;f=[];if(4096<e+l.cookie.length+10){for(var j=0,o=a.length;j<o;j++)if(-1!=a[j].indexOf(d)){var k=a[j].split("=");try{(g=eval("("+decodeURIComponent(k[1])+")"))&&g.iCreate&&f.push({name:k[0],time:g.iCreate})}catch(m){}}for(f.sort(function(a,b){return b.time-a.time});4096<e+l.cookie.length+10;){if(0===f.length)return;d=f.pop();l.cookie=
d.name+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path="+c.join("/")+"/"}}l.cookie=b},_fnReadCookie:function(a){for(var b=X.location.pathname.split("/"),a=a+"_"+b[b.length-1].replace(/[\/:]/g,"").toLowerCase()+"=",b=l.cookie.split(";"),c=0;c<b.length;c++){for(var d=b[c];" "==d.charAt(0);)d=d.substring(1,d.length);if(0===d.indexOf(a))return decodeURIComponent(d.substring(a.length,d.length))}return null},_fnDetectHeader:V,_fnGetUniqueThs:N,_fnScrollBarWidth:Qa,_fnApplyToChildren:C,_fnMap:p,_fnGetRowData:Y,
_fnGetCellData:v,_fnSetCellData:F,_fnGetObjectDataFn:Q,_fnSetObjectDataFn:L,_fnApplyColumnDefs:ta,_fnBindAction:Ra,_fnExtend:Ta,_fnCallbackReg:z,_fnCallbackFire:A,_fnJsonString:Wa,_fnRender:S,_fnNodeToColumnIndex:fa,_fnInfoMacros:ja,_fnBrowserDetect:Ua,_fnGetColumns:r};h.extend(j.ext.oApi,this.oApi);for(var sa in j.ext.oApi)sa&&(this[sa]=Va(sa));var ca=this;this.each(function(){var a=0,b,c,d;c=this.getAttribute("id");var i=!1,f=!1;if("table"!=this.nodeName.toLowerCase())D(null,0,"Attempted to initialise DataTables on a node which is not a table: "+
this.nodeName);else{a=0;for(b=j.settings.length;a<b;a++){if(j.settings[a].nTable==this){if(e===n||e.bRetrieve)return j.settings[a].oInstance;if(e.bDestroy){j.settings[a].oInstance.fnDestroy();break}else{D(j.settings[a],0,"Cannot reinitialise DataTable.\n\nTo retrieve the DataTables object for this table, pass no arguments or see the docs for bRetrieve and bDestroy");return}}if(j.settings[a].sTableId==this.id){j.settings.splice(a,1);break}}if(null===c||""===c)this.id=c="DataTables_Table_"+j.ext._oExternConfig.iNextUnique++;
var g=h.extend(!0,{},j.models.oSettings,{nTable:this,oApi:ca.oApi,oInit:e,sDestroyWidth:h(this).width(),sInstance:c,sTableId:c});j.settings.push(g);g.oInstance=1===ca.length?ca:h(this).dataTable();e||(e={});e.oLanguage&&pa(e.oLanguage);e=Ta(h.extend(!0,{},j.defaults),e);p(g.oFeatures,e,"bPaginate");p(g.oFeatures,e,"bLengthChange");p(g.oFeatures,e,"bFilter");p(g.oFeatures,e,"bSort");p(g.oFeatures,e,"bInfo");p(g.oFeatures,e,"bProcessing");p(g.oFeatures,e,"bAutoWidth");p(g.oFeatures,e,"bSortClasses");
p(g.oFeatures,e,"bServerSide");p(g.oFeatures,e,"bDeferRender");p(g.oScroll,e,"sScrollX","sX");p(g.oScroll,e,"sScrollXInner","sXInner");p(g.oScroll,e,"sScrollY","sY");p(g.oScroll,e,"bScrollCollapse","bCollapse");p(g.oScroll,e,"bScrollInfinite","bInfinite");p(g.oScroll,e,"iScrollLoadGap","iLoadGap");p(g.oScroll,e,"bScrollAutoCss","bAutoCss");p(g,e,"asStripeClasses");p(g,e,"asStripClasses","asStripeClasses");p(g,e,"fnServerData");p(g,e,"fnFormatNumber");p(g,e,"sServerMethod");p(g,e,"aaSorting");p(g,
e,"aaSortingFixed");p(g,e,"aLengthMenu");p(g,e,"sPaginationType");p(g,e,"sAjaxSource");p(g,e,"sAjaxDataProp");p(g,e,"iCookieDuration");p(g,e,"sCookiePrefix");p(g,e,"sDom");p(g,e,"bSortCellsTop");p(g,e,"iTabIndex");p(g,e,"oSearch","oPreviousSearch");p(g,e,"aoSearchCols","aoPreSearchCols");p(g,e,"iDisplayLength","_iDisplayLength");p(g,e,"bJQueryUI","bJUI");p(g,e,"fnCookieCallback");p(g,e,"fnStateLoad");p(g,e,"fnStateSave");p(g.oLanguage,e,"fnInfoCallback");z(g,"aoDrawCallback",e.fnDrawCallback,"user");
z(g,"aoServerParams",e.fnServerParams,"user");z(g,"aoStateSaveParams",e.fnStateSaveParams,"user");z(g,"aoStateLoadParams",e.fnStateLoadParams,"user");z(g,"aoStateLoaded",e.fnStateLoaded,"user");z(g,"aoRowCallback",e.fnRowCallback,"user");z(g,"aoRowCreatedCallback",e.fnCreatedRow,"user");z(g,"aoHeaderCallback",e.fnHeaderCallback,"user");z(g,"aoFooterCallback",e.fnFooterCallback,"user");z(g,"aoInitComplete",e.fnInitComplete,"user");z(g,"aoPreDrawCallback",e.fnPreDrawCallback,"user");g.oFeatures.bServerSide&&
g.oFeatures.bSort&&g.oFeatures.bSortClasses?z(g,"aoDrawCallback",P,"server_side_sort_classes"):g.oFeatures.bDeferRender&&z(g,"aoDrawCallback",P,"defer_sort_classes");e.bJQueryUI?(h.extend(g.oClasses,j.ext.oJUIClasses),e.sDom===j.defaults.sDom&&"lfrtip"===j.defaults.sDom&&(g.sDom='<"H"lfr>t<"F"ip>')):h.extend(g.oClasses,j.ext.oStdClasses);h(this).addClass(g.oClasses.sTable);if(""!==g.oScroll.sX||""!==g.oScroll.sY)g.oScroll.iBarWidth=Qa();g.iInitDisplayStart===n&&(g.iInitDisplayStart=e.iDisplayStart,
g._iDisplayStart=e.iDisplayStart);e.bStateSave&&(g.oFeatures.bStateSave=!0,Sa(g,e),z(g,"aoDrawCallback",ra,"state_save"));null!==e.iDeferLoading&&(g.bDeferLoading=!0,a=h.isArray(e.iDeferLoading),g._iRecordsDisplay=a?e.iDeferLoading[0]:e.iDeferLoading,g._iRecordsTotal=a?e.iDeferLoading[1]:e.iDeferLoading);null!==e.aaData&&(f=!0);""!==e.oLanguage.sUrl?(g.oLanguage.sUrl=e.oLanguage.sUrl,h.getJSON(g.oLanguage.sUrl,null,function(a){pa(a);h.extend(true,g.oLanguage,e.oLanguage,a);ba(g)}),i=!0):h.extend(!0,
g.oLanguage,e.oLanguage);null===e.asStripeClasses&&(g.asStripeClasses=[g.oClasses.sStripeOdd,g.oClasses.sStripeEven]);b=g.asStripeClasses.length;g.asDestroyStripes=[];if(b){c=!1;d=h(this).children("tbody").children("tr:lt("+b+")");for(a=0;a<b;a++)d.hasClass(g.asStripeClasses[a])&&(c=!0,g.asDestroyStripes.push(g.asStripeClasses[a]));c&&d.removeClass(g.asStripeClasses.join(" "))}c=[];a=this.getElementsByTagName("thead");0!==a.length&&(V(g.aoHeader,a[0]),c=N(g));if(null===e.aoColumns){d=[];a=0;for(b=
c.length;a<b;a++)d.push(null)}else d=e.aoColumns;a=0;for(b=d.length;a<b;a++)e.saved_aoColumns!==n&&e.saved_aoColumns.length==b&&(null===d[a]&&(d[a]={}),d[a].bVisible=e.saved_aoColumns[a].bVisible),o(g,c?c[a]:null);ta(g,e.aoColumnDefs,d,function(a,b){m(g,a,b)});a=0;for(b=g.aaSorting.length;a<b;a++){g.aaSorting[a][0]>=g.aoColumns.length&&(g.aaSorting[a][0]=0);var k=g.aoColumns[g.aaSorting[a][0]];g.aaSorting[a][2]===n&&(g.aaSorting[a][2]=0);e.aaSorting===n&&g.saved_aaSorting===n&&(g.aaSorting[a][1]=
k.asSorting[0]);c=0;for(d=k.asSorting.length;c<d;c++)if(g.aaSorting[a][1]==k.asSorting[c]){g.aaSorting[a][2]=c;break}}P(g);Ua(g);a=h(this).children("caption").each(function(){this._captionSide=h(this).css("caption-side")});b=h(this).children("thead");0===b.length&&(b=[l.createElement("thead")],this.appendChild(b[0]));g.nTHead=b[0];b=h(this).children("tbody");0===b.length&&(b=[l.createElement("tbody")],this.appendChild(b[0]));g.nTBody=b[0];g.nTBody.setAttribute("role","alert");g.nTBody.setAttribute("aria-live",
"polite");g.nTBody.setAttribute("aria-relevant","all");b=h(this).children("tfoot");if(0===b.length&&0<a.length&&(""!==g.oScroll.sX||""!==g.oScroll.sY))b=[l.createElement("tfoot")],this.appendChild(b[0]);0<b.length&&(g.nTFoot=b[0],V(g.aoFooter,g.nTFoot));if(f)for(a=0;a<e.aaData.length;a++)H(g,e.aaData[a]);else ua(g);g.aiDisplay=g.aiDisplayMaster.slice();g.bInitialised=!0;!1===i&&ba(g)}});ca=null;return this};j.fnVersionCheck=function(e){for(var h=function(e,h){for(;e.length<h;)e+="0";return e},m=j.ext.sVersion.split("."),
e=e.split("."),k="",n="",l=0,t=e.length;l<t;l++)k+=h(m[l],3),n+=h(e[l],3);return parseInt(k,10)>=parseInt(n,10)};j.fnIsDataTable=function(e){for(var h=j.settings,m=0;m<h.length;m++)if(h[m].nTable===e||h[m].nScrollHead===e||h[m].nScrollFoot===e)return!0;return!1};j.fnTables=function(e){var o=[];jQuery.each(j.settings,function(j,k){(!e||!0===e&&h(k.nTable).is(":visible"))&&o.push(k.nTable)});return o};j.version="1.9.4";j.settings=[];j.models={};j.models.ext={afnFiltering:[],afnSortData:[],aoFeatures:[],
aTypes:[],fnVersionCheck:j.fnVersionCheck,iApiIndex:0,ofnSearch:{},oApi:{},oStdClasses:{},oJUIClasses:{},oPagination:{},oSort:{},sVersion:j.version,sErrMode:"alert",_oExternConfig:{iNextUnique:0}};j.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};j.models.oRow={nTr:null,_aData:[],_aSortData:[],_anHidden:[],_sRowStripe:""};j.models.oColumn={aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bUseRendered:null,bVisible:null,_bAutoType:!0,fnCreatedCell:null,fnGetData:null,
fnRender:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};j.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,
bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollAutoCss:!0,bScrollCollapse:!1,bScrollInfinite:!1,bServerSide:!1,bSort:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCookieCallback:null,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(e){if(1E3>e)return e;for(var h=e+"",e=h.split(""),j="",h=h.length,k=0;k<h;k++)0===k%3&&0!==k&&(j=this.oLanguage.sInfoThousands+j),j=e[h-k-1]+j;return j},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,
fnRowCallback:null,fnServerData:function(e,j,m,k){k.jqXHR=h.ajax({url:e,data:j,success:function(e){e.sError&&k.oApi._fnLog(k,0,e.sError);h(k.oInstance).trigger("xhr",[k,e]);m(e)},dataType:"json",cache:!1,type:k.sServerMethod,error:function(e,h){"parsererror"==h&&k.oApi._fnLog(k,0,"DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.")}})},fnServerParams:null,fnStateLoad:function(e){var e=this.oApi._fnReadCookie(e.sCookiePrefix+e.sInstance),j;try{j=
"function"===typeof h.parseJSON?h.parseJSON(e):eval("("+e+")")}catch(m){j=null}return j},fnStateLoadParams:null,fnStateLoaded:null,fnStateSave:function(e,h){this.oApi._fnCreateCookie(e.sCookiePrefix+e.sInstance,this.oApi._fnJsonString(h),e.iCookieDuration,e.sCookiePrefix,e.fnCookieCallback)},fnStateSaveParams:null,iCookieDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iScrollLoadGap:100,iTabIndex:0,oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},
oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sInfoThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},j.models.oSearch),sAjaxDataProp:"aaData",
sAjaxSource:null,sCookiePrefix:"SpryMedia_DataTables_",sDom:"lfrtip",sPaginationType:"two_button",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET"};j.defaults.columns={aDataSort:null,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bUseRendered:!0,bVisible:!0,fnCreatedCell:null,fnRender:null,iDataSort:-1,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};j.models.oSettings={oFeatures:{bAutoWidth:null,
bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortClasses:null,bStateSave:null},oScroll:{bAutoCss:null,bCollapse:null,bInfinite:null,iBarWidth:0,iLoadGap:null,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1},aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aoColumns:[],aoHeader:[],aoFooter:[],asDataSearch:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:null,
asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,sPaginationType:"two_button",iCookieDuration:0,sCookiePrefix:"",fnCookieCallback:null,aoStateSave:[],aoStateLoad:[],
oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iDisplayEnd:10,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsTotal,10):this.aiDisplayMaster.length},
fnRecordsDisplay:function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsDisplay,10):this.aiDisplay.length},fnDisplayEnd:function(){return this.oFeatures.bServerSide?!1===this.oFeatures.bPaginate||-1==this._iDisplayLength?this._iDisplayStart+this.aiDisplay.length:Math.min(this._iDisplayStart+this._iDisplayLength,this._iRecordsDisplay):this._iDisplayEnd},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null};j.ext=h.extend(!0,{},j.models.ext);h.extend(j.ext.oStdClasses,
{sTable:"dataTable",sPagePrevEnabled:"paginate_enabled_previous",sPagePrevDisabled:"paginate_disabled_previous",sPageNextEnabled:"paginate_enabled_next",sPageNextDisabled:"paginate_disabled_next",sPageJUINext:"",sPageJUIPrev:"",sPageButton:"paginate_button",sPageButtonActive:"paginate_active",sPageButtonStaticDisabled:"paginate_button paginate_button_disabled",sPageFirst:"first",sPagePrevious:"previous",sPageNext:"next",sPageLast:"last",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",
sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",
sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sFooterTH:"",sJUIHeader:"",sJUIFooter:""});h.extend(j.ext.oJUIClasses,j.ext.oStdClasses,{sPagePrevEnabled:"fg-button ui-button ui-state-default ui-corner-left",sPagePrevDisabled:"fg-button ui-button ui-state-default ui-corner-left ui-state-disabled",sPageNextEnabled:"fg-button ui-button ui-state-default ui-corner-right",
sPageNextDisabled:"fg-button ui-button ui-state-default ui-corner-right ui-state-disabled",sPageJUINext:"ui-icon ui-icon-circle-arrow-e",sPageJUIPrev:"ui-icon ui-icon-circle-arrow-w",sPageButton:"fg-button ui-button ui-state-default",sPageButtonActive:"fg-button ui-button ui-state-default ui-state-disabled",sPageButtonStaticDisabled:"fg-button ui-button ui-state-default ui-state-disabled",sPageFirst:"first ui-corner-tl ui-corner-bl",sPageLast:"last ui-corner-tr ui-corner-br",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",
sSortAsc:"ui-state-default",sSortDesc:"ui-state-default",sSortable:"ui-state-default",sSortableAsc:"ui-state-default",sSortableDesc:"ui-state-default",sSortableNone:"ui-state-default",sSortJUIAsc:"css_right ui-icon ui-icon-triangle-1-n",sSortJUIDesc:"css_right ui-icon ui-icon-triangle-1-s",sSortJUI:"css_right ui-icon ui-icon-carat-2-n-s",sSortJUIAscAllowed:"css_right ui-icon ui-icon-carat-1-n",sSortJUIDescAllowed:"css_right ui-icon ui-icon-carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",
sScrollHead:"dataTables_scrollHead ui-state-default",sScrollFoot:"dataTables_scrollFoot ui-state-default",sFooterTH:"ui-state-default",sJUIHeader:"fg-toolbar ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix",sJUIFooter:"fg-toolbar ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix"});h.extend(j.ext.oPagination,{two_button:{fnInit:function(e,j,m){var k=e.oLanguage.oPaginate,n=function(h){e.oApi._fnPageChange(e,h.data.action)&&m(e)},k=!e.bJUI?'<a class="'+
e.oClasses.sPagePrevDisabled+'" tabindex="'+e.iTabIndex+'" role="button">'+k.sPrevious+'</a><a class="'+e.oClasses.sPageNextDisabled+'" tabindex="'+e.iTabIndex+'" role="button">'+k.sNext+"</a>":'<a class="'+e.oClasses.sPagePrevDisabled+'" tabindex="'+e.iTabIndex+'" role="button"><span class="'+e.oClasses.sPageJUIPrev+'"></span></a><a class="'+e.oClasses.sPageNextDisabled+'" tabindex="'+e.iTabIndex+'" role="button"><span class="'+e.oClasses.sPageJUINext+'"></span></a>';h(j).append(k);var l=h("a",j),
k=l[0],l=l[1];e.oApi._fnBindAction(k,{action:"previous"},n);e.oApi._fnBindAction(l,{action:"next"},n);e.aanFeatures.p||(j.id=e.sTableId+"_paginate",k.id=e.sTableId+"_previous",l.id=e.sTableId+"_next",k.setAttribute("aria-controls",e.sTableId),l.setAttribute("aria-controls",e.sTableId))},fnUpdate:function(e){if(e.aanFeatures.p)for(var h=e.oClasses,j=e.aanFeatures.p,k,l=0,n=j.length;l<n;l++)if(k=j[l].firstChild)k.className=0===e._iDisplayStart?h.sPagePrevDisabled:h.sPagePrevEnabled,k=k.nextSibling,
k.className=e.fnDisplayEnd()==e.fnRecordsDisplay()?h.sPageNextDisabled:h.sPageNextEnabled}},iFullNumbersShowPages:5,full_numbers:{fnInit:function(e,j,m){var k=e.oLanguage.oPaginate,l=e.oClasses,n=function(h){e.oApi._fnPageChange(e,h.data.action)&&m(e)};h(j).append('<a tabindex="'+e.iTabIndex+'" class="'+l.sPageButton+" "+l.sPageFirst+'">'+k.sFirst+'</a><a tabindex="'+e.iTabIndex+'" class="'+l.sPageButton+" "+l.sPagePrevious+'">'+k.sPrevious+'</a><span></span><a tabindex="'+e.iTabIndex+'" class="'+
l.sPageButton+" "+l.sPageNext+'">'+k.sNext+'</a><a tabindex="'+e.iTabIndex+'" class="'+l.sPageButton+" "+l.sPageLast+'">'+k.sLast+"</a>");var t=h("a",j),k=t[0],l=t[1],r=t[2],t=t[3];e.oApi._fnBindAction(k,{action:"first"},n);e.oApi._fnBindAction(l,{action:"previous"},n);e.oApi._fnBindAction(r,{action:"next"},n);e.oApi._fnBindAction(t,{action:"last"},n);e.aanFeatures.p||(j.id=e.sTableId+"_paginate",k.id=e.sTableId+"_first",l.id=e.sTableId+"_previous",r.id=e.sTableId+"_next",t.id=e.sTableId+"_last")},
fnUpdate:function(e,o){if(e.aanFeatures.p){var m=j.ext.oPagination.iFullNumbersShowPages,k=Math.floor(m/2),l=Math.ceil(e.fnRecordsDisplay()/e._iDisplayLength),n=Math.ceil(e._iDisplayStart/e._iDisplayLength)+1,t="",r,B=e.oClasses,u,M=e.aanFeatures.p,L=function(h){e.oApi._fnBindAction(this,{page:h+r-1},function(h){e.oApi._fnPageChange(e,h.data.page);o(e);h.preventDefault()})};-1===e._iDisplayLength?n=k=r=1:l<m?(r=1,k=l):n<=k?(r=1,k=m):n>=l-k?(r=l-m+1,k=l):(r=n-Math.ceil(m/2)+1,k=r+m-1);for(m=r;m<=k;m++)t+=
n!==m?'<a tabindex="'+e.iTabIndex+'" class="'+B.sPageButton+'">'+e.fnFormatNumber(m)+"</a>":'<a tabindex="'+e.iTabIndex+'" class="'+B.sPageButtonActive+'">'+e.fnFormatNumber(m)+"</a>";m=0;for(k=M.length;m<k;m++)u=M[m],u.hasChildNodes()&&(h("span:eq(0)",u).html(t).children("a").each(L),u=u.getElementsByTagName("a"),u=[u[0],u[1],u[u.length-2],u[u.length-1]],h(u).removeClass(B.sPageButton+" "+B.sPageButtonActive+" "+B.sPageButtonStaticDisabled),h([u[0],u[1]]).addClass(1==n?B.sPageButtonStaticDisabled:
B.sPageButton),h([u[2],u[3]]).addClass(0===l||n===l||-1===e._iDisplayLength?B.sPageButtonStaticDisabled:B.sPageButton))}}}});h.extend(j.ext.oSort,{"string-pre":function(e){"string"!=typeof e&&(e=null!==e&&e.toString?e.toString():"");return e.toLowerCase()},"string-asc":function(e,h){return e<h?-1:e>h?1:0},"string-desc":function(e,h){return e<h?1:e>h?-1:0},"html-pre":function(e){return e.replace(/<.*?>/g,"").toLowerCase()},"html-asc":function(e,h){return e<h?-1:e>h?1:0},"html-desc":function(e,h){return e<
h?1:e>h?-1:0},"date-pre":function(e){e=Date.parse(e);if(isNaN(e)||""===e)e=Date.parse("01/01/1970 00:00:00");return e},"date-asc":function(e,h){return e-h},"date-desc":function(e,h){return h-e},"numeric-pre":function(e){return"-"==e||""===e?0:1*e},"numeric-asc":function(e,h){return e-h},"numeric-desc":function(e,h){return h-e}});h.extend(j.ext.aTypes,[function(e){if("number"===typeof e)return"numeric";if("string"!==typeof e)return null;var h,j=!1;h=e.charAt(0);if(-1=="0123456789-".indexOf(h))return null;
for(var k=1;k<e.length;k++){h=e.charAt(k);if(-1=="0123456789.".indexOf(h))return null;if("."==h){if(j)return null;j=!0}}return"numeric"},function(e){var h=Date.parse(e);return null!==h&&!isNaN(h)||"string"===typeof e&&0===e.length?"date":null},function(e){return"string"===typeof e&&-1!=e.indexOf("<")&&-1!=e.indexOf(">")?"html":null}]);h.fn.DataTable=j;h.fn.dataTable=j;h.fn.dataTableSettings=j.settings;h.fn.dataTableExt=j.ext};"function"===typeof define&&define.amd?define(["jquery"],L):jQuery&&!jQuery.fn.dataTable&&
L(jQuery)})(window,document);

View File

@@ -82,3 +82,7 @@ span.jslider {
-o-transition: none;
transition: none;
}
.crosshair {
cursor: crosshair;
}

View File

@@ -13,6 +13,17 @@
return Math.floor(0x100000000 + (Math.random() * 0xF00000000)).toString(16);
}
// A wrapper for getComputedStyle that is compatible with older browsers.
// This is significantly faster than jQuery's .css() function.
function getStyle(el, styleProp) {
if (el.currentStyle)
var x = el.currentStyle[styleProp];
else if (window.getComputedStyle)
var x = document.defaultView.getComputedStyle(el, null)
.getPropertyValue(styleProp);
return x;
}
// Convert a number to a string with leading zeros
function padZeros(n, digits) {
var str = n.toString();
@@ -450,7 +461,16 @@
var self = this;
var createSocketFunc = exports.createSocket || function() {
var ws = new WebSocket('ws://' + window.location.host, 'shiny');
var protocol = 'ws:';
if (window.location.protocol === 'https:')
protocol = 'wss:';
var defaultPath = window.location.pathname;
if (!/\/$/.test(defaultPath))
defaultPath += '/';
defaultPath += 'websocket/';
var ws = new WebSocket(protocol + '//' + window.location.host + defaultPath);
ws.binaryType = 'arraybuffer';
return ws;
};
@@ -472,6 +492,7 @@
};
socket.onclose = function() {
$(document.body).addClass('disconnected');
self.$notifyDisconnected();
};
return socket;
};
@@ -488,6 +509,34 @@
this.$updateConditionals();
};
this.$notifyDisconnected = function() {
// function to normalize hostnames
normalize = function(hostname) {
if (hostname == "127.0.0.1")
return "localhost";
else
return hostname;
}
// Send a 'disconnected' message to parent if we are on the same domin
var parentUrl = (parent !== window) ? document.referrer : null;
if (parentUrl) {
// parse the parent href
var a = document.createElement('a');
a.href = parentUrl;
// post the disconnected message if the hostnames are the same
if (normalize(a.hostname) == normalize(window.location.hostname)) {
protocol = a.protocol.replace(':',''); // browser compatability
origin = protocol + '://' + a.hostname;
if (a.port)
origin = origin + ':' + a.port;
parent.postMessage('disconnected', origin);
}
}
}
// NB: Including blobs will cause IE to break!
// TODO: Make blobs work with Internet Explorer
//
@@ -623,7 +672,18 @@
};
this.$updateConditionals = function() {
var scope = {input: this.$inputValues, output: this.$values};
var inputs = {};
// Input keys use "name:type" format; we don't want the user to
// have to know about the type suffix when referring to inputs.
for (var name in this.$inputValues) {
if (this.$inputValues.hasOwnProperty(name)) {
var shortName = name.replace(/:.*/, '');
inputs[shortName] = this.$inputValues[name];
}
}
var scope = {input: inputs, output: this.$values};
var triggerShown = function() { $(this).trigger('shown'); };
var triggerHidden = function() { $(this).trigger('hidden'); };
@@ -801,6 +861,10 @@
this._sendMessagesToHandlers(message, customMessageHandlers,
customMessageHandlerOrder);
});
addMessageHandler('config', function(message) {
this.config = message;
});
}).call(ShinyApp.prototype);
@@ -987,19 +1051,106 @@
return $(scope).find('.shiny-image-output, .shiny-plot-output');
},
renderValue: function(el, data) {
var self = this;
var $el = $(el);
// Load the image before emptying, to minimize flicker
var img = null;
var clickId, hoverId;
if (data) {
clickId = $el.data('click-id');
hoverId = $el.data('hover-id');
$el.data('coordmap', data.coordmap);
delete data.coordmap;
img = document.createElement('img');
// Copy items from data to img. This should include 'src'
$.each(data, function(key, value) {
img[key] = value;
});
// Firefox doesn't have offsetX/Y, so we need to use an alternate
// method of calculation for it
function mouseOffset(mouseEvent) {
if (typeof(mouseEvent.offsetX) !== 'undefined') {
return {
x: mouseEvent.offsetX,
y: mouseEvent.offsetY
};
}
var offset = $el.offset();
return {
x: mouseEvent.pageX - offset.left,
y: mouseEvent.pageY - offset.top
};
}
function createMouseHandler(inputId) {
return function(e) {
if (e === null) {
Shiny.onInputChange(inputId, null);
return;
}
// TODO: Account for scrolling within the image??
var coordmap = $el.data('coordmap');
function devToUsrX(deviceX) {
var x = deviceX - coordmap.bounds.left;
var factor = (coordmap.usr.right - coordmap.usr.left) /
(coordmap.bounds.right - coordmap.bounds.left);
return (x * factor) + coordmap.usr.left;
}
function devToUsrY(deviceY) {
var y = deviceY - coordmap.bounds.bottom;
var factor = (coordmap.usr.top - coordmap.usr.bottom) /
(coordmap.bounds.top - coordmap.bounds.bottom);
return (y * factor) + coordmap.usr.bottom;
}
var offset = mouseOffset(e);
var userX = devToUsrX(offset.x);
if (coordmap.log.x)
userX = Math.pow(10, userX);
var userY = devToUsrY(offset.y);
if (coordmap.log.y)
userY = Math.pow(10, userY);
Shiny.onInputChange(inputId, {
x: userX, y: userY,
".nonce": Math.random()
});
}
};
if (!$el.data('hover-func')) {
var hoverDelayType = $el.data('hover-delay-type') || 'debounce';
var delayFunc = (hoverDelayType === 'throttle') ? throttle : debounce;
var hoverFunc = delayFunc($el.data('hover-delay') || 300,
createMouseHandler(hoverId));
$el.data('hover-func', hoverFunc);
}
if (clickId)
$(img).on('mousedown', createMouseHandler(clickId));
if (hoverId) {
$(img).on('mousemove', $el.data('hover-func'));
$(img).on('mouseout', function(e) {
$el.data('hover-func')(null);
});
}
if (clickId || hoverId) {
$(img).addClass('crosshair');
}
}
$(el).empty();
$el.empty();
if (img)
$(el).append(img);
$el.append(img);
}
});
outputBindings.register(imageOutputBinding, 'shiny.imageOutput');
@@ -1033,6 +1184,53 @@
});
outputBindings.register(downloadLinkOutputBinding, 'shiny.downloadLink');
var datatableOutputBinding = new OutputBinding();
$.extend(datatableOutputBinding, {
find: function(scope) {
return $(scope).find('.shiny-datatable-output');
},
onValueError: function(el, err) {
exports.unbindAll(el);
this.renderError(el, err);
},
renderValue: function(el, data) {
var $el = $(el).empty();
if (!data || !data.colnames) return;
var colnames = $.makeArray(data.colnames);
var header = colnames.map(function(x) {
return '<th>' + x + '</th>';
}).join('');
header = '<thead><tr>' + header + '</tr></thead>';
var footer = colnames.map(function(x) {
return '<th><input type="text" placeholder="' + x + '" /></th>';
}).join('');
footer = '<tfoot>' + footer + '</tfoot>';
var content = '<table class="table table-striped table-hover">' +
header + footer + '</table>';
$el.append(content);
var oTable = $(el).children("table").dataTable($.extend({
"bProcessing": true,
"bServerSide": true,
"aaSorting": [],
"bSortClasses": false,
"iDisplayLength": 25,
"sAjaxSource": data.action
}, data.options));
// use debouncing for searching boxes
$el.find('label input').first().unbind('keyup')
.keyup(debounce(data.searchDelay, function() {
oTable.fnFilter(this.value);
}));
var searchInputs = $el.find("tfoot input");
searchInputs.keyup(debounce(data.searchDelay, function() {
oTable.fnFilter(this.value, searchInputs.index(this));
}));
// FIXME: ugly scrollbars in tab panels b/c Bootstrap uses 'visible: auto'
$el.parents('.tab-content').css('overflow', 'visible');
}
});
outputBindings.register(datatableOutputBinding, 'shiny.datatableOutput');
// =========================================================================
// Input bindings
// =========================================================================
@@ -2518,7 +2716,7 @@
// non-zero, then we know that no ancestor has display:none.
if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) {
return false;
} else if (getComputedStyle(obj, null).display === 'none') {
} else if (getStyle(obj, 'display') === 'none') {
return true;
} else {
return(isHidden(obj.parentNode));
@@ -2640,5 +2838,13 @@
target.removeData('animating');
}
});
$(document).on('keydown', function(e) {
if (e.which !== 114 || (!e.ctrlKey && !e.metaKey) || (e.shiftKey || e.altKey))
return;
var url = 'reactlog?w=' + Shiny.shinyapp.config.workerId;
window.open(url);
e.preventDefault();
});
})();

View File

@@ -16,4 +16,12 @@
Creates an action button whose value is initially zero,
and increments by one each time it is pressed.
}
\seealso{
Other input.elements: \code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{numericInput}}, \code{\link{radioButtons}},
\code{\link{selectInput}}, \code{\link{sliderInput}},
\code{\link{submitButton}}, \code{\link{textInput}}
}

View File

@@ -36,5 +36,12 @@ checkboxGroupInput("variable", "Variable:",
\seealso{
\code{\link{checkboxInput}},
\code{\link{updateCheckboxGroupInput}}
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{numericInput}}, \code{\link{radioButtons}},
\code{\link{selectInput}}, \code{\link{sliderInput}},
\code{\link{submitButton}}, \code{\link{textInput}}
}

View File

@@ -26,5 +26,13 @@ checkboxInput("outliers", "Show outliers", FALSE)
\seealso{
\code{\link{checkboxGroupInput}},
\code{\link{updateCheckboxInput}}
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{dateInput}}, \code{\link{dateRangeInput}},
\code{\link{fileInput}}, \code{\link{numericInput}},
\code{\link{radioButtons}}, \code{\link{selectInput}},
\code{\link{sliderInput}}, \code{\link{submitButton}},
\code{\link{textInput}}
}

View File

@@ -84,5 +84,13 @@ dateInput("date", "Date:",
\seealso{
\code{\link{dateRangeInput}},
\code{\link{updateDateInput}}
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{numericInput}}, \code{\link{radioButtons}},
\code{\link{selectInput}}, \code{\link{sliderInput}},
\code{\link{submitButton}}, \code{\link{textInput}}
}

View File

@@ -8,11 +8,6 @@
separator = " to ")
}
\arguments{
\item{inputId}{Input variable to assign the control's
value to.}
\item{label}{Display label for the control.}
\item{start}{The initial start date. Either a Date
object, or a string in \code{yyyy-mm-dd} format. If NULL
(the default), will use the current date in the client's
@@ -23,6 +18,14 @@
default), will use the current date in the client's time
zone.}
\item{separator}{String to display between the start and
end input boxes.}
\item{inputId}{Input variable to assign the control's
value to.}
\item{label}{Display label for the control.}
\item{min}{The minimum allowed date. Either a Date
object, or a string in \code{yyyy-mm-dd} format.}
@@ -46,9 +49,6 @@
"ms", "nb", "nl", "pl", "pt", "pt", "ro", "rs",
"rs-latin", "ru", "sk", "sl", "sv", "sw", "th", "tr",
"uk", "zh-CN", and "zh-TW".}
\item{separator}{String to display between the start and
end input boxes.}
}
\description{
Creates a pair of text inputs which, when clicked on,
@@ -105,5 +105,13 @@ dateRangeInput("daterange", "Date range:",
\seealso{
\code{\link{dateInput}},
\code{\link{updateDateRangeInput}}
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{fileInput}}, \code{\link{numericInput}},
\code{\link{radioButtons}}, \code{\link{selectInput}},
\code{\link{sliderInput}}, \code{\link{submitButton}},
\code{\link{textInput}}
}

View File

@@ -3,7 +3,7 @@
\title{Convert an expression or quoted expression to a function}
\usage{
exprToFunction(expr, env = parent.frame(2),
quoted = FALSE)
quoted = FALSE, caller_offset = 1)
}
\arguments{
\item{expr}{A quoted or unquoted expression, or a
@@ -13,6 +13,9 @@
Defaults to the calling environment two steps back.}
\item{quoted}{Is the expression quoted?}
\item{caller_offset}{If specified, the offset in the
callstack of the functiont to be treated as the caller.}
}
\description{
This is to be called from another function, because it
@@ -22,7 +25,7 @@
\details{
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
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.

View File

@@ -39,4 +39,13 @@
This file may be deleted if the user performs another
upload operation.} }
}
\seealso{
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{numericInput}},
\code{\link{radioButtons}}, \code{\link{selectInput}},
\code{\link{sliderInput}}, \code{\link{submitButton}},
\code{\link{textInput}}
}

View File

@@ -1,54 +1,49 @@
\name{includeHTML}
\alias{includeCSS}
\alias{includeHTML}
\alias{includeText}
\alias{includeMarkdown}
\alias{includeScript}
\alias{includeText}
\title{Include Content From a File}
\usage{
includeHTML(path)
includeText(path)
includeMarkdown(path)
includeCSS(path, ...)
includeScript(path, ...)
}
\title{Include Content From a File}
\arguments{
\item{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.
}
\item{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.}
\item{...}{Any additional attributes to be applied to the
generated tag.}
}
\description{
Include HTML, text, or rendered Markdown into a \link[=shinyUI]{Shiny UI}.
Include HTML, text, or rendered Markdown into a
\link[=shinyUI]{Shiny UI}.
}
\details{
These functions provide a convenient way to include an extensive amount
of HTML, textual, or Markdown content, rather than using a large literal R
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.
\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.
The \code{includeMarkdown} function requires the
\code{markdown} package.
}
\note{
The \code{includeMarkdown} function requires the \code{markdown} package.
}
\examples{
doc <- tags$html(
tags$head(
tags$title('My first page')
),
tags$body(
h1('My first heading'),
p('My first paragraph, with some ',
strong('bold'),
' text.'),
div(id='myDiv', class='simpleDiv',
'Here is a div with some attributes.')
)
)
cat(as.character(doc))
}

View File

@@ -0,0 +1,39 @@
\name{installExprFunction}
\alias{installExprFunction}
\title{Installs an expression in the given environment as a function, and registers
debug hooks so that breakpoints may be set in the function.}
\usage{
installExprFunction(expr, name,
eval.env = parent.frame(2), quoted = FALSE,
assign.env = parent.frame(1),
label = as.character(sys.call(-1)[[1]]))
}
\arguments{
\item{expr}{A quoted or unquoted expression}
\item{name}{The name the function should be given}
\item{eval.env}{The desired environment for the function.
Defaults to the calling environment two steps back.}
\item{quoted}{Is the expression quoted?}
\item{assign.env}{The environment in which the function
should be assigned.}
\item{label}{A label for the object to be shown in the
debugger. Defaults to the name of the calling function.}
}
\description{
This function can replace \code{exprToFunction} as
follows: we may use \code{func <- exprToFunction(expr)}
if we do not want the debug hooks, or
\code{installExprFunction(expr, "func")} if we do. Both
approaches create a function named \code{func} in the
current environment.
}
\seealso{
Wraps \code{exprToFunction}; see that method's
documentation for more documentation and examples.
}

16
man/is.reactivevalues.Rd Normal file
View File

@@ -0,0 +1,16 @@
\name{is.reactivevalues}
\alias{is.reactivevalues}
\title{Checks whether an object is a reactivevalues object}
\usage{
is.reactivevalues(x)
}
\arguments{
\item{x}{The object to test.}
}
\description{
Checks whether its argument is a reactivevalues object.
}
\seealso{
\code{\link{reactiveValues}}.
}

View File

@@ -33,5 +33,13 @@ numericInput("obs", "Observations:", 10,
}
\seealso{
\code{\link{updateNumericInput}}
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{radioButtons}}, \code{\link{selectInput}},
\code{\link{sliderInput}}, \code{\link{submitButton}},
\code{\link{textInput}}
}

View File

@@ -32,17 +32,41 @@
sooner than all observers with a lower priority level.
Positive, negative, and zero values are allowed.}
}
\value{
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. } }
}
\description{
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 expression, 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).
Creates an observer from the given expression.
}
\details{
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

View File

@@ -2,7 +2,9 @@
\alias{plotOutput}
\title{Create an plot output element}
\usage{
plotOutput(outputId, width = "100\%", height = "400px")
plotOutput(outputId, width = "100\%", height = "400px",
clickId = NULL, hoverId = NULL, hoverDelay = 300,
hoverDelayType = c("debounce", "throttle"))
}
\arguments{
\item{outputId}{output variable to read the plot from}
@@ -13,6 +15,33 @@
\code{"px"} appended.}
\item{height}{Plot height}
\item{clickId}{If not \code{NULL}, the plot will send
coordinates to the server whenever it is clicked. This
information will be accessible on the \code{input} object
using \code{input$}\emph{\code{clickId}}. The value will
be a named list or vector with \code{x} and \code{y}
elements indicating the mouse position in user units.}
\item{hoverId}{If not \code{NULL}, the plot will send
coordinates to the server whenever the mouse pauses on
the plot for more than the number of milliseconds
determined by \code{hoverTimeout}. This information will
be The value will be \code{NULL} if the user is not
hovering, and a named list or vector with \code{x} and
\code{y} elements indicating the mouse position in user
units.}
\item{hoverDelay}{The delay for hovering, in
milliseconds.}
\item{hoverDelayType}{The type of algorithm for limiting
the number of hover events. Use \code{"throttle"} to
limit the number of hover events to one every
\code{hoverDelay} milliseconds. Use \code{"debounce"} to
suspend events while the cursor is moving, and wait until
the cursor has been at rest for \code{hoverDelay}
milliseconds before sending an event.}
}
\value{
A plot output element that can be included in a panel

View File

@@ -39,5 +39,9 @@
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)}.
}

View File

@@ -34,5 +34,13 @@ radioButtons("dist", "Distribution type:",
}
\seealso{
\code{\link{updateRadioButtons}}
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{numericInput}}, \code{\link{selectInput}},
\code{\link{sliderInput}}, \code{\link{submitButton}},
\code{\link{textInput}}
}

View File

@@ -1,12 +1,16 @@
\name{reactive}
\alias{is.reactive}
\alias{reactive}
\title{Create a reactive expression}
\usage{
reactive(x, env = parent.frame(), quoted = FALSE,
label = NULL)
is.reactive(x)
}
\arguments{
\item{x}{An expression (quoted or unquoted).}
\item{x}{For \code{reactive}, an expression (quoted or
unquoted). For \code{is.reactive}, an object to test.}
\item{env}{The parent environment for the reactive
expression. By default, this is the calling environment,
@@ -21,6 +25,9 @@
\item{label}{A label for the reactive expression, useful
for debugging.}
}
\value{
a function, wrapped in a S3 class "reactive"
}
\description{
Wraps a normal expression to create a reactive
expression. Conceptually, a reactive expression is a

75
man/reactiveFileReader.Rd Normal file
View File

@@ -0,0 +1,75 @@
\name{reactiveFileReader}
\alias{reactiveFileReader}
\title{Reactive file reader}
\usage{
reactiveFileReader(intervalMillis, session, filePath,
readFunc, ...)
}
\arguments{
\item{intervalMillis}{Approximate number of milliseconds
to wait between checks of the file's last modified time.
This can be a numeric value, or a function that returns a
numeric value.}
\item{session}{The user session to associate this file
reader with, or \code{NULL} if none. If non-null, the
reader will automatically stop when the session ends.}
\item{filePath}{The file path to poll against and to pass
to \code{readFunc}. This can either be a single-element
character vector, or a function that returns one.}
\item{readFunc}{The function to use to read the file;
must expect the first argument to be the file path to
read. The return value of this function is used as the
value of the reactive file reader.}
\item{...}{Any additional arguments to pass to
\code{readFunc} whenever it is invoked.}
}
\value{
A reactive expression that returns the contents of the
file, and automatically invalidates when the file changes
on disk (as determined by last modified time).
}
\description{
Given a file path and read function, returns a reactive
data source for the contents of the file.
}
\details{
\code{reactiveFileReader} works by periodically checking
the file's last modified time; if it has changed, then
the file is re-read and any reactive dependents are
invalidated.
The \code{intervalMillis}, \code{filePath}, and
\code{readFunc} functions will each be executed in a
reactive context; therefore, they may read reactive
values and reactive expressions.
}
\examples{
\dontrun{
# Per-session reactive file reader
shinyServer(function(input, output, session)) {
fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
output$data <- renderTable({
fileData()
})
}
# Cross-session reactive file reader. In this example, all sessions share
# the same reader, so read.csv only gets executed once no matter how many
# user sessions are connected.
fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
shinyServer(function(input, output, session)) {
output$data <- renderTable({
fileData()
})
}
}
}
\seealso{
\code{\link{reactivePoll}}
}

81
man/reactivePoll.Rd Normal file
View File

@@ -0,0 +1,81 @@
\name{reactivePoll}
\alias{reactivePoll}
\title{Reactive polling}
\usage{
reactivePoll(intervalMillis, session, checkFunc,
valueFunc)
}
\arguments{
\item{intervalMillis}{Approximate number of milliseconds
to wait between calls to \code{checkFunc}. This can be
either a numeric value, or a function that returns a
numeric value.}
\item{session}{The user session to associate this file
reader with, or \code{NULL} if none. If non-null, the
reader will automatically stop when the session ends.}
\item{checkFunc}{A relatively cheap function whose values
over time will be tested for equality; inequality
indicates that the underlying value has changed and needs
to be invalidated and re-read using \code{valueFunc}. See
Details.}
\item{valueFunc}{A function that calculates the
underlying value. See Details.}
}
\value{
A reactive expression that returns the result of
\code{valueFunc}, and invalidates when \code{checkFunc}
changes.
}
\description{
Used to create a reactive data source, which works by
periodically polling a non-reactive data source.
}
\details{
\code{reactivePoll} works by pairing a relatively cheap
"check" function with a more expensive value retrieval
function. The check function will be executed
periodically and should always return a consistent value
until the data changes. When the check function returns a
different value, then the value retrieval function will
be used to re-populate the data.
Note that the check function doesn't return \code{TRUE}
or \code{FALSE} to indicate whether the underlying data
has changed. Rather, the check function indicates change
by returning a different value from the previous time it
was called.
For example, \code{reactivePoll} is used to implement
\code{reactiveFileReader} by pairing a check function
that simply returns the last modified timestamp of a
file, and a value retrieval function that actually reads
the contents of the file.
As another example, one might read a relational database
table reactively by using a check function that does
\code{SELECT MAX(timestamp) FROM table} and a value
retrieval function that does \code{SELECT * FROM table}.
The \code{intervalMillis}, \code{checkFunc}, and
\code{valueFunc} functions will be executed in a reactive
context; therefore, they may read reactive values and
reactive expressions.
}
\examples{
\dontrun{
# Assume the existence of readTimestamp and readValue functions
shinyServer(function(input, output, session) {
data <- reactivePoll(1000, session, readTimestamp, readValue)
output$dataTable <- renderTable({
data()
})
})
}
}
\seealso{
\code{\link{reactiveFileReader}}
}

View File

@@ -42,6 +42,7 @@ values <- reactiveValues(a = 1, b = 2)
isolate(values$a)
}
\seealso{
\code{\link{isolate}}.
\code{\link{isolate}} and
\code{\link{is.reactivevalues}}.
}

35
man/renderDataTable.Rd Normal file
View File

@@ -0,0 +1,35 @@
\name{renderDataTable}
\alias{renderDataTable}
\title{Table output with the JavaScript library DataTables}
\usage{
renderDataTable(expr, options = NULL, searchDelay = 500,
env = parent.frame(), quoted = FALSE)
}
\arguments{
\item{expr}{An expression that returns a data frame or a
matrix.}
\item{options}{A list of initialization options to be
passed to DataTables.}
\item{searchDelay}{The delay for searching, in
milliseconds (to avoid too frequent search requests).}
\item{env}{The environment in which to evaluate
\code{expr}.}
\item{quoted}{Is \code{expr} a quoted expression (with
\code{quote()})? This is useful if you want to save an
expression in a variable.}
}
\description{
Makes a reactive version of the given function that
returns a data frame (or matrix), which will be rendered
with the DataTables library. Paging, searching,
filtering, and sorting can be done on the R side using
Shiny as the server infrastructure.
}
\references{
\url{http://datatables.net}
}

View File

@@ -16,7 +16,7 @@
expression in a variable.}
\item{deleteFile}{Should the file in \code{func()$src} be
deleted after it is sent to the client browser? Genrrally
deleted after it is sent to the client browser? Generally
speaking, if the image is a temp file generated within
\code{func}, then this should be \code{TRUE}; if the
image is not a temp file, this should be \code{FALSE}.}
@@ -99,4 +99,8 @@ shinyServer(function(input, output, clientData) {
}
}
\seealso{
For more details on how the images are generated, and how
to control the output, see \code{\link{plotPNG}}.
}

View File

@@ -51,4 +51,8 @@
\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}}.
}

View File

@@ -2,8 +2,9 @@
\alias{runApp}
\title{Run Shiny Application}
\usage{
runApp(appDir = getwd(), port = 8100L,
launch.browser = getOption("shiny.launch.browser", interactive()))
runApp(appDir = getwd(), port = NULL,
launch.browser = getOption("shiny.launch.browser", interactive()),
workerId = "")
}
\arguments{
\item{appDir}{The directory of the application. Should
@@ -12,11 +13,17 @@
\code{index.html}. Defaults to the working directory.}
\item{port}{The TCP port that the application should
listen on. Defaults to port 8100.}
listen on. Defaults to choosing a random port.}
\item{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.}
started. Defaults to true in interactive sessions only.
This value of this parameter can also be a function to
call with the application's URL.}
\item{workerId}{Can generally be ignored. Exists to help
some editions of Shiny Server Pro route requests to the
correct process.}
}
\description{
Runs a Shiny application. This function normally does not

View File

@@ -2,7 +2,7 @@
\alias{runExample}
\title{Run Shiny Example Applications}
\usage{
runExample(example = NA, port = 8100L,
runExample(example = NA, port = NULL,
launch.browser = getOption("shiny.launch.browser", interactive()))
}
\arguments{
@@ -10,7 +10,7 @@
\code{NA} (the default) to list the available examples.}
\item{port}{The TCP port that the application should
listen on. Defaults to port 8100.}
listen on. Defaults to choosing a random port.}
\item{launch.browser}{If true, the system's default web
browser will be launched automatically after the app is

View File

@@ -2,7 +2,7 @@
\alias{runGist}
\title{Run a Shiny application from https://gist.github.com}
\usage{
runGist(gist, port = 8100L,
runGist(gist, port = NULL,
launch.browser = getOption("shiny.launch.browser", interactive()))
}
\arguments{
@@ -13,7 +13,7 @@
valid values.}
\item{port}{The TCP port that the application should
listen on. Defaults to port 8100.}
listen on. Defaults to choosing a random port.}
\item{launch.browser}{If true, the system's default web
browser will be launched automatically after the app is

View File

@@ -3,7 +3,7 @@
\title{Run a Shiny application from a GitHub repository}
\usage{
runGitHub(repo, username = getOption("github.user"),
ref = "master", subdir = NULL, port = 8100,
ref = "master", subdir = NULL, port = NULL,
launch.browser = getOption("shiny.launch.browser", interactive()))
}
\arguments{
@@ -20,7 +20,7 @@
path such as `\code{"inst/shinyapp"}.}
\item{port}{The TCP port that the application should
listen on. Defaults to port 8100.}
listen on. Defaults to choosing a random port.}
\item{launch.browser}{If true, the system's default web
browser will be launched automatically after the app is

View File

@@ -2,7 +2,7 @@
\alias{runUrl}
\title{Run a Shiny application from a URL}
\usage{
runUrl(url, filetype = NULL, subdir = NULL, port = 8100,
runUrl(url, filetype = NULL, subdir = NULL, port = NULL,
launch.browser = getOption("shiny.launch.browser", interactive()))
}
\arguments{
@@ -18,7 +18,7 @@
path such as `\code{"inst/shinyapp"}.}
\item{port}{The TCP port that the application should
listen on. Defaults to port 8100.}
listen on. Defaults to choosing a random port.}
\item{launch.browser}{If true, the system's default web
browser will be launched automatically after the app is

View File

@@ -39,5 +39,13 @@ selectInput("variable", "Variable:",
}
\seealso{
\code{\link{updateSelectInput}}
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{numericInput}}, \code{\link{radioButtons}},
\code{\link{sliderInput}}, \code{\link{submitButton}},
\code{\link{textInput}}
}

View File

@@ -1,41 +1,20 @@
\name{shiny-package}
\alias{shiny-package}
\alias{shiny}
\docType{package}
\title{
Web Application Framework for R
}
\name{shiny-package}
\alias{shiny}
\alias{shiny-package}
\title{Web Application Framework for R}
\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.
The Shiny tutorial at \url{http://rstudio.github.com/shiny/tutorial}
explains the framework in-depth, walks you through
building a simple application, and includes extensive annotated
examples.
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.
}
\details{
\tabular{ll}{
Package: \tab shiny\cr
Type: \tab Package\cr
Version: \tab 0.1.0\cr
Date: \tab 2012-07-28\cr
License: \tab GPL-3\cr
Depends: \tab R (>= 2.14.1), methods, websockets (>= 1.1.4), caTools, RJSONIO, xtable\cr
Imports: \tab stats, tools, utils, datasets\cr
URL: \tab https://github.com/rstudio/shiny, http://rstudio.github.com/shiny/tutorial\cr
BugReports: \tab https://github.com/rstudio/shiny/issues\cr
The Shiny tutorial at
\url{http://rstudio.github.com/shiny/tutorial} explains
the framework in depth, walks you through building a
simple application, and includes extensive annotated
examples.
}
}
\author{
RStudio, Inc.
Maintainer: Joe Cheng <joe@rstudio.org>
}
\keyword{ package }

47
man/showReactLog.Rd Normal file
View File

@@ -0,0 +1,47 @@
\name{showReactLog}
\alias{showReactLog}
\title{Reactive Log Visualizer}
\usage{
showReactLog()
}
\description{
Provides an interactive browser-based tool for
visualizing reactive dependencies and execution in your
application.
}
\details{
To use the reactive log visualizer, start with a fresh R
session and run the command
\code{options(shiny.reactlog=TRUE)}; then launch your
application in the usual way (e.g. using
\code{\link{runApp}}). At any time you can hit Ctrl+F3
(or for Mac users, Command+F3) in your web browser to
launch the reactive log visualization.
The reactive log visualization only includes reactive
activity up until the time the report was loaded. If you
want to see more recent activity, refresh the browser.
Note that Shiny does not distinguish between reactive
dependencies that "belong" to one Shiny user session
versus another, so the visualization will include all
reactive activity that has taken place in the process,
not just for a particular application or session.
As an alternative to pressing Ctrl/Command+F3--for
example, if you are using reactives outside of the
context of a Shiny application--you can run the
\code{showReactLog} function, which will generate the
reactive log visualization as a static HTML file and
launch it in your default browser. In this case,
refreshing your browser will not load new activity into
the report; you will need to call \code{showReactLog()}
explicitly.
For security and performance reasons, do not enable
\code{shiny.reactlog} in production environments. When
the option is enabled, it's possible for any user of your
app to see at least some of the source code of your
reactive expressions and observers.
}

View File

@@ -19,9 +19,11 @@
\item{max}{The maximum value (inclusive) that can be
selected.}
\item{value}{The initial value of the slider. A warning
will be issued if the value doesn't fit between
\code{min} and \code{max}.}
\item{value}{The initial value of the slider. A numeric
vector of length one will create a regular slider; a
numeric vector of length two will create a double-ended
range slider. A warning will be issued if the value
doesn't fit between \code{min} and \code{max}.}
\item{step}{Specifies the interval between each
selectable value on the slider (\code{NULL} means no
@@ -70,5 +72,13 @@
}
\seealso{
\code{\link{updateSliderInput}}
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{numericInput}}, \code{\link{radioButtons}},
\code{\link{selectInput}}, \code{\link{submitButton}},
\code{\link{textInput}}
}

15
man/stopApp.Rd Normal file
View File

@@ -0,0 +1,15 @@
\name{stopApp}
\alias{stopApp}
\title{Stop the currently running Shiny app}
\usage{
stopApp(returnValue = NULL)
}
\arguments{
\item{returnValue}{The value that should be returned from
\code{\link{runApp}}.}
}
\description{
Stops the currently running Shiny app, returning control
to the caller of \code{\link{runApp}}.
}

View File

@@ -19,4 +19,13 @@
\examples{
submitButton("Update View")
}
\seealso{
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{numericInput}}, \code{\link{radioButtons}},
\code{\link{selectInput}}, \code{\link{sliderInput}},
\code{\link{textInput}}
}

View File

@@ -1,8 +1,11 @@
\name{tableOutput}
\alias{dataTableOutput}
\alias{tableOutput}
\title{Create a table output element}
\usage{
tableOutput(outputId)
dataTableOutput(outputId)
}
\arguments{
\item{outputId}{output variable to read the table from}

View File

@@ -1,6 +1,8 @@
\name{tag}
\alias{tag}
\alias{tagAppendChild}
\alias{tagAppendChildren}
\alias{tagSetChildren}
\alias{tagList}
\title{
HTML Tag Object
@@ -16,6 +18,14 @@ sets of tags; see the contents of bootstrap.R for examples.
\code{tagAppendChild(tag, child)}
\code{tagAppendChildren(tag, child1, child2)}
\code{tagAppendChildren(tag, list = list(child1, child2))}
\code{tagSetChildren(tag, child1, child2)}
\code{tagSetChildren(tag, list = list(child1, child2))}
\code{tagList(...)}
}
@@ -38,6 +48,10 @@ sets of tags; see the contents of bootstrap.R for examples.
}
\item{...}{
Unnamed items that comprise this list of tags.
}
\item{list}{
An optional list of elements. Can be used with or instead of the \code{...}
items.
}
}

View File

@@ -25,5 +25,13 @@ textInput("caption", "Caption:", "Data Summary")
}
\seealso{
\code{\link{updateTextInput}}
Other input.elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{numericInput}}, \code{\link{radioButtons}},
\code{\link{selectInput}}, \code{\link{sliderInput}},
\code{\link{submitButton}}
}

22
man/validateCssUnit.Rd Normal file
View File

@@ -0,0 +1,22 @@
\name{validateCssUnit}
\alias{validateCssUnit}
\title{Validate proper CSS formatting of a unit}
\usage{
validateCssUnit(x)
}
\arguments{
\item{x}{The unit to validate. Will be treated as a
number of pixels if a unit is not specified.}
}
\value{
A properly formatted CSS unit of length, if possible.
Otherwise, will throw an error.
}
\description{
Validate proper CSS formatting of a unit
}
\examples{
validateCssUnit("10\%")
validateCssUnit(400) #treated as '400px'
}