[med-svn] [r-cran-shiny] 02/06: New upstream version 1.0.5+dfsg

Andreas Tille tille at debian.org
Thu Sep 14 11:58:14 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository r-cran-shiny.

commit 2da58953a1a4e7d72dda8263e22f89b115b96d91
Author: Andreas Tille <tille at debian.org>
Date:   Thu Sep 14 13:19:21 2017 +0200

    New upstream version 1.0.5+dfsg
---
 DESCRIPTION                                        |   42 +-
 LICENSE                                            |    2 +-
 MD5                                                |  458 +++----
 NAMESPACE                                          |   18 +
 NEWS.md                                            |  183 ++-
 R/app.R                                            |   11 +-
 R/bookmark-state.R                                 |   86 +-
 R/bootstrap.R                                      |  381 +++---
 R/fileupload.R                                     |   17 +-
 R/globals.R                                        |    2 +-
 R/history.R                                        |   95 ++
 R/html-deps.R                                      |   15 +-
 R/input-checkboxgroup.R                            |   57 +-
 R/input-daterange.R                                |    4 +-
 R/input-file.R                                     |    9 +-
 R/input-radiobuttons.R                             |   66 +-
 R/input-select.R                                   |    8 +-
 R/input-slider.R                                   |   14 +-
 R/input-utils.R                                    |   93 +-
 R/insert-tab.R                                     |  325 +++++
 R/middleware.R                                     |   35 +-
 R/modules.R                                        |    5 +
 R/progress.R                                       |   31 +-
 R/reactives.R                                      |  283 ++++-
 R/render-plot.R                                    |  568 ++++++---
 R/render-table.R                                   |    7 +-
 R/serializers.R                                    |   27 +-
 R/server-input-handlers.R                          |    6 +-
 R/server.R                                         |   48 +-
 R/shiny.R                                          |  278 +++-
 R/shinyui.R                                        |    3 +-
 R/shinywrappers.R                                  |   45 +-
 R/showcase.R                                       |    3 +-
 R/snapshot.R                                       |   44 +
 R/update-input.R                                   |   53 +-
 R/utils.R                                          |   75 +-
 README.md                                          |    4 +-
 inst/examples/01_hello/Readme.md                   |    7 +-
 inst/examples/01_hello/app.R                       |   59 +
 inst/examples/01_hello/server.R                    |   21 -
 inst/examples/01_hello/ui.R                        |   24 -
 inst/examples/02_text/Readme.md                    |    2 +-
 inst/examples/02_text/app.R                        |   64 +
 inst/examples/02_text/server.R                     |   26 -
 inst/examples/02_text/ui.R                         |   27 -
 inst/examples/03_reactivity/Readme.md              |    4 +-
 inst/examples/03_reactivity/app.R                  |  102 ++
 inst/examples/03_reactivity/server.R               |   53 -
 inst/examples/03_reactivity/ui.R                   |   34 -
 inst/examples/04_mpg/Readme.md                     |    4 +-
 inst/examples/04_mpg/app.R                         |   75 ++
 inst/examples/04_mpg/server.R                      |   34 -
 inst/examples/04_mpg/ui.R                          |   29 -
 inst/examples/05_sliders/app.R                     |   86 ++
 inst/examples/05_sliders/server.R                  |   29 -
 inst/examples/05_sliders/ui.R                      |   43 -
 inst/examples/06_tabsets/Readme.md                 |    2 +-
 inst/examples/06_tabsets/app.R                     |   92 ++
 inst/examples/06_tabsets/server.R                  |   44 -
 inst/examples/06_tabsets/ui.R                      |   38 -
 inst/examples/07_widgets/app.R                     |   82 ++
 inst/examples/07_widgets/server.R                  |   32 -
 inst/examples/07_widgets/ui.R                      |   43 -
 inst/examples/08_html/Readme.md                    |    5 +-
 inst/examples/08_html/app.R                        |   47 +
 inst/examples/08_html/server.R                     |   42 -
 inst/examples/08_html/www/index.html               |   33 +-
 inst/examples/09_upload/Readme.md                  |    5 +-
 inst/examples/09_upload/app.R                      |   92 ++
 inst/examples/09_upload/server.R                   |   20 -
 inst/examples/09_upload/ui.R                       |   28 -
 inst/examples/10_download/Readme.md                |    4 +-
 inst/examples/10_download/app.R                    |   63 +
 inst/examples/10_download/server.R                 |   21 -
 inst/examples/10_download/ui.R                     |   13 -
 inst/examples/11_timer/app.R                       |   21 +
 inst/examples/11_timer/server.R                    |    6 -
 inst/examples/11_timer/ui.R                        |    3 -
 inst/staticdocs/index.r                            |   17 +-
 .../shared/ionrangeslider/css/ion.rangeSlider.css  |    1 +
 .../shared/ionrangeslider/js/ion.rangeSlider.js    |  151 ++-
 .../ionrangeslider/js/ion.rangeSlider.min.js       |    4 +-
 inst/www/shared/shiny-showcase.css                 |    1 +
 inst/www/shared/shiny-showcase.js                  |    2 +-
 inst/www/shared/shiny.css                          |   15 +
 inst/www/shared/shiny.js                           | 1322 +++++++++++++++++---
 inst/www/shared/shiny.js.map                       |    2 +-
 inst/www/shared/shiny.min.js                       |    8 +-
 inst/www/shared/shiny.min.js.map                   |    2 +-
 man/NS.Rd                                          |    1 -
 man/Progress.Rd                                    |    4 +-
 man/absolutePanel.Rd                               |    1 -
 man/actionButton.Rd                                |    3 +-
 man/addResourcePath.Rd                             |    1 -
 man/applyInputHandlers.Rd                          |    1 -
 man/bookmarkButton.Rd                              |    1 -
 man/bootstrapLib.Rd                                |    1 -
 man/bootstrapPage.Rd                               |    3 +-
 man/brushOpts.Rd                                   |    1 -
 man/brushedPoints.Rd                               |    1 -
 man/callModule.Rd                                  |    1 -
 man/checkboxGroupInput.Rd                          |   44 +-
 man/checkboxInput.Rd                               |    3 +-
 man/clickOpts.Rd                                   |    1 -
 man/column.Rd                                      |    1 -
 man/conditionalPanel.Rd                            |   71 +-
 man/createWebDependency.Rd                         |   13 +-
 man/dateInput.Rd                                   |    3 +-
 man/dateRangeInput.Rd                              |    3 +-
 man/dblclickOpts.Rd                                |    1 -
 man/debounce.Rd                                    |    2 +-
 man/domains.Rd                                     |    4 +-
 man/downloadButton.Rd                              |    4 +-
 man/downloadHandler.Rd                             |    1 -
 man/enableBookmarking.Rd                           |    1 -
 man/exportTestValues.Rd                            |    1 -
 man/exprToFunction.Rd                              |    1 -
 man/fileInput.Rd                                   |   11 +-
 man/fillPage.Rd                                    |    1 -
 man/fillRow.Rd                                     |    3 +-
 man/fixedPage.Rd                                   |    1 -
 man/flowLayout.Rd                                  |    1 -
 man/fluidPage.Rd                                   |    1 -
 man/freezeReactiveValue.Rd                         |   13 +-
 man/getQueryString.Rd                              |   93 ++
 man/headerPanel.Rd                                 |    1 -
 man/helpText.Rd                                    |    1 -
 man/hoverOpts.Rd                                   |    1 -
 man/htmlOutput.Rd                                  |    1 -
 man/icon.Rd                                        |    1 -
 man/inputPanel.Rd                                  |    1 -
 man/insertTab.Rd                                   |  148 +++
 man/insertUI.Rd                                    |    1 -
 man/installExprFunction.Rd                         |    1 -
 man/invalidateLater.Rd                             |    1 -
 man/is.reactivevalues.Rd                           |    1 -
 man/isRunning.Rd                                   |   15 +
 man/isolate.Rd                                     |    1 -
 man/knitr_methods.Rd                               |    5 +-
 man/mainPanel.Rd                                   |    1 -
 man/makeReactiveBinding.Rd                         |    1 -
 man/markOutputAttrs.Rd                             |   22 +
 man/markRenderFunction.Rd                          |    1 -
 man/maskReactiveContext.Rd                         |    1 -
 man/modalButton.Rd                                 |    1 -
 man/modalDialog.Rd                                 |    1 -
 man/navbarPage.Rd                                  |   12 +-
 man/navlistPanel.Rd                                |    4 +-
 man/nearPoints.Rd                                  |    1 -
 man/numericInput.Rd                                |    3 +-
 man/observe.Rd                                     |    1 -
 man/observeEvent.Rd                                |    6 +-
 man/onBookmark.Rd                                  |    2 +-
 man/onFlush.Rd                                     |    5 +-
 man/onStop.Rd                                      |   81 ++
 man/outputOptions.Rd                               |    1 -
 man/pageWithSidebar.Rd                             |    1 -
 man/parseQueryString.Rd                            |    1 -
 man/passwordInput.Rd                               |    3 +-
 man/plotOutput.Rd                                  |    3 +-
 man/plotPNG.Rd                                     |    1 -
 man/radioButtons.Rd                                |   56 +-
 man/reactive.Rd                                    |    3 +-
 man/reactiveFileReader.Rd                          |    3 +-
 man/reactivePlot.Rd                                |    1 -
 man/reactivePoll.Rd                                |   18 +-
 man/reactivePrint.Rd                               |    1 -
 man/reactiveTable.Rd                               |    1 -
 man/reactiveText.Rd                                |    1 -
 man/reactiveTimer.Rd                               |    1 -
 man/reactiveUI.Rd                                  |    1 -
 man/reactiveVal.Rd                                 |   83 ++
 man/reactiveValues.Rd                              |    1 -
 man/reactiveValuesToList.Rd                        |    1 -
 man/registerInputHandler.Rd                        |    1 -
 man/removeInputHandler.Rd                          |    1 -
 man/removeUI.Rd                                    |    1 -
 man/renderDataTable.Rd                             |    1 -
 man/renderImage.Rd                                 |    1 -
 man/renderPlot.Rd                                  |    2 +-
 man/renderPrint.Rd                                 |    1 -
 man/renderTable.Rd                                 |    1 -
 man/renderText.Rd                                  |    1 -
 man/renderUI.Rd                                    |    1 -
 man/repeatable.Rd                                  |    1 -
 man/req.Rd                                         |    3 +-
 man/restoreInput.Rd                                |    1 -
 man/runApp.Rd                                      |    1 -
 man/runExample.Rd                                  |    1 -
 man/runGadget.Rd                                   |    1 -
 man/runUrl.Rd                                      |    3 +-
 man/safeError.Rd                                   |    1 -
 man/selectInput.Rd                                 |    5 +-
 man/serverInfo.Rd                                  |    1 -
 man/session.Rd                                     |   14 +-
 man/setBookmarkExclude.Rd                          |    1 -
 man/setSerializer.Rd                               |   20 +
 man/shiny-options.Rd                               |    1 -
 man/shiny-package.Rd                               |    5 +-
 man/shinyApp.Rd                                    |   13 +-
 man/shinyDeprecated.Rd                             |    1 -
 man/shinyOptions.Rd                                |    1 -
 man/shinyServer.Rd                                 |    1 -
 man/shinyUI.Rd                                     |    1 -
 man/showBookmarkUrlModal.Rd                        |    1 -
 man/showModal.Rd                                   |    3 +-
 man/showNotification.Rd                            |    3 +-
 man/showReactLog.Rd                                |    1 -
 man/showTab.Rd                                     |   85 ++
 man/sidebarLayout.Rd                               |    1 -
 man/sidebarPanel.Rd                                |    1 -
 man/sliderInput.Rd                                 |    5 +-
 man/snapshotExclude.Rd                             |   14 +
 man/snapshotPreprocessInput.Rd                     |   19 +
 man/snapshotPreprocessOutput.Rd                    |   17 +
 man/splitLayout.Rd                                 |    1 -
 man/stacktrace.Rd                                  |   17 +-
 man/stopApp.Rd                                     |    1 -
 man/submitButton.Rd                                |    3 +-
 man/tabPanel.Rd                                    |    1 -
 man/tableOutput.Rd                                 |    3 +-
 man/tabsetPanel.Rd                                 |    4 +-
 man/textAreaInput.Rd                               |    3 +-
 man/textInput.Rd                                   |    3 +-
 man/textOutput.Rd                                  |    1 -
 man/titlePanel.Rd                                  |    1 -
 man/updateActionButton.Rd                          |    1 -
 man/updateCheckboxGroupInput.Rd                    |   29 +-
 man/updateCheckboxInput.Rd                         |    1 -
 man/updateDateInput.Rd                             |    1 -
 man/updateDateRangeInput.Rd                        |    1 -
 man/updateNumericInput.Rd                          |    1 -
 man/updateQueryString.Rd                           |   85 +-
 man/updateRadioButtons.Rd                          |   33 +-
 man/updateSelectInput.Rd                           |    1 -
 man/updateSliderInput.Rd                           |    1 -
 man/updateTabsetPanel.Rd                           |    3 +-
 man/updateTextAreaInput.Rd                         |    6 +-
 man/updateTextInput.Rd                             |    6 +-
 man/urlModal.Rd                                    |    1 -
 man/validate.Rd                                    |    3 +-
 man/verbatimTextOutput.Rd                          |    1 -
 man/verticalLayout.Rd                              |    1 -
 man/viewer.Rd                                      |    7 +-
 man/wellPanel.Rd                                   |    1 -
 man/withMathJax.Rd                                 |    1 -
 man/withProgress.Rd                                |    8 +-
 tests/testthat/test-bootstrap.r                    |  108 +-
 tests/testthat/test-get-extension.R                |   11 +
 tests/testthat/test-modules.R                      |   25 +-
 tests/testthat/test-plot-coordmap.R                |    8 +-
 tests/testthat/test-reactivity.r                   |   71 ++
 tests/testthat/test-utils.R                        |   21 +
 253 files changed, 5912 insertions(+), 1984 deletions(-)

diff --git a/DESCRIPTION b/DESCRIPTION
index ade7155..1101187 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,7 +1,7 @@
 Package: shiny
 Type: Package
 Title: Web Application Framework for R
-Version: 1.0.0
+Version: 1.0.5
 Authors at R: c(
     person("Winston", "Chang", role = c("aut", "cre"), email = "winston at rstudio.com"),
     person("Joe", "Cheng", role = "aut", email = "joe at rstudio.com"),
@@ -56,12 +56,13 @@ Authors at R: c(
     )
 Description: Makes it incredibly easy to build interactive web
     applications with R. Automatic "reactive" binding between inputs and
-    outputs and extensive pre-built widgets make it possible to build
+    outputs and extensive prebuilt widgets make it possible to build
     beautiful, responsive, and powerful applications with minimal effort.
 License: GPL-3 | file LICENSE
-Depends: R (>= 3.0.0), methods
-Imports: utils, httpuv (>= 1.3.3), mime (>= 0.3), jsonlite (>= 0.9.16),
-        xtable, digest, htmltools (>= 0.3.5), R6 (>= 2.0), sourcetools
+Depends: R (>= 3.0.2), methods
+Imports: utils, httpuv (>= 1.3.5), mime (>= 0.3), jsonlite (>= 0.9.16),
+        xtable, digest, htmltools (>= 0.3.5), R6 (>= 2.0), sourcetools,
+        tools
 Suggests: datasets, Cairo (>= 1.5-5), testthat, knitr (>= 1.6),
         markdown, rmarkdown, ggplot2, magrittr
 URL: http://shiny.rstudio.com
@@ -69,23 +70,24 @@ BugReports: https://github.com/rstudio/shiny/issues
 Collate: 'app.R' 'bookmark-state-local.R' 'stack.R' 'bookmark-state.R'
         'bootstrap-layout.R' 'conditions.R' 'map.R' 'globals.R'
         'utils.R' 'bootstrap.R' 'cache.R' 'diagnose.R' 'fileupload.R'
-        'graph.R' 'hooks.R' 'html-deps.R' 'htmltools.R'
-        'image-interact-opts.R' 'image-interact.R' 'imageutils.R'
-        'input-action.R' 'input-checkbox.R' 'input-checkboxgroup.R'
-        'input-date.R' 'input-daterange.R' 'input-file.R'
-        'input-numeric.R' 'input-password.R' 'input-radiobuttons.R'
-        'input-select.R' 'input-slider.R' 'input-submit.R'
-        'input-text.R' 'input-textarea.R' 'input-utils.R' 'insert-ui.R'
+        'graph.R' 'reactives.R' 'reactive-domains.R' 'history.R'
+        'hooks.R' 'html-deps.R' 'htmltools.R' 'image-interact-opts.R'
+        'image-interact.R' 'imageutils.R' 'input-action.R'
+        'input-checkbox.R' 'input-checkboxgroup.R' 'input-date.R'
+        'input-daterange.R' 'input-file.R' 'input-numeric.R'
+        'input-password.R' 'input-radiobuttons.R' 'input-select.R'
+        'input-slider.R' 'input-submit.R' 'input-text.R'
+        'input-textarea.R' 'input-utils.R' 'insert-tab.R' 'insert-ui.R'
         'jqueryui.R' 'middleware-shiny.R' 'middleware.R' 'modal.R'
         'modules.R' 'notifications.R' 'priorityqueue.R' 'progress.R'
-        'react.R' 'reactive-domains.R' 'reactives.R' 'render-plot.R'
-        'render-table.R' 'run-url.R' 'serializers.R'
-        'server-input-handlers.R' 'server.R' 'shiny-options.R'
-        'shiny.R' 'shinyui.R' 'shinywrappers.R' 'showcase.R' 'tar.R'
-        'test-export.R' 'timer.R' 'update-input.R'
-RoxygenNote: 5.0.1
+        'react.R' 'render-plot.R' 'render-table.R' 'run-url.R'
+        'serializers.R' 'server-input-handlers.R' 'server.R'
+        'shiny-options.R' 'shiny.R' 'shinyui.R' 'shinywrappers.R'
+        'showcase.R' 'snapshot.R' 'tar.R' 'test-export.R' 'timer.R'
+        'update-input.R'
+RoxygenNote: 6.0.1
 NeedsCompilation: no
-Packaged: 2017-01-09 19:02:04 UTC; winston
+Packaged: 2017-08-23 18:15:30 UTC; winston
 Author: Winston Chang [aut, cre],
   Joe Cheng [aut],
   JJ Allaire [aut],
@@ -118,4 +120,4 @@ Author: Winston Chang [aut, cre],
   R Core Team [ctb, cph] (tar implementation from R)
 Maintainer: Winston Chang <winston at rstudio.com>
 Repository: CRAN
-Date/Publication: 2017-01-12 00:49:04
+Date/Publication: 2017-08-23 20:20:10 UTC
diff --git a/LICENSE b/LICENSE
index a3fb8ea..483f6d0 100644
--- a/LICENSE
+++ b/LICENSE
@@ -12,7 +12,7 @@ these components are included below):
 - Respond.js, https://github.com/scottjehl/Respond
 - bootstrap-datepicker, https://github.com/eternicode/bootstrap-datepicker
 - Font Awesome, https://github.com/FortAwesome/Font-Awesome
-- selectize.js, https://github.com/brianreavis/selectize.js
+- selectize.js, https://github.com/selectize/selectize.js
 - es5-shim, https://github.com/es-shims/es5-shim
 - ion.rangeSlider, https://github.com/IonDen/ion.rangeSlider
 - strftime for Javascript, https://github.com/samsonjs/strftime
diff --git a/MD5 b/MD5
index 9c468d3..7d12b64 100644
--- a/MD5
+++ b/MD5
@@ -1,119 +1,111 @@
-87829d8932c5b2dace9fe3ad4707cbc4 *DESCRIPTION
-3ea3bb190b681ed860efbb72545a0de7 *LICENSE
-c2786b9d967c31e423683d011eddb8f8 *NAMESPACE
-a8c3d3c81b17040ac2f297dfb2fc1f45 *NEWS.md
-1162fa76c4fa048c037ac94fe66d17db *R/app.R
+abd60f266c89e0191040245488422716 *DESCRIPTION
+1887ac5c76d4937bdc81fc4d2ae2a0be *LICENSE
+e7133ff2ab8a64dcdecb2087f4673b16 *NAMESPACE
+f1316e1599ab7b357adcdd027b313a93 *NEWS.md
+c39f143ffc7c5edc9448286bd2e63c76 *R/app.R
 bc5f3a814689aee175d6472b08d45cff *R/bookmark-state-local.R
-126fca9813a088ddac9fa080e26b787c *R/bookmark-state.R
+9eafa825586f7ca17d7564287a1842d8 *R/bookmark-state.R
 ba54e23962a1b2a5b41ecbccba8170e1 *R/bootstrap-layout.R
-9c66b36bbbea9a49eca6f69202fcb536 *R/bootstrap.R
+dffaa3291d5d995e17e4e9afe7a94b6a *R/bootstrap.R
 d18246ad3e9786fc0a4e7485cbb63eca *R/cache.R
 c930e3553b07208d57cfa4240f2c4183 *R/conditions.R
 776e0c0c4e11677b365eeb18dd7754cc *R/diagnose.R
-a9c75936f800b277e302048d41c0c133 *R/fileupload.R
-b3edd134e3395689f36cf683e096fd32 *R/globals.R
+3801156f8b242e492ee46efe5d0ba420 *R/fileupload.R
+ee51cbfb4700ed583d4484b2feb443c1 *R/globals.R
 dba3b362ca79f6daef8cb712e844f5c9 *R/graph.R
+01fbbea9dc6ee0c120f0f3afca0aed2b *R/history.R
 60c83a73e77c0aabcc30c29cfd343c10 *R/hooks.R
-d51c0bdc4f4fdce2044436601117c50c *R/html-deps.R
+b1b4cf54b8b8746f257b1a7166432e8e *R/html-deps.R
 cb5f6557eed03c0e328c4930ccb2dd5b *R/htmltools.R
 a948ed0c0e57270308f3240987fc91c8 *R/image-interact-opts.R
 937c2a6ff9c2a0becd87929301ee58a9 *R/image-interact.R
 7389e7f4bc581a5f6bdff941b97929a8 *R/imageutils.R
 7f8f6802204589ab37d06172f5940e53 *R/input-action.R
 65d9ee630649a84e145e6a520ebb9c0d *R/input-checkbox.R
-411b22116c2e1b00417eb67ef6acb720 *R/input-checkboxgroup.R
+9616dbc7080f8d00d59339fcacc80c9c *R/input-checkboxgroup.R
 123716afe5ba949701ab8e12e6253eeb *R/input-date.R
-945aa3795285c8128d42c9e057baf5af *R/input-daterange.R
-3ca3eb926ae21ac315f2c964933795c2 *R/input-file.R
+5565e503ca696cebebda697cb00142fd *R/input-daterange.R
+f57968418bfe71dc628993a98e2f9357 *R/input-file.R
 fa92350905d2ebd34c9a5248d8330430 *R/input-numeric.R
 38fe4bab9adcf4e937539409cc9f9bd9 *R/input-password.R
-ac1b06b5184e16f88f346bf76f576de0 *R/input-radiobuttons.R
-caf808bd1c5eb50b9ae36b0ba6de6a35 *R/input-select.R
-2d48946eba984f4aaabc9b99e17148c5 *R/input-slider.R
+bd01d2bdcf8759223767c44b9dd58166 *R/input-radiobuttons.R
+00f1606dfcc284400ec8e0758aea5836 *R/input-select.R
+b9186566cac440b3a03aca495e09bf2f *R/input-slider.R
 e28536765e141943afb23fc9914257e3 *R/input-submit.R
 b7ae016e7e83bc0b2392b8f0aaa3a1d2 *R/input-text.R
 18117fb36bd141f40a2d428b4ebde85f *R/input-textarea.R
-7eac31e4b672ae2dbfd56eb120bbe253 *R/input-utils.R
+1434d1de4b948bd5170821638f5e04fe *R/input-utils.R
+698afc5e79d8b73c54c9cd8a53fa250e *R/insert-tab.R
 d2d2d55307c55b74433703cff2ed2590 *R/insert-ui.R
 eda55c83ceea5d4c0c370a9d0e917c7b *R/jqueryui.R
 059f74580e15ea18075ebc75d196a4b2 *R/map.R
 271adce2a34dd07803a59bab8dc5f21e *R/middleware-shiny.R
-7aa4d3e31e3d05eab6370ca2d3b98e74 *R/middleware.R
+edb93854773697ffd95afb924c9cd591 *R/middleware.R
 524669ba875dfb1292dd899bf6344d7d *R/modal.R
-6d0f7f380ee6dc6c3b7db6bb1fa2a6ab *R/modules.R
+7991358eba18d039380aa625bdc9e9cf *R/modules.R
 9eee0da791854d797abe756b3e557eda *R/notifications.R
 60974a727692bdfa4608c60e8a8e649e *R/priorityqueue.R
-0bd0e39113f584047eb540053e914377 *R/progress.R
+3b6094fb297e14d43f9b50414db0871a *R/progress.R
 0933ed20ab79b75e1afbd8123f907120 *R/react.R
 46c87fe2a02ddbc777432437239c121b *R/reactive-domains.R
-ce4b0accf871a97427637fd202bde48b *R/reactives.R
-170e0ca3ae345dc244e9d56496ecef80 *R/render-plot.R
-613aabcd6945abb8c642ff84bb1897e0 *R/render-table.R
+1f1f80daafaf1d9981d7a12ee405b6d2 *R/reactives.R
+7a204bebefca29579ba96046e1373437 *R/render-plot.R
+c8c2a48e52577a2a998a48f7c41806f2 *R/render-table.R
 f147ee14f642fdcc6912f86d447fe4ef *R/run-url.R
-1b787135fbb2bc3548b7b2e4186cc42a *R/serializers.R
-1efda313aeaaf2e5887511d97bdc1d17 *R/server-input-handlers.R
-d7a4b5155639557cae290029ef1b370c *R/server.R
+a112f811aaef3c0ef995a96c994e063f *R/serializers.R
+d40fbacec1f55c6766c846c2aa5578d7 *R/server-input-handlers.R
+927c6ac8ff0ae2aa7a0ed641757c7ef0 *R/server.R
 7413722439f9f9a4b384e3882e29ebb1 *R/shiny-options.R
-104ca784a8ec4abc21aec77b8d3f1f65 *R/shiny.R
-aaf2d667acb01884f99de0e4c1c62ddf *R/shinyui.R
-f48385e37beb1623fff0362d2d9336a7 *R/shinywrappers.R
-31498a9cc37604d758ec61194c5041a3 *R/showcase.R
+7762d289906f50592aa8dda034792f62 *R/shiny.R
+b6020be46c90a61d3a7808724267012d *R/shinyui.R
+538ef04828ee9c7b5c2de53b16f7daa0 *R/shinywrappers.R
+b2c54948d97a818e8ccc55ba8c06c78e *R/showcase.R
+26e425465b94d893480124dded884143 *R/snapshot.R
 175d3263ed5066754fb83d0894232c9f *R/stack.R
 3f61918de4e7b5aad9fedc41c12dcbfa *R/tar.R
 631abc9b6a1b7bc032bd48344ba5e0d9 *R/test-export.R
 650376bbc07bcff1258d7f2a7a64243f *R/timer.R
-08367ad1506e3c67cb4694651146d43c *R/update-input.R
-0ce1eb3f0d02b7d4ee3d63eaf45dadf0 *R/utils.R
-c98c21cb2969035684026a665046b71c *README.md
+cbaf62e1db3b010ee31c26202ad6bee5 *R/update-input.R
+3c8c383c2c4be71b1b920b4161fe1754 *R/utils.R
+02ba25dfcf6f985707c6add54f55fc42 *README.md
 13f970fb92cec9836ea297717872f46e *inst/examples/01_hello/DESCRIPTION
-85d0eaa1f8d68da015f0447469489143 *inst/examples/01_hello/Readme.md
-2bff53f9df19d6de6bd04fe0a06c2471 *inst/examples/01_hello/server.R
-58c2edbbe1e9797317f86f1c71b822a4 *inst/examples/01_hello/ui.R
+d7749eb95c7a7784dadd9af85f6676e4 *inst/examples/01_hello/Readme.md
+4f58327ec6b8b3da90e3726d49f49499 *inst/examples/01_hello/app.R
 d3b1c0d771db155d345b79c6ccf1f9c8 *inst/examples/02_text/DESCRIPTION
-5b172219d13f1f4b39ed7892197765dc *inst/examples/02_text/Readme.md
-18ae9d566d9d287e0efff4f707bf01a5 *inst/examples/02_text/server.R
-94b942e0e52a9203b1407b257e05eab1 *inst/examples/02_text/ui.R
+446a33445b2da61fdefc01555724e0a2 *inst/examples/02_text/Readme.md
+509459a761291c7d0fe07f493ce6debc *inst/examples/02_text/app.R
 33dfe9004b36e210472b5ce87154d801 *inst/examples/03_reactivity/DESCRIPTION
-65d5098f007b0aabe3f5bfb35ab134cc *inst/examples/03_reactivity/Readme.md
-b1235a7038404dda5f26974a0bbe545f *inst/examples/03_reactivity/server.R
-19c6b0066bb4effb47414dcfc8aa7a46 *inst/examples/03_reactivity/ui.R
+1406350bae213d4cd2f077e67f7eec9e *inst/examples/03_reactivity/Readme.md
+eda57578dce4da768883fc33a6ae31ec *inst/examples/03_reactivity/app.R
 00a88b6c31a74461d091e7c924e02710 *inst/examples/04_mpg/DESCRIPTION
-9ecc57ace7f7c6e7616740bf06cce2d8 *inst/examples/04_mpg/Readme.md
-2f22c896468e17bab2707fcd64410d7e *inst/examples/04_mpg/server.R
-30a731a6f209338f4c39596188c581a9 *inst/examples/04_mpg/ui.R
+87b16935976bb74159902b12cc767d13 *inst/examples/04_mpg/Readme.md
+44b6a6dbaf651eaef873186b9e17ce9f *inst/examples/04_mpg/app.R
 2c88b0033217c3596d5a9cd42888ee4f *inst/examples/05_sliders/DESCRIPTION
 9dea4065b5cef11b0a761952539d8893 *inst/examples/05_sliders/Readme.md
-4ed36a98b90af58221eb8d8cec3cf27b *inst/examples/05_sliders/server.R
-16b8fab7da19759737d3a6414375a77a *inst/examples/05_sliders/ui.R
+108f7f23834d1e86d6bf514e0e3f4321 *inst/examples/05_sliders/app.R
 f601f0341132edd28266937c9f9fcd8f *inst/examples/06_tabsets/DESCRIPTION
-f3c2f11e2df951548bdb75cbaf19946a *inst/examples/06_tabsets/Readme.md
-fdab09eb2c2be970e9a92f5762707252 *inst/examples/06_tabsets/server.R
-06feed1ca040463292775e0369c86b42 *inst/examples/06_tabsets/ui.R
+5e5cc1ec5d9c560e526a73d48327aa47 *inst/examples/06_tabsets/Readme.md
+1cb1cad04912eba89c246a7ef446b12e *inst/examples/06_tabsets/app.R
 448d49f3c1fd4f84bf6affd027605023 *inst/examples/07_widgets/DESCRIPTION
 75a55e4fdd6696b324a3b62107abaa75 *inst/examples/07_widgets/Readme.md
-3cc1347c380c0a7c1a2fb6f676f851e8 *inst/examples/07_widgets/server.R
-8e24b0e96dbd95d4be5bc4fb1860ae18 *inst/examples/07_widgets/ui.R
+74e5356ed7b82c9ec3367edf77015461 *inst/examples/07_widgets/app.R
 fc57e0cc1f1689531e8b6d2b308530a6 *inst/examples/08_html/DESCRIPTION
-5554a23aa8a10b633f5fddfe3d159d57 *inst/examples/08_html/Readme.md
-41124d8f6c1a9895786583462d83bee4 *inst/examples/08_html/server.R
-0785b66c45a23e001192e7a238473cee *inst/examples/08_html/www/index.html
+1a43e395464bfa0650827d8e232200f8 *inst/examples/08_html/Readme.md
+734716e20623fe72bd654b2c79aff5e1 *inst/examples/08_html/app.R
+6400328ef184a61a10971236aef84cd4 *inst/examples/08_html/www/index.html
 12d0f805ce58a70a7f69505c53617108 *inst/examples/09_upload/DESCRIPTION
-0ab651ad98f08f34e0589f69456c11f2 *inst/examples/09_upload/Readme.md
-283b2bae49c7f61e6bf8bc0eb1ebbae6 *inst/examples/09_upload/server.R
-25d426339fc3db342bc075c64aa3a22a *inst/examples/09_upload/ui.R
+d7099c3e2c81ebc0c1b9206cad33a0ef *inst/examples/09_upload/Readme.md
+2bd701ca78133711167e78dff33dc819 *inst/examples/09_upload/app.R
 6e681f3bb2f604aa20ee1b0a32ccaf55 *inst/examples/10_download/DESCRIPTION
-e17525eec6c34fcdcb81db74a73185a6 *inst/examples/10_download/Readme.md
-d762e479421f843d01ff968dae55e327 *inst/examples/10_download/server.R
-608192108ae87e9a847a5aa11cd2c129 *inst/examples/10_download/ui.R
+3bb23d0bb24bc5a676cc233639fa39ef *inst/examples/10_download/Readme.md
+9dde4cde08116cbf65a05da150fd8170 *inst/examples/10_download/app.R
 c481010dc652e7a3ab80140f461c0ef2 *inst/examples/11_timer/DESCRIPTION
 f1b063aae380d9463f3739dd60c3e666 *inst/examples/11_timer/Readme.md
-35d7c446405450684e2acef391dae83f *inst/examples/11_timer/server.R
-41f3b74ab8de992e28301ee763af45e5 *inst/examples/11_timer/ui.R
-ff5ae7942e0369cefa318637bcbe2e63 *inst/staticdocs/index.r
+2d20df93b6d6400ea0c53b4722dbc811 *inst/examples/11_timer/app.R
+759cd09ca01bc40a0717f93e59539381 *inst/staticdocs/index.r
 cd58550659401cbeb23bc3dad25399b4 *inst/template/default.html
 c6e3068736b084cd7d78d38452ced423 *inst/www-dir/index.html
 fc07eb5cb6354d9070657e2cf0df3658 *inst/www/reactive-graph.html
-0651d338f968e5b2e9f256d066542e88 *inst/www/shared/babel-polyfill.min.js
 b9b46bcc4dad6cc90fc4f95073c50735 *inst/www/shared/bootstrap/css/bootstrap-theme.css
 d6cc0a3c7532b74efe92da8992bd7542 *inst/www/shared/bootstrap/css/bootstrap-theme.css.map
 ab6b02efeaf178e0247b9504051472fb *inst/www/shared/bootstrap/css/bootstrap-theme.min.css
@@ -224,7 +216,7 @@ ec39d75cbc4de8171c2f6656a26816a3 *inst/www/shared/highlight/LICENSE
 e095ae120d1b8a9c12d0ebf7e7fa967b *inst/www/shared/highlight/classref.txt
 0b5415083b347c7d5f98b47bcf7e758a *inst/www/shared/highlight/highlight.pack.js
 097a443771d9f9e78d203331be166d3e *inst/www/shared/highlight/rstudio.css
-2ae68042ac97e2b2213b713deece387f *inst/www/shared/ionrangeslider/css/ion.rangeSlider.css
+1c40a65255f51087a28d21bc1afb1cd7 *inst/www/shared/ionrangeslider/css/ion.rangeSlider.css
 8d631a7cac12ac3c47d8bfe67f896543 *inst/www/shared/ionrangeslider/css/ion.rangeSlider.skinFlat.css
 7527c2a31899e27ddbc0fcba8dfc3b8d *inst/www/shared/ionrangeslider/css/ion.rangeSlider.skinHTML5.css
 06a452d69645df91f1903faf13aa7608 *inst/www/shared/ionrangeslider/css/ion.rangeSlider.skinModern.css
@@ -236,8 +228,8 @@ bcdb14f38e27b16edeabb62e6e9a829b *inst/www/shared/ionrangeslider/img/sprite-skin
 6035779c2555ab87be45bbc21f9cae47 *inst/www/shared/ionrangeslider/img/sprite-skin-modern.png
 41732f58be91fcdc79381f239685c0e1 *inst/www/shared/ionrangeslider/img/sprite-skin-nice.png
 43c6858e46da90d6e201dfed860e8b86 *inst/www/shared/ionrangeslider/img/sprite-skin-simple.png
-d4b2be381ee15900642f9ab06c9bfc65 *inst/www/shared/ionrangeslider/js/ion.rangeSlider.js
-ec750d838b13f4e4c8b79dcd89c098a8 *inst/www/shared/ionrangeslider/js/ion.rangeSlider.min.js
+11bb5d6b4c6d18b4de9ac1a981ae2ff8 *inst/www/shared/ionrangeslider/js/ion.rangeSlider.js
+cba79a8674a552fd022833ed67f8df51 *inst/www/shared/ionrangeslider/js/ion.rangeSlider.min.js
 f3b07ee7912d1f78c0535a919e4f192c *inst/www/shared/jquery-AUTHORS.txt
 fb2d334dabf4902825df4fe6c2298b4b *inst/www/shared/jquery.js
 4f252523d4af0b478c810c2547a63e19 *inst/www/shared/jquery.min.js
@@ -264,174 +256,185 @@ c12cac44216cf877fd0c6903f3794407 *inst/www/shared/jqueryui/jquery-ui.theme.min.c
 d75b17ebe7200b2ff0b8d20d32853c35 *inst/www/shared/selectize/css/selectize.bootstrap3.css
 bd681a8efd2f45628dddf749426dc633 *inst/www/shared/selectize/js/es5-shim.min.js
 146435eeda32f0e12bca8519f0da5ad9 *inst/www/shared/selectize/js/selectize.min.js
-c539894c4785ce273996b9d5376c048e *inst/www/shared/shiny-showcase.css
-226233db9edba2c3503b0df84ef8a1cc *inst/www/shared/shiny-showcase.js
+f11b608c0debab353b71155aed2514fb *inst/www/shared/shiny-showcase.css
+653a34006ad328671104166b9393ec33 *inst/www/shared/shiny-showcase.js
 b4db4844f8e15ea3da7bea9749b9b925 *inst/www/shared/shiny-testmode.js
-12879788a4d199155c6187779b1e5605 *inst/www/shared/shiny.css
-884d6152374c3a67f7de78c044133014 *inst/www/shared/shiny.js
-e1aae0c73e45c0744bd033a41fcd5e68 *inst/www/shared/shiny.js.map
-98b57c0172430410b1398f25893d36e8 *inst/www/shared/shiny.min.js
-00e742d4774bcf3f9ed3f55726a0d36c *inst/www/shared/shiny.min.js.map
+2aa583f053abd0f602693c919af85a2c *inst/www/shared/shiny.css
+49bb64dc74a2522aaf4f2d1390942ec2 *inst/www/shared/shiny.js
+38f2e0804e60905a3e917d7a2ac8e413 *inst/www/shared/shiny.js.map
+651b636032c868673d12ef38ed248ac5 *inst/www/shared/shiny.min.js
+f24fec1589a800cf25b2df7b68000485 *inst/www/shared/shiny.min.js.map
 d42bccda0accb4e937f79e5bd9540643 *inst/www/shared/showdown/compressed/showdown.js
 ae18a8f49f1a446ca1772447b95a99d0 *inst/www/shared/showdown/license.txt
 e192e62c348b4dc09fd3d9f32cd558c3 *inst/www/shared/showdown/src/showdown.js
 4feedff422885d51bf57601f8a987d70 *inst/www/shared/strftime/strftime-min.js
 d8ccfa9e1bad66e47dbd88a663fb5a86 *man/HTML.Rd
-c272996ea1fd62ed42113b3c4a9c06e6 *man/NS.Rd
-6b695cf8aa4e2e2f9000350dfa35ee63 *man/Progress.Rd
-9b645260993b8a9dba0e1de99dfeb47d *man/absolutePanel.Rd
-0983de4f0d434fb88e8a2179dee83ab2 *man/actionButton.Rd
-b5801edc866cc52676a891edda00df68 *man/addResourcePath.Rd
-f23a4205ab93ffde15bd44ba1404ea5d *man/applyInputHandlers.Rd
-d38b6b09e59d71964cd040c0b0d063a1 *man/bookmarkButton.Rd
-5c7e3991f2c6c68b4376d9d03c86f682 *man/bootstrapLib.Rd
-0ce227618e7e1c463a235374c8bdfa9d *man/bootstrapPage.Rd
-d1641fa3516d97c0890cca51a88e2586 *man/brushOpts.Rd
-a64dfa56528185a338efd8438e97ac3f *man/brushedPoints.Rd
+e23a3c16808ba0a990a166d50471d3ee *man/NS.Rd
+f0946a2a9514e8005451ed6793ea70b6 *man/Progress.Rd
+3b1c66d9183a07e7cd7dc210d4e92a2f *man/absolutePanel.Rd
+6c7e8dbd7d0b6e5db70de95156e7f7d7 *man/actionButton.Rd
+9196f885d43573245edcfa6e8c21e925 *man/addResourcePath.Rd
+8b39eef7c36603a8a6b20503b0e665da *man/applyInputHandlers.Rd
+e9f06de555dbb80e38d109989caafc28 *man/bookmarkButton.Rd
+6d1f01bac78e9e5a0396bb061c38b262 *man/bootstrapLib.Rd
+af6e6b2dbb10393ae17a55d2387f5fd9 *man/bootstrapPage.Rd
+cd10aaae837ef873c73dc07ec3d16c93 *man/brushOpts.Rd
+72a32260269895c274147f8a64e7ae16 *man/brushedPoints.Rd
 8953ddcb594cf24180e4ea1a563e5f0b *man/builder.Rd
-9e712890a20931b166ec8a82545deae7 *man/callModule.Rd
-df6def08194f9c22e0b70afd57ed5689 *man/checkboxGroupInput.Rd
-57c8638b00ed42a76290d99bb2ab86e3 *man/checkboxInput.Rd
-5c6fd00763d20fa97fc89a955913a631 *man/clickOpts.Rd
-9bacf8c019a10445b779a3fc0c64269e *man/column.Rd
-6c9870829d5833a37428af1cfd010461 *man/conditionalPanel.Rd
-f3c0810329c410cc843f5c6e9d440e16 *man/createWebDependency.Rd
-d8b80674884b9676da8c6523955628fd *man/dateInput.Rd
-82007d8ad0c65de77c28fa624d921af9 *man/dateRangeInput.Rd
-a941504ef1e6235ce5161b26cd207543 *man/dblclickOpts.Rd
-304376a72a996303e354d717884677b2 *man/debounce.Rd
-2d9b353d16b677b55a58ff0cff020541 *man/domains.Rd
-088e4bb6aa4558a58dd8fecf40847e72 *man/downloadButton.Rd
-b1b0171792a1332a9fc950fc05feea3c *man/downloadHandler.Rd
-1ad2440a33bd4cfb1abed64535c81bb6 *man/enableBookmarking.Rd
-4a14be9b31878c6e3ea36a5d9ef1c1bb *man/exportTestValues.Rd
-a285c035ff015a2575f8219c4ad6aa6d *man/exprToFunction.Rd
-6baa248b78954215db322d3853be3cbd *man/fileInput.Rd
-7e7ce1a49c339b757eda2facaecf3292 *man/fillPage.Rd
-843a9cfba2fe717c74b5b23580ececb7 *man/fillRow.Rd
-f380c5aa9dc647d7f9b9ffbf7ee0a251 *man/fixedPage.Rd
-e583f217b68ed5f8e6f1b54a78846d1c *man/flowLayout.Rd
-eccc5ddd4b6d2efa6b366b19983d293d *man/fluidPage.Rd
-1e7a5118508425f723db518ce5ef10e7 *man/freezeReactiveValue.Rd
-56046edbee3dff3739e86ebdf2feec48 *man/headerPanel.Rd
-a4ba8d8c599204740128fea8b6711029 *man/helpText.Rd
-b7e98aeab986df2f38906b9810c43ecb *man/hoverOpts.Rd
-406342e1835b163037604718f4d2be33 *man/htmlOutput.Rd
+1c54017f6abae5ea610ca0e6a94fd2be *man/callModule.Rd
+7ac3c0b1d712c7aab3742e751f8d9392 *man/checkboxGroupInput.Rd
+80dc942a5ae8199071f5475e369a08b3 *man/checkboxInput.Rd
+9f375aaae5fed18d5ae2dbf82ef1806c *man/clickOpts.Rd
+3021281f9f1dbe1148f8d2e3a74945e7 *man/column.Rd
+b2c9985436db7d30dbb9d4f61eca68cb *man/conditionalPanel.Rd
+5ffc5bd3aa288a7285fad9731c598f2c *man/createWebDependency.Rd
+c4d1dd373f95e65cae112344c27e530e *man/dateInput.Rd
+1533238432260f5cf8afa074542d45e8 *man/dateRangeInput.Rd
+4d90ed07a33b73bcd778515a5192249a *man/dblclickOpts.Rd
+3e0a81845214376fd5e0bbfc1703fd07 *man/debounce.Rd
+669ec284feec5faf7c393a03472117a7 *man/domains.Rd
+3f09c4b535a3252a15a3fa640256ba00 *man/downloadButton.Rd
+4fe68ea5179a415ef4b458a3c61ec5f6 *man/downloadHandler.Rd
+ef8ae2b5b8feaeee970ce37905bf2df4 *man/enableBookmarking.Rd
+5f4a2bfb16112c726196691cd1fae183 *man/exportTestValues.Rd
+f2f0dfa0f116584758968e3e09d12984 *man/exprToFunction.Rd
+319f3e9fb62b0f81eec8e7f108b4e8ae *man/fileInput.Rd
+38b3a66fe81475de11d0419cd7f81959 *man/fillPage.Rd
+c8e6c5f78c16d2a9c45451a1c01cf436 *man/fillRow.Rd
+016bbfa2ae9aec46bdd8ad4fcdf01184 *man/fixedPage.Rd
+77a39470b7fc7be1f0339a847fe88c2e *man/flowLayout.Rd
+daa42995324616f1f93c3aa5f6b79b54 *man/fluidPage.Rd
+177f7aa6ed7a2c59d605500e4678aa5f *man/freezeReactiveValue.Rd
+3be7c3e32cd6165eb1214798407a2590 *man/getQueryString.Rd
+7ca9c9ae3b0aba3dacdcbdcb922ab0a5 *man/headerPanel.Rd
+ca943bc9a44fcd9c01377c4fd4dbe17c *man/helpText.Rd
+6f899435881d7c6010efdaf7bfdf8c03 *man/hoverOpts.Rd
+a89aeec52412e2df523b0ff1bd8db126 *man/htmlOutput.Rd
 b6db1c34fc97f2fa8fb24f7d2713aec5 *man/htmlTemplate.Rd
-6ae1540704407e789762a0fc97f21fe6 *man/icon.Rd
+4a3b979871117f8f93ce6f59e67a71a7 *man/icon.Rd
 82b947ce74f2de68a3d3d9f40c73770d *man/include.Rd
-669379d570b9756032d5739598566053 *man/inputPanel.Rd
-9c3b0afb61ab3434e56b0f2b3b6eee8f *man/insertUI.Rd
-388595ea019cb50b911585a0e83385ab *man/installExprFunction.Rd
-e076609c816c0988da5c2be9198fc820 *man/invalidateLater.Rd
-c4df81c50b02bbab02a021451f779900 *man/is.reactivevalues.Rd
-d90537fdcf9d3a8e70606e24194b80ec *man/isolate.Rd
-e6422e37aa4e65a851ea929c44537daa *man/knitr_methods.Rd
+4ec676989453fc1466a9e356a9d7ff93 *man/inputPanel.Rd
+3862969d38b21d980938c6b1c60445ee *man/insertTab.Rd
+fcb421105b3e338cf2a0da72aa5025ab *man/insertUI.Rd
+474bab3bc6a6e1fcce24995bb0359a11 *man/installExprFunction.Rd
+ce5cb7044423b2917711ec77e3ac3321 *man/invalidateLater.Rd
+4a70ba89cc0ac271e5ea3d9485349f29 *man/is.reactivevalues.Rd
+7713a4975c3197d3fb614fe7348e5910 *man/isRunning.Rd
+f6eade4b9899e193356441ec5fce61d9 *man/isolate.Rd
+b4ad8201c8ee6139856982be87f626a8 *man/knitr_methods.Rd
 0cef753d073f90ea736a0b259cb53c3f *man/knitr_methods_htmltools.Rd
-cbc1ae1138599f3156f30fea5d91a148 *man/mainPanel.Rd
-5a7e974ba5c1909aea3940f62535e5be *man/makeReactiveBinding.Rd
-4833824d625986e1b6cf9c38803dabb9 *man/markRenderFunction.Rd
-658fedea2ffa8f76b52ed1c5f70b02e9 *man/maskReactiveContext.Rd
-198500a6aad23a6f97d9b4440cf25db2 *man/modalButton.Rd
-7a8bda204c8d2e590f36118a4678c918 *man/modalDialog.Rd
-6ef0d9c2932cbc94fbdcfb2ec55c55bb *man/navbarPage.Rd
-6a5f9a365314215f24b6fdac95819f81 *man/navlistPanel.Rd
-c5093ba7b905be444ec88e92c96601fd *man/nearPoints.Rd
-6425976695c5876714e9aafb25dd4e56 *man/numericInput.Rd
-502664a517e3252a2ecd47ffd12657b7 *man/observe.Rd
-c33f35b9295bfd7a3c38fb1823ee53cf *man/observeEvent.Rd
-775a1e9289ca94c74b5bde1cb7570bec *man/onBookmark.Rd
-a6066c56c39f8ef669f6afa7b0d14665 *man/onFlush.Rd
-5f97903060489ca6bf90931b1efd434c *man/outputOptions.Rd
-64956bda426d937ece36462ce7d64664 *man/pageWithSidebar.Rd
-32188a9e53789dca0e17a0956f2c5e0f *man/parseQueryString.Rd
-81fe1abb4ed16abcfb3cb33051438adc *man/passwordInput.Rd
-fe835df646692ac7eadc7dc0d36da99a *man/plotOutput.Rd
-ea0107429a4eef996a2006adccd3846f *man/plotPNG.Rd
-629ecf8d44e9d10b323a317b2e424a17 *man/radioButtons.Rd
-c837729b4fc389cd68abc41bc254053c *man/reactive.Rd
-4734037e317cbe3fe6aab349f1802a22 *man/reactiveFileReader.Rd
-1233e22b239fff94341d8a8c2b3d7f4f *man/reactivePlot.Rd
-8430f33d78d7ed9bb11107996137c100 *man/reactivePoll.Rd
-ba03335e953884bb60bce143cff17a84 *man/reactivePrint.Rd
-69ba7fcda401f11bb6a1297d9cbb31a5 *man/reactiveTable.Rd
-b09c00b93df747fe2e0e63ba438d68e9 *man/reactiveText.Rd
-39abeb3429c3509befab9538f8effed1 *man/reactiveTimer.Rd
-3c02e74514e9a44e0b6a06fb21ef7c02 *man/reactiveUI.Rd
-c84a1d66e76e48ccb58d2ba45abcdb74 *man/reactiveValues.Rd
-5a3d5ff42b6ffdadd689873b61f30e08 *man/reactiveValuesToList.Rd
-d1197c9ae61dea32d0c572b007a6d977 *man/registerInputHandler.Rd
-5acd414830d166e09a322fb79abe8b8d *man/removeInputHandler.Rd
-e165d130c98a3a604eab4bdf2fece4c3 *man/removeUI.Rd
-c2f3efe5e160871890a8f35f5ca32a0c *man/renderDataTable.Rd
-3cec290a138279854e41bbabbed01e6c *man/renderImage.Rd
-07f9b12328d89985c84b2e4e875cfc00 *man/renderPlot.Rd
-411b9f59f26c4148ab98a9a809a80ac1 *man/renderPrint.Rd
-a7f657cc7de1d2b53391731fb4c1701a *man/renderTable.Rd
-9713fe1dff93b89d8400a76528373143 *man/renderText.Rd
-cb63e3d50cf01f80de1b720febe4e32b *man/renderUI.Rd
-28c14b86847bad36ea6b4a01b0a9d314 *man/repeatable.Rd
-aa771cdab26c33133cf361b289ba503a *man/req.Rd
-f0670deffbe1f77380892ca1f5a8b37e *man/restoreInput.Rd
-f320b68865d50839f115856139666c70 *man/runApp.Rd
-6e500ae957bc388d27e14b5bc1851b68 *man/runExample.Rd
-1524263164e53933bc07e3eb968a113a *man/runGadget.Rd
-a26b63c9973d07c18281d758bfac1d28 *man/runUrl.Rd
-fc0e60e0d97dcc6fca79678b8b3ef925 *man/safeError.Rd
-f32c4c17d259eb6d06c32b5642cb3f81 *man/selectInput.Rd
-312354ec69a77cc6f25c682811203170 *man/serverInfo.Rd
-5906c1d9bc243cf3f1283342ed8dbb1b *man/session.Rd
-2da0c9c542da2c542c440713e9171468 *man/setBookmarkExclude.Rd
-1a4118e80bcdf4086c793bcf91ca2a86 *man/shiny-options.Rd
-30cc9e53fee6282852effbdad93a696e *man/shiny-package.Rd
-dce9964b142672d221649101383c418c *man/shinyApp.Rd
-d5ec7a3b8a327bcd55172015ed341e81 *man/shinyDeprecated.Rd
-20198c4520f4383415916da04d79d716 *man/shinyOptions.Rd
-0aaa8b2fd73d0f2505700a4433435e65 *man/shinyServer.Rd
-5afad6e82d6fcba704801a058d7b2df9 *man/shinyUI.Rd
-12f2e5120721a796286b3839d56a2294 *man/showBookmarkUrlModal.Rd
-5446bebae929fd941f5bd6cd7c0e5a22 *man/showModal.Rd
-c4d7c2453fbdabcf687b871e6af4b300 *man/showNotification.Rd
-0b835143915d5213dad9a0c0a2d03b52 *man/showReactLog.Rd
-e7e92a912dc3b56c30f85f5436bc9036 *man/sidebarLayout.Rd
-ed31f0a3d0e3457d9d3f1b22a6d1d4a7 *man/sidebarPanel.Rd
+69edee9c30e3640bf89b71e3e449c2fd *man/mainPanel.Rd
+dba808bebd281797dae21475bdf6049f *man/makeReactiveBinding.Rd
+99c19e4fb01d4652e344855277761e20 *man/markOutputAttrs.Rd
+d0726ee9f541425ea7a24087e711169d *man/markRenderFunction.Rd
+fb25fe1b240e69fe51a6dac08859bdff *man/maskReactiveContext.Rd
+ca399e628342726fd49e5c40939ce608 *man/modalButton.Rd
+ba5a1fc6334585a011a62cad5e8d6591 *man/modalDialog.Rd
+32ef8b6100b718bf1a9b7f69cd93f4e2 *man/navbarPage.Rd
+eb0b5402fb46286166820e3741ea3264 *man/navlistPanel.Rd
+952a528824718be5e3bd884686c3b6de *man/nearPoints.Rd
+88351da92e9f148adc3bcd5ec7fc6f2b *man/numericInput.Rd
+f65d1cb8ea75892cbcb7b3d738997aec *man/observe.Rd
+7e676c73257595bebe6769bd61519351 *man/observeEvent.Rd
+e08a010d9530707763c4c96dcfb5571f *man/onBookmark.Rd
+57a8ade7a7d296b463b2ac2913ad9e33 *man/onFlush.Rd
+bb5582cf2d2f58ce05cff2d3ee725370 *man/onStop.Rd
+749153fa8047402a7ba847eeff2c4bdb *man/outputOptions.Rd
+4776e90efb6fe434d1081e99579f977c *man/pageWithSidebar.Rd
+3dcd2e2897b0a7d22af04241f65c56b0 *man/parseQueryString.Rd
+d19c6b8179b86daf4fdcc57a58d7562f *man/passwordInput.Rd
+0bbe2f1e005d335a920613e6ca37aa17 *man/plotOutput.Rd
+81a2343d915b729b5d538b1753b55101 *man/plotPNG.Rd
+12c1a515662274b73c66204c07433148 *man/radioButtons.Rd
+93154b3476a821a3d1cef37bcfedba69 *man/reactive.Rd
+5ac5efad0a357d2cfa8c87c18bfa0671 *man/reactiveFileReader.Rd
+aa93922fd476f66f137cae2508f80a90 *man/reactivePlot.Rd
+e8fe72f87512afb6fbb469e2684736b5 *man/reactivePoll.Rd
+7e6f7ecbfa2a70c0746959ae045a2524 *man/reactivePrint.Rd
+86fe2483ce2638fc4c912d0555b5f1b1 *man/reactiveTable.Rd
+667c617489d2b5ffa4bae7a272ef6714 *man/reactiveText.Rd
+2aed894035cfadfd4d8a88b8b67eb330 *man/reactiveTimer.Rd
+d1f1a32e6a67931374223a69cd7a1237 *man/reactiveUI.Rd
+8520943641ebfd3e9fbc8ee255c03ca7 *man/reactiveVal.Rd
+4d03b7963defb9459c0fd91a285b4cb6 *man/reactiveValues.Rd
+25959e5f7c1c5b0c50d4848dd24f3eab *man/reactiveValuesToList.Rd
+bee751be2821f4d957f5b353b2f28818 *man/registerInputHandler.Rd
+96be8138decbe2350d44c4f21b404b07 *man/removeInputHandler.Rd
+f78776dd0fefe0d47bdc2a3e0667d4db *man/removeUI.Rd
+195684ce8aeef8284d88884a35a545fe *man/renderDataTable.Rd
+07c987a254da9ff9b5315688c1abf652 *man/renderImage.Rd
+6852ab6e61a77cb341693706f76c5bbe *man/renderPlot.Rd
+4cf762564562fd3c14e6356e24c1795a *man/renderPrint.Rd
+4dc8fd831e429c26459c179e2ce54e26 *man/renderTable.Rd
+1218dbeac3f7b75657328039abed5c94 *man/renderText.Rd
+6790efb00aa8a9991c7e047bc4ead0b6 *man/renderUI.Rd
+c44457e7ad5c92cfce956c47b735ae45 *man/repeatable.Rd
+c8e7c8490ee2e13aaa58b113f1083259 *man/req.Rd
+5ef7b1a3dbafff8aa3d2fb1d0ee0bdda *man/restoreInput.Rd
+790429ea7a61977c1f3d90394e028881 *man/runApp.Rd
+809d886ca5fcb77aee86bb9d7474a6aa *man/runExample.Rd
+1070e6100267e1b8c5383fff5b17919c *man/runGadget.Rd
+43bbedbc43b042163357d9aa10bf604a *man/runUrl.Rd
+9f955f2e3673503125317e510215fe50 *man/safeError.Rd
+0d551df7bea011865bcae29d00ee6adf *man/selectInput.Rd
+cff87be6c6083c833a4ace9c66d5612b *man/serverInfo.Rd
+d86e4c9c991936fb11f1aba5ccfc344f *man/session.Rd
+0fc9b1cb40f814b323e13b725abb2b85 *man/setBookmarkExclude.Rd
+c0be993d56d2e4421937cae2c019d5f0 *man/setSerializer.Rd
+656b1733f79b9b68fce76c877fae9d53 *man/shiny-options.Rd
+a45149361bb44db83c25825b3b31d56d *man/shiny-package.Rd
+2510e1cd43989165950ebcee0a88acf0 *man/shinyApp.Rd
+ce7b953393aacc9f36ef2f01759fb99c *man/shinyDeprecated.Rd
+8d6c470681c0f3622719070c674060eb *man/shinyOptions.Rd
+0dcd9fbeef272a991a65c64570252348 *man/shinyServer.Rd
+30dd296cbc400a335dc64938e8492382 *man/shinyUI.Rd
+350106adf5b2f4b0591e326aee4e2462 *man/showBookmarkUrlModal.Rd
+baf1569b0a975080d00fe91536fb9cbe *man/showModal.Rd
+4368c90678bb10a7c2722ba314efc617 *man/showNotification.Rd
+2966f0e08bdf592adba03f24138e569b *man/showReactLog.Rd
+d53ba4d8444eb550c23752cab9b9f837 *man/showTab.Rd
+8acc14c99057ca1a8a54ce64c8844f04 *man/sidebarLayout.Rd
+3f32b486b17a4ee0f84d77c93083e20e *man/sidebarPanel.Rd
 025c15a6e3be95777dda20d2559a7497 *man/singleton.Rd
-e59447049feaabd35597c00756fd4874 *man/sliderInput.Rd
-be0642fdffe02e7ec8cd3ee006a576ed *man/splitLayout.Rd
-f1dbb30aeb7032894d8af89caddf1e8f *man/stacktrace.Rd
-029e949411bc4c0aa9658e32f7319aaa *man/stopApp.Rd
-cd1d9f648c339ff3f03e047dd63832bf *man/submitButton.Rd
+5aae972390ba1cfb4fb277705d4b307d *man/sliderInput.Rd
+36648c74dea8830fe983fe54efdaaca4 *man/snapshotExclude.Rd
+7d9762c79acc54ce356f08b1da2615d0 *man/snapshotPreprocessInput.Rd
+9cb6b5c80abe1b34fd63eddcdad0146a *man/snapshotPreprocessOutput.Rd
+56efb57661899c6db06318cfd7bb3e4e *man/splitLayout.Rd
+2ffc92d901f48769cabd3c30f33bcb6b *man/stacktrace.Rd
+3f2e7a78046eedb71b0794b4349e2f7e *man/stopApp.Rd
+b691787a30fee483185649224c67d0b6 *man/submitButton.Rd
 d32cc8ebf7ebce324a0983102a0a6de9 *man/suppressDependencies.Rd
-1b43b425f3cc300e5624e01f80802af7 *man/tabPanel.Rd
-f98f806c6a8d5837242934318e4c7d77 *man/tableOutput.Rd
-6fdfc632cfd96a91746d5cc168359b68 *man/tabsetPanel.Rd
+95c96aaab9500a1efac89f22cd49d7aa *man/tabPanel.Rd
+ea21776e3a795b75e073ae61952e751c *man/tableOutput.Rd
+acb1b8880f3ada019eb3017afc3c9033 *man/tabsetPanel.Rd
 df812031fdc7a63233007259573230e5 *man/tag.Rd
-790855f0657c91551f3c288d256bed1a *man/textAreaInput.Rd
-f75162e96f0cb3df7bd385cb0b600332 *man/textInput.Rd
-f53a4f566d6a81d6b578f386684cc50d *man/textOutput.Rd
-01e84543a9c89efdcb047e79db9b0798 *man/titlePanel.Rd
-0c0bc2ae8bddcd03a94c277cbaadfae0 *man/updateActionButton.Rd
-1ad46a40db182925ea25b172a213343a *man/updateCheckboxGroupInput.Rd
-db1b0f13650b95f71793ae1bd094de40 *man/updateCheckboxInput.Rd
-0c356b3d03d1309dd111198731254df1 *man/updateDateInput.Rd
-2b93a7bf335d9c693a3fa323fb8bdaf0 *man/updateDateRangeInput.Rd
-6fc25fe24ace006d2d472e0dca0d7140 *man/updateNumericInput.Rd
-dd9d5a3e653c5eb24914fc0c87d5279e *man/updateQueryString.Rd
-ffb9ee1fbcd72ae2c05f28d248bf9808 *man/updateRadioButtons.Rd
-599b08675ceb6a0b94bcf38b4c91136d *man/updateSelectInput.Rd
-9a3a91b1779425416a020aa2face286f *man/updateSliderInput.Rd
-c3aa33fcc31510f39c9f08b003bd37df *man/updateTabsetPanel.Rd
-e6af9d2a830c2556201347e1c76f1b2a *man/updateTextAreaInput.Rd
-546a3d58223af5c48c287440c29013a0 *man/updateTextInput.Rd
-f4e84efeebbd28c7ef8646401ec5af6d *man/urlModal.Rd
-f7500412dc367e7b74b2c74b3ddfd1e9 *man/validate.Rd
+da1ddda86c0c084e887622184e20e7ad *man/textAreaInput.Rd
+027f77e9366668807ba11853510e549b *man/textInput.Rd
+1fb73cd5ecebfcc96902c053680a3a8f *man/textOutput.Rd
+0049fe2bda692dd0b40c2e9b7281cd79 *man/titlePanel.Rd
+a0edd66d26f5309519580870360087a6 *man/updateActionButton.Rd
+6e5621a57c37c929759853b1dbdf9d28 *man/updateCheckboxGroupInput.Rd
+3d6b88f254b0119aec719b8c857c00e4 *man/updateCheckboxInput.Rd
+b294a7c2d8f93a0afefc6036f74c728d *man/updateDateInput.Rd
+83474ca30abbcdc00a8738ee6f30754a *man/updateDateRangeInput.Rd
+38836e328c06257651cd007ea8264d48 *man/updateNumericInput.Rd
+26be04bbc8ebdaf2983dd04d58ceee5d *man/updateQueryString.Rd
+69854b3b86dc86f6c51b56bd8f1a56da *man/updateRadioButtons.Rd
+bec21efaf2fad68133977af7d64b8d7b *man/updateSelectInput.Rd
+1afe10c77f03961c3efad89ecb6df3cc *man/updateSliderInput.Rd
+a283947a462404f857359dfd4e451759 *man/updateTabsetPanel.Rd
+823bb5b7995dc0df7a7667310e13e0c3 *man/updateTextAreaInput.Rd
+ab060fe1608671a42b293ed8ebfb2fb1 *man/updateTextInput.Rd
+6bcb2d94dece8f1c75c08efc5dab231e *man/urlModal.Rd
+ef18b97daf3ced29f2fe4dc3eb8bb10e *man/validate.Rd
 21bdae51050d3832ab46550659947bd0 *man/validateCssUnit.Rd
-aa7ae49457a120d4b986aac02630ce80 *man/verbatimTextOutput.Rd
-2b7dbd754c2b127a4c14019e27d904e5 *man/verticalLayout.Rd
-ca965f9478634d57819776471e7df8c6 *man/viewer.Rd
-29fc1b3faa440f561c6b78d02d6ccbe4 *man/wellPanel.Rd
-a12068140cb7a01bdf11ef6168e328f3 *man/withMathJax.Rd
-18329794b5438873183e0d75e4448de9 *man/withProgress.Rd
+c7ba4156c106d2cec3771dff7506eea3 *man/verbatimTextOutput.Rd
+b42d3f31c7a69d88ca2d62fb68af7f93 *man/verticalLayout.Rd
+10056cc8f2fee682fa87d3ae781abf72 *man/viewer.Rd
+1227c84fe5f1be3638aafff802ff0dd0 *man/wellPanel.Rd
+e1d1a891f130c90cd450551e726c88b8 *man/withMathJax.Rd
+3844592740e63bdf9957a5e6134080df *man/withProgress.Rd
 0138d018b7594f66e96ec66f82763758 *man/withTags.Rd
 d44a15e0a188bccb87dcab4fefdc872e *tests/test-all.R
 89f3a210025c4dee6c10f4b357eefbbf *tests/test-encoding/01-symbols/app.R
@@ -440,14 +443,15 @@ f83fbbf30d068acc35d1180c2f078c57 *tests/test-encoding/02-backslash/ui.R
 f58976e91aff9e28a61444ca5926dc57 *tests/test-encoding/test-all.R
 02f05d1aa4c8bcb4808c39938deed480 *tests/testthat/helper.R
 12821fa94f20f5207fad48c25bb57178 *tests/testthat/test-bookmarking.R
-4657d2b5d67291cc64000b7213acb29e *tests/testthat/test-bootstrap.r
+65ee64b2b72e6efdd5e3dc32805d6afc *tests/testthat/test-bootstrap.r
 3e5c74660dbf4c2d5e21961465c5c973 *tests/testthat/test-diagnostics.R
 dbe84e866824cfdf042f6d92729188cf *tests/testthat/test-gc.r
+9c4e3951ad85a57f22e156badc1b4517 *tests/testthat/test-get-extension.R
 8ae16d4481858309a4625678e2b8e040 *tests/testthat/test-input-handler.R
-f9ee5309105f8f0c9743572e7409336f *tests/testthat/test-modules.R
+388ab7574cd8f63b27f469cdcf94cb88 *tests/testthat/test-modules.R
 f3f69a0a024170377ac09de1c788ba12 *tests/testthat/test-options.R
-093c126111fb2a656665464f4238b2cd *tests/testthat/test-plot-coordmap.R
-1f7e7402d887e941ba458148c3827a46 *tests/testthat/test-reactivity.r
+0391fbeb3f9bdc6b50d50f54e3f6490a *tests/testthat/test-plot-coordmap.R
+e70250a7f5d552b8021b2cda9bcfc008 *tests/testthat/test-reactivity.r
 bc84dab9f44f9079568a0c2e4fc83884 *tests/testthat/test-stack.R
 5c2dfe371178ff9274169cde1bc7e5f3 *tests/testthat/test-stacks.R
 fbace4e45a8036a85bdd983da02b330c *tests/testthat/test-staticdocs.R
@@ -457,4 +461,4 @@ fe4b5516a083f1c9ede932e37b27e34c *tests/testthat/test-timer.R
 c59f5d981a17ad5c7e93f367abb45a1b *tests/testthat/test-ui.R
 77572f6e0036925f9c37a120d93beba7 *tests/testthat/test-update-input.R
 10bf6c1026eb046245839061ea91494b *tests/testthat/test-url.R
-a548f446b80e3790a5874aefaf802eed *tests/testthat/test-utils.R
+9c58a5b13bbdbbedae4a25e1095a19c5 *tests/testthat/test-utils.R
diff --git a/NAMESPACE b/NAMESPACE
index a9e62b7..377f18d 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -22,6 +22,8 @@ S3method(as.shiny.appobj,list)
 S3method(as.shiny.appobj,shiny.appobj)
 S3method(as.tags,shiny.appobj)
 S3method(as.tags,shiny.render.function)
+S3method(format,reactiveExpr)
+S3method(format,reactiveVal)
 S3method(names,reactivevalues)
 S3method(print,reactive)
 S3method(print,shiny.appobj)
@@ -38,6 +40,7 @@ export(actionButton)
 export(actionLink)
 export(addResourcePath)
 export(animationOptions)
+export(appendTab)
 export(as.shiny.appobj)
 export(basicPage)
 export(bookmarkButton)
@@ -84,9 +87,12 @@ export(flowLayout)
 export(fluidPage)
 export(fluidRow)
 export(formatStackTrace)
+export(freezeReactiveVal)
 export(freezeReactiveValue)
 export(getDefaultReactiveDomain)
+export(getQueryString)
 export(getShinyOption)
+export(getUrlHash)
 export(h1)
 export(h2)
 export(h3)
@@ -95,6 +101,7 @@ export(h5)
 export(h6)
 export(headerPanel)
 export(helpText)
+export(hideTab)
 export(hoverOpts)
 export(hr)
 export(htmlOutput)
@@ -109,6 +116,7 @@ export(includeMarkdown)
 export(includeScript)
 export(includeText)
 export(inputPanel)
+export(insertTab)
 export(insertUI)
 export(installExprFunction)
 export(invalidateLater)
@@ -116,6 +124,7 @@ export(is.reactive)
 export(is.reactivevalues)
 export(is.shiny.appobj)
 export(is.singleton)
+export(isRunning)
 export(isTruthy)
 export(isolate)
 export(knit_print.html)
@@ -147,6 +156,7 @@ export(onReactiveDomainEnded)
 export(onRestore)
 export(onRestored)
 export(onSessionEnded)
+export(onStop)
 export(outputOptions)
 export(p)
 export(pageWithSidebar)
@@ -156,6 +166,7 @@ export(passwordInput)
 export(plotOutput)
 export(plotPNG)
 export(pre)
+export(prependTab)
 export(printError)
 export(printStackTrace)
 export(radioButtons)
@@ -168,12 +179,14 @@ export(reactiveTable)
 export(reactiveText)
 export(reactiveTimer)
 export(reactiveUI)
+export(reactiveVal)
 export(reactiveValues)
 export(reactiveValuesToList)
 export(registerInputHandler)
 export(removeInputHandler)
 export(removeModal)
 export(removeNotification)
+export(removeTab)
 export(removeUI)
 export(renderDataTable)
 export(renderImage)
@@ -197,6 +210,7 @@ export(selectizeInput)
 export(serverInfo)
 export(setBookmarkExclude)
 export(setProgress)
+export(setSerializer)
 export(shinyApp)
 export(shinyAppDir)
 export(shinyAppFile)
@@ -207,10 +221,14 @@ export(showBookmarkUrlModal)
 export(showModal)
 export(showNotification)
 export(showReactLog)
+export(showTab)
 export(sidebarLayout)
 export(sidebarPanel)
 export(singleton)
 export(sliderInput)
+export(snapshotExclude)
+export(snapshotPreprocessInput)
+export(snapshotPreprocessOutput)
 export(span)
 export(splitLayout)
 export(stopApp)
diff --git a/NEWS.md b/NEWS.md
index 79215b6..81dae32 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,184 @@
+shiny 1.0.5
+===========
+
+## Full changelog
+
+### Bug fixes
+
+* Fixed [#1818](https://github.com/rstudio/shiny/issues/1818): `conditionalPanel()` expressions that have a newline character in them caused the application to not work. ([#1820](https://github.com/rstudio/shiny/pull/1820))
+
+* Added a safe wrapper function for internal calls to `jsonlite::fromJSON()`. ([#1822](https://github.com/rstudio/shiny/pull/1822))
+
+* Fixed [#1824](https://github.com/rstudio/shiny/issues/1824): HTTP HEAD requests on static files caused the application to stop. ([#1825](https://github.com/rstudio/shiny/pull/1825))
+
+
+shiny 1.0.4
+===========
+
+There are three headlining features in this release of Shiny. It is now possible to add and remove tabs from a `tabPanel`; there is a new function, `onStop()`, which registers callbacks that execute when an application exits; and `fileInput`s now can have files dragged and dropped on them. In addition to these features, this release has a number of minor features and bug fixes. See the full changelog below for more details.
+
+## Full changelog
+
+### New features
+
+* Implemented [#1668](https://github.com/rstudio/shiny/issues/1668): dynamic tabs: added functions (`insertTab`, `appendTab`, `prependTab`, `removeTab`, `showTab` and `hideTab`) that allow you to do those actions for an existing `tabsetPanel`. ([#1794](https://github.com/rstudio/shiny/pull/1794))
+
+* Implemented [#1213](https://github.com/rstudio/shiny/issues/1213): Added a new function, `onStop()`, which can be used to register callback functions that are invoked when an application exits, or when a user session ends. (Multiple sessions can be connected to a single running Shiny application.) This is useful if you have finalization/clean-up code that should be run after the application exits. ([#1770](https://github.com/rstudio/shiny/pull/1770)
+
+* Implemented [#1155](https://github.com/rstudio/shiny/issues/1155): Files can now be drag-and-dropped on `fileInput` controls. The appearance of `fileInput` controls while files are being dragged can be modified by overriding the `shiny-file-input-active` and `shiny-file-input-over` classes. ([#1782](https://github.com/rstudio/shiny/pull/1782))
+
+### Minor new features and improvements
+
+* Addressed [#1688](https://github.com/rstudio/shiny/issues/1688): trigger a new `shiny:outputinvalidated` event when an output gets invalidated, at the same time that the `recalculating` CSS class is added. ([#1758](https://github.com/rstudio/shiny/pull/1758), thanks [@andrewsali](https://github.com/andrewsali)!)
+
+* Addressed [#1508](https://github.com/rstudio/shiny/issues/1508): `fileInput` now permits the same file to be uploaded multiple times. ([#1719](https://github.com/rstudio/shiny/pull/1719))
+
+* Addressed [#1501](https://github.com/rstudio/shiny/issues/1501): The `fileInput` control now retains uploaded file extensions on the server. This fixes [readxl](https://github.com/tidyverse/readxl)'s `readxl::read_excel` and other functions that must recognize a file's extension in order to work. ([#1706](https://github.com/rstudio/shiny/pull/1706))
+
+* For `conditionalPanel`s, Shiny now gives more informative messages if there are errors evaluating or parsing the JavaScript conditional expression. ([#1727](https://github.com/rstudio/shiny/pull/1727))
+
+* Addressed [#1586](https://github.com/rstudio/shiny/issues/1586): The `conditionalPanel` function now accepts an `ns` argument. The `ns` argument can be used in a [module](https://shiny.rstudio.com/articles/modules.html) UI function to scope the `condition` expression to the module's own input and output IDs. ([#1735](https://github.com/rstudio/shiny/pull/1735))
+
+* With `options(shiny.testmode=TRUE)`, the Shiny process will send a message to the client in response to a changed input, even if no outputs have changed. This helps to streamline testing using the shinytest package. ([#1747](https://github.com/rstudio/shiny/pull/1747))
+
+* Addressed [#1738](https://github.com/rstudio/shiny/issues/1738): The `updateTextInput` and `updateTextAreaInput` functions can now update the placeholder. ([#1742](https://github.com/rstudio/shiny/pull/1742))
+
+* Converted examples to single file apps, and made updates and enhancements to comments in the example app scripts. ([#1685](https://github.com/rstudio/shiny/pull/1685))
+
+* Added new `snapshotPreprocessInput()` and `snapshotPreprocessOutput()` functions, which is used for preprocessing and input and output values before taking a test snapshot. ([#1760](https://github.com/rstudio/shiny/pull/1760), [#1789](https://github.com/rstudio/shiny/pull/1789))
+
+* The HTML generated by `renderTable()` no longer includes comments with the R version, xtable version, and timestamp. ([#1771](https://github.com/rstudio/shiny/pull/1771))
+
+* Added a function `isRunning` to test whether a Shiny app is currently running. ([#1785](https://github.com/rstudio/shiny/pull/1785))
+
+* Added a function `setSerializer`, which allows authors to specify a function for serializing the value of a custom input. ([#1791](https://github.com/rstudio/shiny/pull/1791))
+
+### Bug fixes
+
+* Fixed [#1546](https://github.com/rstudio/shiny/issues/1546): make it possible (without any hacks) to write arbitrary data into a module's `session$userData` (which is exactly the same environment as the parent's `session$userData`). To be clear, it allows something like `session$userData$x <- TRUE`, but not something like `session$userData <- TRUE` (that is not allowed in any context, whether you're in the main app, or in a module) ([#1732](https://github.com/rstudio/shiny/pull/1732)).
+
+* Fixed [#1701](https://github.com/rstudio/shiny/issues/1701): There was a partial argument match in the `generateOptions` function. ([#1702](https://github.com/rstudio/shiny/pull/1702))
+
+* Fixed [#1710](https://github.com/rstudio/shiny/issues/1710): `ReactiveVal` objects did not have separate dependents. ([#1712](https://github.com/rstudio/shiny/pull/1712))
+
+* Fixed [#1438](https://github.com/rstudio/shiny/issues/1438): `unbindAll()` should not be called when inserting content with `insertUI()`. A previous fix ([#1449](https://github.com/rstudio/shiny/pull/1449)) did not work correctly. ([#1736](https://github.com/rstudio/shiny/pull/1736))
+
+* Fixed [#1755](https://github.com/rstudio/shiny/issues/1755): dynamic htmlwidgets sent the path of the package on the server to the client. ([#1756](https://github.com/rstudio/shiny/pull/1756))
+
+* Fixed [#1763](https://github.com/rstudio/shiny/issues/1763): Shiny's private random stream leaked out into the main random stream. ([#1768](https://github.com/rstudio/shiny/pull/1768))
+
+* Fixed [#1680](https://github.com/rstudio/shiny/issues/1680): `options(warn=2)` was not respected when running an app. ([#1790](https://github.com/rstudio/shiny/pull/1790))
+
+* Fixed [#1772](https://github.com/rstudio/shiny/issues/1772): ensure that `runApp()` respects the `shinyApp(onStart = function())` argument. ([#1770](https://github.com/rstudio/shiny/pull/1770))
+
+* Fixed [#1474](https://github.com/rstudio/shiny/issues/1474): A `browser()` call in an observer could cause an error in the RStudio IDE on Windows. ([#1802](https://github.com/rstudio/shiny/pull/1802))
+
+
+shiny 1.0.3
+================
+
+This is a hotfix release of Shiny. With previous versions of Shiny, when running an application on the newly-released version of R, 3.4.0, it would print a message: `Warning in body(fun) : argument is not a function`. This has no effect on the application, but because the message could be alarming to users, we are releasing a new version of Shiny that fixes this issue.
+
+## Full changelog
+
+### Bug fixes
+
+* Fixed [#1672](https://github.com/rstudio/shiny/issues/1672): When an error occurred while uploading a file, the progress bar did not change colors. ([#1673](https://github.com/rstudio/shiny/pull/1673))
+
+* Fixed [#1676](https://github.com/rstudio/shiny/issues/1676): On R 3.4.0, running a Shiny application gave a warning: `Warning in body(fun) : argument is not a function`. ([#1677](https://github.com/rstudio/shiny/pull/1677))
+
+
+shiny 1.0.2
+================
+
+This is a hotfix release of Shiny. The primary reason for this release is because the web host for MathJax JavaScript library is scheduled to be shut down in the next few weeks. After it is shut down, Shiny applications that use MathJax will no longer be able to load the MathJax library if they are run with Shiny 1.0.1 and below. (If you don't know whether your application uses MathJax, it probably does not.) For more information about why the MathJax CDN is shutting down, see https://ww [...]
+
+## Full changelog
+
+### Minor new features and improvements
+
+* Added a `shiny:sessioninitialized` Javascript event, which is fired at the end of the initialize method of the Session object. This allows us to listen for this event when we want to get the value of things like `Shiny.user`. ([#1568](https://github.com/rstudio/shiny/pull/1568))
+
+* Fixed [#1649](https://github.com/rstudio/shiny/issues/1649): allow the `choices` argument in `checkboxGroupInput()` to be `NULL` (or `c()`) to keep backward compatibility with Shiny < 1.0.1. This will result in the same thing as providing `choices = character(0)`. ([#1652](https://github.com/rstudio/shiny/pull/1652))
+
+* The official URL for accessing MathJax libraries over CDN has been deprecated and will be removed soon. We have switched to a new rstudio.com URL that we will support going forward. ([#1664](https://github.com/rstudio/shiny/pull/1664))
+
+### Bug fixes
+
+* Fixed [#1653](https://github.com/rstudio/shiny/issues/1653): wrong code example in documentation. ([#1658](https://github.com/rstudio/shiny/pull/1658))
+
+
+shiny 1.0.1
+================
+
+This is a maintenance release of Shiny, mostly aimed at fixing bugs and introducing minor features. The most notable additions in this version of Shiny are the introduction of the `reactiveVal()` function (it's like `reactiveValues()`, but it only stores a single value), and that the choices of `radioButtons()` and `checkboxGroupInput()` can now contain HTML content instead of just plain text.
+
+## Full changelog
+
+### Breaking changes
+
+* The functions `radioButtons()`, `checkboxGroupInput()` and `selectInput()` (and the corresponding `updateXXX()` functions) no longer accept a `selected` argument whose value is the name of a choice, instead of the value of the choice. This feature had been deprecated since Shiny 0.10 (it printed a warning message, but still tried to match the name to the right choice) and it's now completely unsupported.
+
+### New features
+
+* Added `reactiveVal` function, for storing a single value which can be (reactively) read and written. Similar to `reactiveValues`, except that `reactiveVal` just lets you store a single value instead of storing multiple values by name. ([#1614](https://github.com/rstudio/shiny/pull/1614))
+
+### Minor new features and improvements
+
+* Fixed [#1637](https://github.com/rstudio/shiny/issues/1637): Outputs stay faded on MS Edge. ([#1640](https://github.com/rstudio/shiny/pull/1640))
+
+* Addressed [#1348](https://github.com/rstudio/shiny/issues/1348) and [#1437](https://github.com/rstudio/shiny/issues/1437) by adding two new arguments to `radioButtons()` and `checkboxGroupInput()`: `choiceNames` (list or vector) and `choiceValues` (list or vector). These can be passed in as an alternative to `choices`, with the added benefit that the elements in `choiceNames` can be arbitrary UI (i.e. anything created by `HTML()` and the `tags()` functions, like icons and images). Whil [...]
+
+* Updated `tools/README.md` with more detailed instructions. ([#1616](https://github.com/rstudio/shiny/pull/1616))
+
+* Fixed [#1565](https://github.com/rstudio/shiny/issues/1565), which meant that resources with spaces in their names return HTTP 404. ([#1566](https://github.com/rstudio/shiny/pull/1566))
+
+* Exported `session$user` (if it exists) to the client-side; it's accessible in the Shiny object: `Shiny.user`. ([#1563](https://github.com/rstudio/shiny/pull/1563))
+
+* Added support for HTML5's `pushState` which allows for pseudo-navigation
+in shiny apps. For more info, see the documentation (`?updateQueryString` and `?getQueryString`). ([#1447](https://github.com/rstudio/shiny/pull/1447))
+
+* Fixed [#1121](https://github.com/rstudio/shiny/issues/1121): plot interactions with ggplot2 now support `coord_fixed()`. ([#1525](https://github.com/rstudio/shiny/pull/1525))
+
+* Added `snapshotExclude` function, which marks an output so that it is not recorded in a test snapshot. ([#1559](https://github.com/rstudio/shiny/pull/1559))
+
+* Added `shiny:filedownload` JavaScript event, which is triggered when a `downloadButton` or `downloadLink` is clicked. Also, the values of `downloadHandler`s are not recorded in test snapshots, because the values change every time the application is run. ([#1559](https://github.com/rstudio/shiny/pull/1559))
+
+* Added support for plot interactions with ggplot2 > 2.2.1. ([#1578](https://github.com/rstudio/shiny/pull/1578))
+
+* Fixed [#1577](https://github.com/rstudio/shiny/issues/1577): Improved `escapeHTML` (util.js) in terms of the order dependency of replacing, XSS risk attack and performance. ([#1579](https://github.com/rstudio/shiny/pull/1579))
+
+* The `shiny:inputchanged` JavaScript event now includes two new fields, `binding` and `el`, which contain the input binding and DOM element, respectively. Additionally, `Shiny.onInputChange()` now accepts an optional argument, `opts`, which can contain the same fields. ([#1596](https://github.com/rstudio/shiny/pull/1596))
+
+* The `NS()` function now returns a vectorized function. ([#1613](https://github.com/rstudio/shiny/pull/1613))
+
+* Fixed [#1617](https://github.com/rstudio/shiny/issues/1617): `fileInput` can have customized text for the button and the placeholder. ([#1619](https://github.com/rstudio/shiny/pull/1619))
+
+### Bug fixes
+
+* Fixed [#1511](https://github.com/rstudio/shiny/issues/1511): `fileInput`s did not trigger the `shiny:inputchanged` event on the client. Also removed `shiny:fileuploaded` JavaScript event, because it is no longer needed after this fix. ([#1541](https://github.com/rstudio/shiny/pull/1541), [#1570](https://github.com/rstudio/shiny/pull/1570))
+
+* Fixed [#1472](https://github.com/rstudio/shiny/issues/1472): With a Progress object, calling `set(value=NULL)` made the progress bar go to 100%. Now it does not change the value of the progress bar. The documentation also incorrectly said that setting the `value` to `NULL` would hide the progress bar. ([#1547](https://github.com/rstudio/shiny/pull/1547))
+
+* Fixed [#162](https://github.com/rstudio/shiny/issues/162): When a dynamically-generated input changed to a different `inputType`, it might be incorrectly deduplicated.  ([#1594](https://github.com/rstudio/shiny/pull/1594))
+
+* Removed redundant call to `inputs.setInput`. ([#1595](https://github.com/rstudio/shiny/pull/1595))
+
+* Fixed bug where `dateRangeInput` did not respect `weekstart` argument. ([#1592](https://github.com/rstudio/shiny/pull/1592))
+
+* Fixed [#1598](https://github.com/rstudio/shiny/issues/1598): `setBookmarkExclude()` did not work properly inside of modules. ([#1599](https://github.com/rstudio/shiny/pull/1599))
+
+* Fixed [#1605](https://github.com/rstudio/shiny/issues/1605): sliders did not move when clicked on the bar area. ([#1610](https://github.com/rstudio/shiny/pull/1610))
+
+* Fixed [#1621](https://github.com/rstudio/shiny/issues/1621): if a `reactiveTimer`'s session was closed before the first time that the `reactiveTimer` fired, then the `reactiveTimer` would not get cleared and would keep firing indefinitely. ([#1623](https://github.com/rstudio/shiny/pull/1623))
+
+* Fixed [#1634](https://github.com/rstudio/shiny/issues/1634): If brushing on a plot causes the plot to redraw, then the redraw could in turn trigger the plot to redraw again and again. This was due to spurious changes in values of floating point numbers. ([#1641](https://github.com/rstudio/shiny/pull/1641))
+
+### Library updates
+
+* Closed [#1500](https://github.com/rstudio/shiny/issues/1500): Updated ion.rangeSlider to 2.1.6. ([#1540](https://github.com/rstudio/shiny/pull/1540))
+
+
 shiny 1.0.0
 ===========
 
@@ -7,7 +188,7 @@ Here are some highlights from this release. For more details, see the full chang
 
 ## Support for testing Shiny applications
 
-Shiny now supports automated testing of applications, with the [shinytest](https://github.com/MangoTheCat/shinytest) package. Shinytest has not yet been released on CRAN, but will be soon. ([#18](https://github.com/rstudio/shiny/issues/18), [#1464](https://github.com/rstudio/shiny/pull/1464))
+Shiny now supports automated testing of applications, with the [shinytest](https://github.com/rstudio/shinytest) package. Shinytest has not yet been released on CRAN, but will be soon. ([#18](https://github.com/rstudio/shiny/issues/18), [#1464](https://github.com/rstudio/shiny/pull/1464))
 
 ## Debounce/throttle reactives
 
diff --git a/R/app.R b/R/app.R
index 4857e71..5504a18 100644
--- a/R/app.R
+++ b/R/app.R
@@ -71,7 +71,7 @@
 #' }
 #' @export
 shinyApp <- function(ui=NULL, server=NULL, onStart=NULL, options=list(),
-                     uiPattern="/", enableBookmarking = NULL) {
+                     uiPattern="/", enableBookmarking=NULL) {
   if (is.null(server)) {
     stop("`server` missing from shinyApp")
   }
@@ -212,7 +212,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
     if (file.exists(file.path.ci(appDir, "global.R")))
       sourceUTF8(file.path.ci(appDir, "global.R"))
   }
-  onEnd <- function() {
+  onStop <- function() {
     setwd(oldwd)
     monitorHandle()
     monitorHandle <<- NULL
@@ -223,7 +223,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
       httpHandler = joinHandlers(c(uiHandler, wwwDir, fallbackWWWDir)),
       serverFuncSource = serverFuncSource,
       onStart = onStart,
-      onEnd = onEnd,
+      onStop = onStop,
       options = options
     ),
     class = "shiny.appobj"
@@ -317,8 +317,9 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
     oldwd <<- getwd()
     setwd(appDir)
     monitorHandle <<- initAutoReloadMonitor(appDir)
+    if (!is.null(appObj()$onStart)) appObj()$onStart()
   }
-  onEnd <- function() {
+  onStop <- function() {
     setwd(oldwd)
     monitorHandle()
     monitorHandle <<- NULL
@@ -329,7 +330,7 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
       httpHandler = joinHandlers(c(dynHttpHandler, wwwDir, fallbackWWWDir)),
       serverFuncSource = dynServerFuncSource,
       onStart = onStart,
-      onEnd = onEnd,
+      onStop = onStop,
       options = options
     ),
     class = "shiny.appobj"
diff --git a/R/bookmark-state.R b/R/bookmark-state.R
index cc3c46e..18d04db 100644
--- a/R/bookmark-state.R
+++ b/R/bookmark-state.R
@@ -349,7 +349,7 @@ RestoreContext <- R6Class("RestoreContext",
         mapply(names(vals), vals, SIMPLIFY = FALSE,
           FUN = function(name, value) {
             tryCatch(
-              jsonlite::fromJSON(value),
+              safeFromJSON(value),
               error = function(e) {
                 stop("Failed to parse URL parameter \"", name, "\"")
               }
@@ -493,12 +493,90 @@ restoreInput <- function(id, default) {
 #' It typically is called from an observer. Note that this will not work in
 #' Internet Explorer 9 and below.
 #'
+#' For \code{mode = "push"}, only three updates are currently allowed:
+#' \enumerate{
+#'   \item the query string (format: \code{?param1=val1&param2=val2})
+#'   \item the hash (format: \code{#hash})
+#'   \item both the query string and the hash
+#'     (format: \code{?param1=val1&param2=val2#hash})
+#' }
+#'
+#' In other words, if \code{mode = "push"}, the \code{queryString} must start
+#' with either \code{?} or with \code{#}.
+#'
+#' A technical curiosity: under the hood, this function is calling the HTML5
+#' history API (which is where the names for the \code{mode} argument come from).
+#' When \code{mode = "replace"}, the function called is
+#' \code{window.history.replaceState(null, null, queryString)}.
+#' When \code{mode = "push"}, the function called is
+#' \code{window.history.pushState(null, null, queryString)}.
+#'
 #' @param queryString The new query string to show in the location bar.
+#' @param mode When the query string is updated, should the the current history
+#'   entry be replaced (default), or should a new history entry be pushed onto
+#'   the history stack? The former should only be used in a live bookmarking
+#'   context. The latter is useful if you want to navigate between states using
+#'   the browser's back and forward buttons. See Examples.
 #' @param session A Shiny session object.
-#' @seealso \code{\link{enableBookmarking}} for examples.
+#' @seealso \code{\link{enableBookmarking}}, \code{\link{getQueryString}}
+#' @examples
+#' ## Only run these examples in interactive sessions
+#' if (interactive()) {
+#'
+#'   ## App 1: Doing "live" bookmarking
+#'   ## Update the browser's location bar every time an input changes.
+#'   ## This should not be used with enableBookmarking("server"),
+#'   ## because that would create a new saved state on disk every time
+#'   ## the user changes an input.
+#'   enableBookmarking("url")
+#'   shinyApp(
+#'     ui = function(req) {
+#'       fluidPage(
+#'         textInput("txt", "Text"),
+#'         checkboxInput("chk", "Checkbox")
+#'       )
+#'     },
+#'     server = function(input, output, session) {
+#'       observe({
+#'         # Trigger this observer every time an input changes
+#'         reactiveValuesToList(input)
+#'         session$doBookmark()
+#'       })
+#'       onBookmarked(function(url) {
+#'         updateQueryString(url)
+#'       })
+#'     }
+#'   )
+#'
+#'   ## App 2: Printing the value of the query string
+#'   ## (Use the back and forward buttons to see how the browser
+#'   ## keeps a record of each state)
+#'   shinyApp(
+#'     ui = fluidPage(
+#'       textInput("txt", "Enter new query string"),
+#'       helpText("Format: ?param1=val1&param2=val2"),
+#'       actionButton("go", "Update"),
+#'       hr(),
+#'       verbatimTextOutput("query")
+#'     ),
+#'     server = function(input, output, session) {
+#'       observeEvent(input$go, {
+#'         updateQueryString(input$txt, mode = "push")
+#'       })
+#'       output$query <- renderText({
+#'         query <- getQueryString()
+#'         queryText <- paste(names(query), query,
+#'                        sep = "=", collapse=", ")
+#'         paste("Your query string is:\n", queryText)
+#'       })
+#'     }
+#'   )
+#' }
 #' @export
-updateQueryString <- function(queryString, session = getDefaultReactiveDomain()) {
-  session$updateQueryString(queryString)
+updateQueryString <- function(queryString, mode = c("replace", "push"),
+                              session = getDefaultReactiveDomain()) {
+  mode <- match.arg(mode)
+  session$updateQueryString(queryString, mode)
 }
 
 #' Create a button for bookmarking/sharing
diff --git a/R/bootstrap.R b/R/bootstrap.R
index 1cf1265..7779e71 100644
--- a/R/bootstrap.R
+++ b/R/bootstrap.R
@@ -285,7 +285,8 @@ pageWithSidebar <- function(headerPanel,
 #'   example below).
 #'
 #' @seealso \code{\link{tabPanel}}, \code{\link{tabsetPanel}},
-#'   \code{\link{updateNavbarPage}}
+#'   \code{\link{updateNavbarPage}}, \code{\link{insertTab}},
+#'   \code{\link{showTab}}
 #'
 #' @examples
 #' navbarPage("App Title",
@@ -393,10 +394,15 @@ navbarPage <- function(title,
   )
 }
 
+#' @param menuName A name that identifies this \code{navbarMenu}. This
+#'   is needed if you want to insert/remove or show/hide an entire
+#'   \code{navbarMenu}.
+#'
 #' @rdname navbarPage
 #' @export
-navbarMenu <- function(title, ..., icon = NULL) {
+navbarMenu <- function(title, ..., menuName = title, icon = NULL) {
   structure(list(title = title,
+                 menuName = menuName,
                  tabs = list(...),
                  iconClass = iconClass(icon)),
             class = "shiny.navbarmenu")
@@ -502,6 +508,8 @@ mainPanel <- function(..., width = 8) {
 #'
 #' @param condition A JavaScript expression that will be evaluated repeatedly to
 #'   determine whether the panel should be displayed.
+#' @param ns The \code{\link[=NS]{namespace}} object of the current module, if
+#'   any.
 #' @param ... Elements to include in the panel.
 #'
 #' @note You are not recommended to use special JavaScript characters such as a
@@ -510,32 +518,55 @@ mainPanel <- function(..., width = 8) {
 #'   \code{input["foo.bar"]} instead of \code{input.foo.bar} to read the input
 #'   value.
 #' @examples
-#' sidebarPanel(
-#'    selectInput(
-#'       "plotType", "Plot Type",
-#'       c(Scatter = "scatter",
-#'         Histogram = "hist")),
-#'
-#'    # Only show this panel if the plot type is a histogram
-#'    conditionalPanel(
-#'       condition = "input.plotType == 'hist'",
-#'       selectInput(
-#'          "breaks", "Breaks",
-#'          c("Sturges",
-#'            "Scott",
-#'            "Freedman-Diaconis",
-#'            "[Custom]" = "custom")),
-#'
-#'       # Only show this panel if Custom is selected
+#' ## Only run this example in interactive R sessions
+#' if (interactive()) {
+#'   ui <- fluidPage(
+#'     sidebarPanel(
+#'       selectInput("plotType", "Plot Type",
+#'         c(Scatter = "scatter", Histogram = "hist")
+#'       ),
+#'       # Only show this panel if the plot type is a histogram
 #'       conditionalPanel(
-#'          condition = "input.breaks == 'custom'",
-#'          sliderInput("breakCount", "Break Count", min=1, max=1000, value=10)
+#'         condition = "input.plotType == 'hist'",
+#'         selectInput(
+#'           "breaks", "Breaks",
+#'           c("Sturges", "Scott", "Freedman-Diaconis", "[Custom]" = "custom")
+#'         ),
+#'         # Only show this panel if Custom is selected
+#'         conditionalPanel(
+#'           condition = "input.breaks == 'custom'",
+#'           sliderInput("breakCount", "Break Count", min = 1, max = 50, value = 10)
+#'         )
 #'       )
-#'    )
-#' )
+#'     ),
+#'     mainPanel(
+#'       plotOutput("plot")
+#'     )
+#'   )
+#'
+#'   server <- function(input, output) {
+#'     x <- rnorm(100)
+#'     y <- rnorm(100)
+#'
+#'     output$plot <- renderPlot({
+#'       if (input$plotType == "scatter") {
+#'         plot(x, y)
+#'       } else {
+#'         breaks <- input$breaks
+#'         if (breaks == "custom") {
+#'           breaks <- input$breakCount
+#'         }
+#'
+#'         hist(x, breaks = breaks)
+#'       }
+#'     })
+#'   }
+#'
+#'   shinyApp(ui, server)
+#' }
 #' @export
-conditionalPanel <- function(condition, ...) {
-  div('data-display-if'=condition, ...)
+conditionalPanel <- function(condition, ..., ns = NS(NULL)) {
+  div(`data-display-if`=condition, `data-ns-prefix`=ns(""), ...)
 }
 
 #' Create a help text element
@@ -609,7 +640,8 @@ tabPanel <- function(title, ..., value = title, icon = NULL) {
 #'   Bootstrap 3.
 #' @return A tabset that can be passed to \code{\link{mainPanel}}
 #'
-#' @seealso \code{\link{tabPanel}}, \code{\link{updateTabsetPanel}}
+#' @seealso \code{\link{tabPanel}}, \code{\link{updateTabsetPanel}},
+#'   \code{\link{insertTab}}, \code{\link{showTab}}
 #'
 #' @examples
 #' # Show a tabset that includes a plot, summary, and
@@ -676,7 +708,9 @@ tabsetPanel <- function(...,
 #'   supported. This is because version 0.11 switched to Bootstrap 3, which
 #'   doesn't support separators.
 #'
-#' @seealso \code{\link{tabPanel}}, \code{\link{updateNavlistPanel}}
+#' @seealso \code{\link{tabPanel}}, \code{\link{updateNavlistPanel}},
+#'    \code{\link{insertTab}}, \code{\link{showTab}}
+#'
 #' @examples
 #' fluidPage(
 #'
@@ -726,189 +760,158 @@ navlistPanel <- function(...,
     fixedRow(columns)
 }
 
+# Helpers to build tabsetPanels (& Co.) and their elements
+markTabAsSelected <- function(x) {
+  attr(x, "selected") <- TRUE
+  x
+}
 
-buildTabset <- function(tabs, ulClass, textFilter = NULL,
-                        id = NULL, selected = NULL) {
-
-  # This function proceeds in two phases. First, it scans over all the items
-  # to find and mark which tab should start selected. Then it actually builds
-  # the tab nav and tab content lists.
-
-  # Mark an item as selected
-  markSelected <- function(x) {
-    attr(x, "selected") <- TRUE
-    x
-  }
-
-  # Returns TRUE if an item is selected
-  isSelected <- function(x) {
-    isTRUE(attr(x, "selected", exact = TRUE))
-  }
-
-  # Returns TRUE if a list of tab items contains a selected tab, FALSE
-  # otherwise.
-  containsSelected <- function(tabs) {
-    any(vapply(tabs, isSelected, logical(1)))
-  }
-
-  # Take a pass over all tabs, and mark the selected tab.
-  foundSelectedItem <- FALSE
-  findAndMarkSelected <- function(tabs, selected) {
-    lapply(tabs, function(divTag) {
-      if (foundSelectedItem) {
-        # If we already found the selected tab, no need to keep looking
-
-      } else if (is.character(divTag)) {
-        # Strings don't represent selectable items
+isTabSelected <- function(x) {
+  isTRUE(attr(x, "selected", exact = TRUE))
+}
 
-      } else if (inherits(divTag, "shiny.navbarmenu")) {
-        # Navbar menu
-        divTag$tabs <- findAndMarkSelected(divTag$tabs, selected)
+containsSelectedTab <- function(tabs) {
+  any(vapply(tabs, isTabSelected, logical(1)))
+}
 
+findAndMarkSelectedTab <- function(tabs, selected, foundSelected) {
+  tabs <- lapply(tabs, function(div) {
+    if (foundSelected || is.character(div)) {
+      # Strings are not selectable items
+
+    } else if (inherits(div, "shiny.navbarmenu")) {
+      # Recur for navbarMenus
+      res <- findAndMarkSelectedTab(div$tabs, selected, foundSelected)
+      div$tabs <- res$tabs
+      foundSelected <<- res$foundSelected
+
+    } else {
+      # Base case: regular tab item. If the `selected` argument is
+      # provided, check for a match in the existing tabs; else,
+      # mark first available item as selected
+      if (is.null(selected)) {
+        foundSelected <<- TRUE
+        div <- markTabAsSelected(div)
       } else {
-        # Regular tab item
-        if (is.null(selected)) {
-          # If selected tab isn't specified, mark first available item
-          # as selected.
-          foundSelectedItem <<- TRUE
-          divTag <- markSelected(divTag)
-
-        } else {
-          # If selected tab is specified, check for a match
-          tabValue <- divTag$attribs$`data-value` %OR% divTag$attribs$title
-          if (identical(selected, tabValue)) {
-            foundSelectedItem <<- TRUE
-            divTag <- markSelected(divTag)
-          }
+        tabValue <- div$attribs$`data-value` %OR% div$attribs$title
+        if (identical(selected, tabValue)) {
+          foundSelected <<- TRUE
+          div <- markTabAsSelected(div)
         }
       }
-
-      return(divTag)
-    })
-  }
-
-
-  # Append an optional icon to an aTag
-  appendIcon <- function(aTag, iconClass) {
-    if (!is.null(iconClass)) {
-      # for font-awesome we specify fixed-width
-      if (grepl("fa-", iconClass, fixed = TRUE))
-        iconClass <- paste(iconClass, "fa-fw")
-      aTag <- tagAppendChild(aTag, icon(name = NULL, class = iconClass))
     }
-    aTag
-  }
+    return(div)
+  })
+  return(list(tabs = tabs, foundSelected = foundSelected))
+}
 
-  # Build the tabset
-  build <- function(tabs, ulClass, textFilter = NULL, id = NULL) {
-    # add tab input sentinel class if we have an id
-    if (!is.null(id))
-      ulClass <- paste(ulClass, "shiny-tab-input")
-
-    if (anyNamed(tabs)) {
-      nms <- names(tabs)
-      nms <- nms[nzchar(nms)]
-      stop("Tabs should all be unnamed arguments, but some are named: ",
-           paste(nms, collapse = ", "))
+# Returns the icon object (or NULL if none), provided either a
+# tabPanel, OR the icon class
+getIcon <- function(tab = NULL, iconClass = NULL) {
+  if (!is.null(tab)) iconClass <- tab$attribs$`data-icon-class`
+  if (!is.null(iconClass)) {
+    if (grepl("fa-", iconClass, fixed = TRUE)) {
+      # for font-awesome we specify fixed-width
+      iconClass <- paste(iconClass, "fa-fw")
     }
+    icon(name = NULL, class = iconClass)
+  } else NULL
+}
 
-    tabNavList <- tags$ul(class = ulClass, id = id)
-    tabContent <- tags$div(class = "tab-content")
-    tabsetId <- p_randomInt(1000, 10000)
-    tabId <- 1
-
-    buildItem <- function(divTag) {
-      # check for text; pass it to the textFilter or skip it if there is none
-      if (is.character(divTag)) {
-        if (!is.null(textFilter)) {
-          tabNavList <<- tagAppendChild(tabNavList, textFilter(divTag))
-        }
-
-      } else if (inherits(divTag, "shiny.navbarmenu")) {
-
-        # create the a tag
-        aTag <- tags$a(href="#",
-                       class="dropdown-toggle",
-                       `data-toggle`="dropdown")
-
-        # add optional icon
-        aTag <- appendIcon(aTag, divTag$iconClass)
-
-        # add the title and caret
-        aTag <- tagAppendChild(aTag, divTag$title)
-        aTag <- tagAppendChild(aTag, tags$b(class="caret"))
-
-        # build the dropdown list element
-        liTag <- tags$li(class = "dropdown", aTag)
-
-        # text filter for separators
-        textFilter <- function(text) {
-          if (grepl("^\\-+$", text))
-            tags$li(class="divider")
-          else
-            tags$li(class="dropdown-header", text)
-        }
-
-        # build the child tabset
-        tabset <- build(divTag$tabs, "dropdown-menu", textFilter)
-        liTag <- tagAppendChild(liTag, tabset$navList)
-
-        # If this navbar menu contains a selected item, mark it as active
-        if (containsSelected(divTag$tabs)) {
-          liTag$attribs$class <- paste(liTag$attribs$class, "active")
-        }
+# Text filter for navbarMenu's (plain text) separators
+navbarMenuTextFilter <- function(text) {
+  if (grepl("^\\-+$", text)) tags$li(class = "divider")
+  else tags$li(class = "dropdown-header", text)
+}
 
-        tabNavList <<- tagAppendChild(tabNavList, liTag)
-        # don't add a standard tab content div, rather add the list of tab
-        # content divs that are contained within the tabset
-        tabContent <<- tagAppendChildren(tabContent,
-                                        list = tabset$content$children)
+# This function is called internally by navbarPage, tabsetPanel
+# and navlistPanel
+buildTabset <- function(tabs, ulClass, textFilter = NULL, id = NULL,
+                        selected = NULL, foundSelected = FALSE) {
 
-      } else {
-        # Standard navbar item
-        # compute id and assign it to the div
-        thisId <- paste("tab", tabsetId, tabId, sep="-")
-        divTag$attribs$id <- thisId
-        tabId <<- tabId + 1
+  res <- findAndMarkSelectedTab(tabs, selected, foundSelected)
+  tabs <- res$tabs
+  foundSelected <- res$foundSelected
 
-        tabValue <- divTag$attribs$`data-value`
+  # add input class if we have an id
+  if (!is.null(id)) ulClass <- paste(ulClass, "shiny-tab-input")
 
-        # create the a tag
-        aTag <- tags$a(href=paste("#", thisId, sep=""),
-                       `data-toggle` = "tab",
-                       `data-value` = tabValue)
+  if (anyNamed(tabs)) {
+    nms <- names(tabs)
+    nms <- nms[nzchar(nms)]
+    stop("Tabs should all be unnamed arguments, but some are named: ",
+      paste(nms, collapse = ", "))
+  }
 
-        # append optional icon
-        aTag <- appendIcon(aTag, divTag$attribs$`data-icon-class`)
+  tabsetId <- p_randomInt(1000, 10000)
+  tabs <- lapply(seq_len(length(tabs)), buildTabItem,
+            tabsetId = tabsetId, foundSelected = foundSelected,
+            tabs = tabs, textFilter = textFilter)
 
-        # add the title
-        aTag <- tagAppendChild(aTag, divTag$attribs$title)
+  tabNavList <- tags$ul(class = ulClass, id = id,
+                  `data-tabsetid` = tabsetId, lapply(tabs, "[[", 1))
 
-        # create the li tag
-        liTag <- tags$li(aTag)
+  tabContent <- tags$div(class = "tab-content",
+                  `data-tabsetid` = tabsetId, lapply(tabs, "[[", 2))
 
-        # If selected, set appropriate classes on li tag and div tag.
-        if (isSelected(divTag)) {
-          liTag$attribs$class <- "active"
-          divTag$attribs$class <- "tab-pane active"
-        }
+  list(navList = tabNavList, content = tabContent)
+}
 
-        divTag$attribs$title <- NULL
+# Builds tabPanel/navbarMenu items (this function used to be
+# declared inside the buildTabset() function and it's been
+# refactored for clarity and reusability). Called internally
+# by buildTabset.
+buildTabItem <- function(index, tabsetId, foundSelected, tabs = NULL,
+                         divTag = NULL, textFilter = NULL) {
+
+  divTag <- if (!is.null(divTag)) divTag else tabs[[index]]
+
+  if (is.character(divTag) && !is.null(textFilter)) {
+    # text item: pass it to the textFilter if it exists
+    liTag <- textFilter(divTag)
+    divTag <- NULL
+
+  } else if (inherits(divTag, "shiny.navbarmenu")) {
+    # navbarMenu item: build the child tabset
+    tabset <- buildTabset(divTag$tabs, "dropdown-menu",
+      navbarMenuTextFilter, foundSelected = foundSelected)
+
+    # if this navbarMenu contains a selected item, mark it active
+    containsSelected <- containsSelectedTab(divTag$tabs)
+    liTag <- tags$li(
+      class = paste0("dropdown", if (containsSelected) " active"),
+      tags$a(href = "#",
+        class = "dropdown-toggle", `data-toggle` = "dropdown",
+        `data-value` = divTag$menuName,
+        divTag$title, tags$b(class = "caret"),
+        getIcon(iconClass = divTag$iconClass)
+      ),
+      tabset$navList   # inner tabPanels items
+    )
+    # list of tab content divs from the child tabset
+    divTag <- tabset$content$children
 
-        # append the elements to our lists
-        tabNavList <<- tagAppendChild(tabNavList, liTag)
-        tabContent <<- tagAppendChild(tabContent, divTag)
-      }
+  } else {
+    # tabPanel item: create the tab's liTag and divTag
+    tabId <- paste("tab", tabsetId, index, sep = "-")
+    liTag <- tags$li(
+               tags$a(
+                 href = paste("#", tabId, sep = ""),
+                 `data-toggle` = "tab",
+                 `data-value` = divTag$attribs$`data-value`,
+                 divTag$attribs$title,
+                 getIcon(iconClass = divTag$attribs$`data-icon-class`)
+               )
+    )
+    # if this tabPanel is selected item, mark it active
+    if (isTabSelected(divTag)) {
+      liTag$attribs$class <- "active"
+      divTag$attribs$class <- "tab-pane active"
     }
-
-    lapply(tabs, buildItem)
-    list(navList = tabNavList, content = tabContent)
+    divTag$attribs$id <- tabId
+    divTag$attribs$title <- NULL
   }
-
-
-  # Finally, actually invoke the functions to do the processing.
-  tabs <- findAndMarkSelected(tabs, selected)
-  build(tabs, ulClass, textFilter, id)
+  return(list(liTag = liTag, divTag = divTag))
 }
 
 
@@ -1453,7 +1456,7 @@ uiOutput <- htmlOutput
 #' }
 #'
 #' @aliases downloadLink
-#' @seealso downloadHandler
+#' @seealso \code{\link{downloadHandler}}
 #' @export
 downloadButton <- function(outputId,
                            label="Download",
diff --git a/R/fileupload.R b/R/fileupload.R
index 8b30c5e..e9dab65 100644
--- a/R/fileupload.R
+++ b/R/fileupload.R
@@ -20,6 +20,18 @@
 # form upload, i.e. traditional HTTP POST-based file upload) doesn't work with
 # the websockets package's HTTP server at the moment.
 
+# @description Returns a file's extension, with a leading dot, if one can be
+#   found. A valid extension contains only alphanumeric characters. If there is
+#   no extension, or if it contains non-alphanumeric characters, an empty
+#   string is returned.
+# @param x character vector giving file paths.
+# @return The extension of \code{x}, with a leading dot, if one was found.
+#   Otherwise, an empty character vector.
+maybeGetExtension <- function(x) {
+  ext <- tools::file_ext(x)
+  ifelse(ext == "", ext, paste0(".", ext))
+}
+
 FileUploadOperation <- R6Class(
   'FileUploadOperation',
   portable = FALSE,
@@ -52,8 +64,9 @@ FileUploadOperation <- R6Class(
       .currentFileInfo <<- file
       .pendingFileInfos <<- tail(.pendingFileInfos, -1)
 
-      filename <- file.path(.dir, as.character(length(.files$name)))
-      row <- data.frame(name=file$name, size=file$size, type=file$type,
+      fileBasename <- basename(.currentFileInfo$name)
+      filename <- file.path(.dir, paste0(as.character(length(.files$name)), maybeGetExtension(fileBasename)))
+      row <- data.frame(name=fileBasename, size=file$size, type=file$type,
                         datapath=filename, stringsAsFactors=FALSE)
 
       if (length(.files$name) == 0)
diff --git a/R/globals.R b/R/globals.R
index e17fd5f..9edfc8b 100644
--- a/R/globals.R
+++ b/R/globals.R
@@ -5,7 +5,7 @@
   # R's lazy-loading package scheme causes the private seed to be cached in the
   # package itself, making our PRNG completely deterministic. This line resets
   # the private seed during load.
-  withPrivateSeed(reinitializeSeed())
+  withPrivateSeed(set.seed(NULL))
 }
 
 .onAttach <- function(libname, pkgname) {
diff --git a/R/history.R b/R/history.R
new file mode 100644
index 0000000..1baed3c
--- /dev/null
+++ b/R/history.R
@@ -0,0 +1,95 @@
+
+#' @include reactive-domains.R
+NULL
+
+#' @include reactives.R
+NULL
+
+#' Get the query string / hash component from the URL
+#'
+#' Two user friendly wrappers for getting the query string and the hash
+#' component from the app's URL.
+#'
+#' These can be particularly useful if you want to display different content
+#' depending on the values in the query string / hash (e.g. instead of basing
+#' the conditional on an input or a calculated reactive, you can base it on the
+#' query string). However, note that, if you're changing the query string / hash
+#' programatically from within the server code, you must use
+#' \code{updateQueryString(_yourNewQueryString_, mode = "push")}. The default
+#' \code{mode} for \code{updateQueryString} is \code{"replace"}, which doesn't
+#' raise any events, so any observers or reactives that depend on it will
+#' \emph{not} get triggered. However, if you're changing the query string / hash
+#' directly by typing directly in the browser and hitting enter, you don't have
+#' to worry about this.
+#'
+#' @param session	A Shiny session object.
+#'
+#' @return For \code{getQueryString}, a named list. For example, the query
+#'   string \code{?param1=value1&param2=value2} becomes \code{list(param1 =
+#'   value1, param2 = value2)}. For \code{getUrlHash}, a character vector with
+#'   the hash (including the leading \code{#} symbol).
+#'
+#' @seealso \code{\link{updateQueryString}}
+#'
+#' @examples
+#' ## Only run this example in interactive R sessions
+#' if (interactive()) {
+#'
+#'   ## App 1: getQueryString
+#'   ## Printing the value of the query string
+#'   ## (Use the back and forward buttons to see how the browser
+#'   ## keeps a record of each state)
+#'   shinyApp(
+#'     ui = fluidPage(
+#'       textInput("txt", "Enter new query string"),
+#'       helpText("Format: ?param1=val1&param2=val2"),
+#'       actionButton("go", "Update"),
+#'       hr(),
+#'       verbatimTextOutput("query")
+#'     ),
+#'     server = function(input, output, session) {
+#'       observeEvent(input$go, {
+#'         updateQueryString(input$txt, mode = "push")
+#'       })
+#'       output$query <- renderText({
+#'         query <- getQueryString()
+#'         queryText <- paste(names(query), query,
+#'                        sep = "=", collapse=", ")
+#'         paste("Your query string is:\n", queryText)
+#'       })
+#'     }
+#'   )
+#'
+#'   ## App 2: getUrlHash
+#'   ## Printing the value of the URL hash
+#'   ## (Use the back and forward buttons to see how the browser
+#'   ## keeps a record of each state)
+#'   shinyApp(
+#'     ui = fluidPage(
+#'       textInput("txt", "Enter new hash"),
+#'       helpText("Format: #hash"),
+#'       actionButton("go", "Update"),
+#'       hr(),
+#'       verbatimTextOutput("hash")
+#'     ),
+#'     server = function(input, output, session) {
+#'       observeEvent(input$go, {
+#'         updateQueryString(input$txt, mode = "push")
+#'       })
+#'       output$hash <- renderText({
+#'         hash <- getUrlHash()
+#'         paste("Your hash is:\n", hash)
+#'       })
+#'     }
+#'   )
+#' }
+#' @export
+getQueryString <- function(session = getDefaultReactiveDomain()) {
+  parseQueryString(session$clientData$url_search)
+}
+
+#' @rdname getQueryString
+#' @export
+getUrlHash <- function(session = getDefaultReactiveDomain()) {
+  session$clientData$url_hash
+}
diff --git a/R/html-deps.R b/R/html-deps.R
index 7dfab12..17796e4 100644
--- a/R/html-deps.R
+++ b/R/html-deps.R
@@ -6,13 +6,18 @@
 #' URL.
 #'
 #' @param dependency A single HTML dependency object, created using
-#'   \code{\link[htmltools]{htmlDependency}}. If the \code{src} value is named, then
-#'   \code{href} and/or \code{file} names must be present.
+#'   \code{\link[htmltools]{htmlDependency}}. If the \code{src} value is named,
+#'   then \code{href} and/or \code{file} names must be present.
+#' @param scrubFile If TRUE (the default), remove \code{src$file} for the
+#'   dependency. This prevents the local file path from being sent to the client
+#'   when dynamic web dependencies are used. If FALSE, don't remove
+#'   \code{src$file}. Setting it to FALSE should be needed only in very unusual
+#'   cases.
 #'
 #' @return A single HTML dependency object that has an \code{href}-named element
 #'   in its \code{src}.
 #' @export
-createWebDependency <- function(dependency) {
+createWebDependency <- function(dependency, scrubFile = TRUE) {
   if (is.null(dependency))
     return(NULL)
 
@@ -25,6 +30,10 @@ createWebDependency <- function(dependency) {
     dependency$src$href <- prefix
   }
 
+  # Don't leak local file path to client
+  if (scrubFile)
+    dependency$src$file <- NULL
+
   return(dependency)
 }
 
diff --git a/R/input-checkboxgroup.R b/R/input-checkboxgroup.R
index 2466472..eb8e58d 100644
--- a/R/input-checkboxgroup.R
+++ b/R/input-checkboxgroup.R
@@ -6,9 +6,22 @@
 #'
 #' @inheritParams textInput
 #' @param choices List of values to show checkboxes for. If elements of the list
-#'   are named then that name rather than the value is displayed to the user.
+#'   are named then that name rather than the value is displayed to the user. If
+#'   this argument is provided, then \code{choiceNames} and \code{choiceValues}
+#'   must not be provided, and vice-versa. The values should be strings; other
+#'   types (such as logicals and numbers) will be coerced to strings.
 #' @param selected The values that should be initially selected, if any.
 #' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
+#' @param choiceNames,choiceValues List of names and values, respectively,
+#'   that are displayed to the user in the app and correspond to the each
+#'   choice (for this reason, \code{choiceNames} and \code{choiceValues}
+#'   must have the same length). If either of these arguments is
+#'   provided, then the other \emph{must} be provided and \code{choices}
+#'   \emph{must not} be provided. The advantage of using both of these over
+#'   a named list for \code{choices} is that \code{choiceNames} allows any
+#'   type of UI object to be passed through (tag objects, icons, HTML code,
+#'   ...), instead of just simple text. See Examples.
+#'
 #' @return A list of HTML elements that can be added to a UI definition.
 #'
 #' @family input elements
@@ -26,26 +39,52 @@
 #'   tableOutput("data")
 #' )
 #'
-#' server <- function(input, output) {
+#' server <- function(input, output, session) {
 #'   output$data <- renderTable({
 #'     mtcars[, c("mpg", input$variable), drop = FALSE]
 #'   }, rownames = TRUE)
 #' }
 #'
 #' shinyApp(ui, server)
+#'
+#' ui <- fluidPage(
+#'   checkboxGroupInput("icons", "Choose icons:",
+#'     choiceNames =
+#'       list(icon("calendar"), icon("bed"),
+#'            icon("cog"), icon("bug")),
+#'     choiceValues =
+#'       list("calendar", "bed", "cog", "bug")
+#'   ),
+#'   textOutput("txt")
+#' )
+#'
+#' server <- function(input, output, session) {
+#'   output$txt <- renderText({
+#'     icons <- paste(input$icons, collapse = ", ")
+#'     paste("You chose", icons)
+#'   })
+#' }
+#'
+#' shinyApp(ui, server)
 #' }
 #' @export
-checkboxGroupInput <- function(inputId, label, choices, selected = NULL,
-  inline = FALSE, width = NULL) {
+checkboxGroupInput <- function(inputId, label, choices = NULL, selected = NULL,
+  inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) {
+
+  # keep backward compatibility with Shiny < 1.0.1 (see #1649)
+  if (is.null(choices) && is.null(choiceNames) && is.null(choiceValues)) {
+    choices <- character(0)
+  }
+
+  args <- normalizeChoicesArgs(choices, choiceNames, choiceValues)
 
   selected <- restoreInput(id = inputId, default = selected)
 
-  # resolve names
-  choices <- choicesWithNames(choices)
-  if (!is.null(selected))
-    selected <- validateSelected(selected, choices, inputId)
+  # default value if it's not specified
+  if (!is.null(selected)) selected <- as.character(selected)
 
-  options <- generateOptions(inputId, choices, selected, inline)
+  options <- generateOptions(inputId, selected, inline,
+    'checkbox', args$choiceNames, args$choiceValues)
 
   divClass <- "form-group shiny-input-checkboxgroup shiny-input-container"
   if (inline)
diff --git a/R/input-daterange.R b/R/input-daterange.R
index 4026aca..538a8ff 100644
--- a/R/input-daterange.R
+++ b/R/input-daterange.R
@@ -98,7 +98,7 @@ dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
           class = "input-sm form-control",
           type = "text",
           `data-date-language` = language,
-          `data-date-weekstart` = weekstart,
+          `data-date-week-start` = weekstart,
           `data-date-format` = format,
           `data-date-start-view` = startview,
           `data-min-date` = min,
@@ -110,7 +110,7 @@ dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
           class = "input-sm form-control",
           type = "text",
           `data-date-language` = language,
-          `data-date-weekstart` = weekstart,
+          `data-date-week-start` = weekstart,
           `data-date-format` = format,
           `data-date-start-view` = startview,
           `data-min-date` = min,
diff --git a/R/input-file.R b/R/input-file.R
index 0ff51a6..70fbf7e 100644
--- a/R/input-file.R
+++ b/R/input-file.R
@@ -27,6 +27,9 @@
 #'   Internet Explorer 9 and earlier.}
 #' @param accept A character vector of MIME types; gives the browser a hint of
 #'   what kind of files the server is expecting.
+#' @param buttonLabel The label used on the button. Can be text or an HTML tag
+#'   object.
+#' @param placeholder The text to show before a file has been uploaded.
 #'
 #' @examples
 #' ## Only run examples in interactive R sessions
@@ -70,7 +73,7 @@
 #' }
 #' @export
 fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
-  width = NULL) {
+  width = NULL, buttonLabel = "Browse...", placeholder = "No file selected") {
 
   restoredValue <- restoreInput(id = inputId, default = NULL)
 
@@ -105,12 +108,12 @@ fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
     div(class = "input-group",
       tags$label(class = "input-group-btn",
         span(class = "btn btn-default btn-file",
-          "Browse...",
+          buttonLabel,
           inputTag
         )
       ),
       tags$input(type = "text", class = "form-control",
-        placeholder = "No file selected", readonly = "readonly"
+        placeholder = placeholder, readonly = "readonly"
       )
     ),
 
diff --git a/R/input-radiobuttons.R b/R/input-radiobuttons.R
index 72407f4..16a1444 100644
--- a/R/input-radiobuttons.R
+++ b/R/input-radiobuttons.R
@@ -3,19 +3,30 @@
 #' Create a set of radio buttons used to select an item from a list.
 #'
 #' If you need to represent a "None selected" state, it's possible to default
-#' the radio buttons to have no options selected by using
-#' \code{selected = character(0)}. However, this is not recommended, as it gives
-#' the user no way to return to that state once they've made a selection.
-#' Instead, consider having the first of your choices be \code{c("None selected"
-#' = "")}.
+#' the radio buttons to have no options selected by using \code{selected =
+#' character(0)}. However, this is not recommended, as it gives the user no way
+#' to return to that state once they've made a selection. Instead, consider
+#' having the first of your choices be \code{c("None selected" = "")}.
 #'
 #' @inheritParams textInput
 #' @param choices List of values to select from (if elements of the list are
-#' named then that name rather than the value is displayed to the user)
-#' @param selected The initially selected value (if not specified then
-#' defaults to the first value)
+#'   named then that name rather than the value is displayed to the user). If
+#'   this argument is provided, then \code{choiceNames} and \code{choiceValues}
+#'   must not be provided, and vice-versa. The values should be strings; other
+#'   types (such as logicals and numbers) will be coerced to strings.
+#' @param selected The initially selected value (if not specified then defaults
+#'   to the first value)
 #' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
 #' @return A set of radio buttons that can be added to a UI definition.
+#' @param choiceNames,choiceValues List of names and values, respectively, that
+#'   are displayed to the user in the app and correspond to the each choice (for
+#'   this reason, \code{choiceNames} and \code{choiceValues} must have the same
+#'   length). If either of these arguments is provided, then the other
+#'   \emph{must} be provided and \code{choices} \emph{must not} be provided. The
+#'   advantage of using both of these over a named list for \code{choices} is
+#'   that \code{choiceNames} allows any type of UI object to be passed through
+#'   (tag objects, icons, HTML code, ...), instead of just simple text. See
+#'   Examples.
 #'
 #' @family input elements
 #' @seealso \code{\link{updateRadioButtons}}
@@ -47,27 +58,46 @@
 #' }
 #'
 #' shinyApp(ui, server)
+#'
+#' ui <- fluidPage(
+#'   radioButtons("rb", "Choose one:",
+#'                choiceNames = list(
+#'                  icon("calendar"),
+#'                  HTML("<p style='color:red;'>Red Text</p>"),
+#'                  "Normal text"
+#'                ),
+#'                choiceValues = list(
+#'                  "icon", "html", "text"
+#'                )),
+#'   textOutput("txt")
+#' )
+#'
+#' server <- function(input, output) {
+#'   output$txt <- renderText({
+#'     paste("You chose", input$rb)
+#'   })
+#' }
+#'
+#' shinyApp(ui, server)
 #' }
 #' @export
-radioButtons <- function(inputId, label, choices, selected = NULL,
-  inline = FALSE, width = NULL) {
+radioButtons <- function(inputId, label, choices = NULL, selected = NULL,
+  inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) {
 
-  # resolve names
-  choices <- choicesWithNames(choices)
+  args <- normalizeChoicesArgs(choices, choiceNames, choiceValues)
 
   selected <- restoreInput(id = inputId, default = selected)
 
   # default value if it's not specified
-  selected <- if (is.null(selected)) choices[[1]] else {
-    validateSelected(selected, choices, inputId)
-  }
+  selected <- if (is.null(selected)) args$choiceValues[[1]] else as.character(selected)
+
   if (length(selected) > 1) stop("The 'selected' argument must be of length 1")
 
-  options <- generateOptions(inputId, choices, selected, inline, type = 'radio')
+  options <- generateOptions(inputId, selected, inline,
+    'radio', args$choiceNames, args$choiceValues)
 
   divClass <- "form-group shiny-input-radiogroup shiny-input-container"
-  if (inline)
-    divClass <- paste(divClass, "shiny-input-container-inline")
+  if (inline) divClass <- paste(divClass, "shiny-input-container-inline")
 
   tags$div(id = inputId,
     style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
diff --git a/R/input-select.R b/R/input-select.R
index fb04f47..28117f3 100644
--- a/R/input-select.R
+++ b/R/input-select.R
@@ -5,7 +5,7 @@
 #'
 #' By default, \code{selectInput()} and \code{selectizeInput()} use the
 #' JavaScript library \pkg{selectize.js}
-#' (\url{https://github.com/brianreavis/selectize.js}) to instead of the basic
+#' (\url{https://github.com/selectize/selectize.js}) to instead of the basic
 #' select input element. To use the standard HTML select input element, use
 #' \code{selectInput()} with \code{selectize=FALSE}.
 #'
@@ -74,8 +74,8 @@
 #' }
 #' @export
 selectInput <- function(inputId, label, choices, selected = NULL,
-                        multiple = FALSE, selectize = TRUE, width = NULL,
-                        size = NULL) {
+  multiple = FALSE, selectize = TRUE, width = NULL,
+  size = NULL) {
 
   selected <- restoreInput(id = inputId, default = selected)
 
@@ -85,7 +85,7 @@ selectInput <- function(inputId, label, choices, selected = NULL,
   # default value if it's not specified
   if (is.null(selected)) {
     if (!multiple) selected <- firstChoice(choices)
-  } else selected <- validateSelected(selected, choices, inputId)
+  } else selected <- as.character(selected)
 
   if (!is.null(size) && selectize) {
     stop("'size' argument is incompatible with 'selectize=TRUE'.")
diff --git a/R/input-slider.R b/R/input-slider.R
index afcc5ca..9c51e12 100644
--- a/R/input-slider.R
+++ b/R/input-slider.R
@@ -164,23 +164,21 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
     `data-grid` = ticks,
     `data-grid-num` = n_ticks,
     `data-grid-snap` = FALSE,
+    `data-prettify-separator` = sep,
+    `data-prettify-enabled` = (sep != ""),
     `data-prefix` = pre,
     `data-postfix` = post,
     `data-keyboard` = TRUE,
     `data-keyboard-step` = step / (max - min) * 100,
-    `data-drag-interval` = dragRange,
+    # This value is only relevant for range sliders; for non-range sliders it
+    # causes problems since ion.RangeSlider 2.1.2 (issue #1605).
+    `data-drag-interval` = if (length(value) > 1) dragRange,
     # The following are ignored by the ion.rangeSlider, but are used by Shiny.
     `data-data-type` = dataType,
     `data-time-format` = timeFormat,
     `data-timezone` = timezone
   ))
 
-  if (sep == "") {
-    sliderProps$`data-prettify-enabled` <- "0"
-  } else {
-    sliderProps$`data-prettify-separator` <- sep
-  }
-
   # Replace any TRUE and FALSE with "true" and "false"
   sliderProps <- lapply(sliderProps, function(x) {
     if (identical(x, TRUE)) "true"
@@ -220,7 +218,7 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
   }
 
   dep <- list(
-    htmlDependency("ionrangeslider", "2.1.2", c(href="shared/ionrangeslider"),
+    htmlDependency("ionrangeslider", "2.1.6", c(href="shared/ionrangeslider"),
       script = "js/ion.rangeSlider.min.js",
       # ion.rangeSlider also needs normalize.css, which is already included in
       # Bootstrap.
diff --git a/R/input-utils.R b/R/input-utils.R
index 65eb70b..7f76c64 100644
--- a/R/input-utils.R
+++ b/R/input-utils.R
@@ -2,45 +2,62 @@ controlLabel <- function(controlName, label) {
   label %AND% tags$label(class = "control-label", `for` = controlName, label)
 }
 
-
-# Before shiny 0.9, `selected` refers to names/labels of `choices`; now it
-# refers to values. Below is a function for backward compatibility. It also
-# coerces the value to `character`.
-validateSelected <- function(selected, choices, inputId) {
-  # this line accomplishes two tings:
-  #   - coerces selected to character
-  #   - drops name, otherwise toJSON() keeps it too
-  selected <- as.character(selected)
-  # if you are using optgroups, you're using shiny > 0.10.0, and you should
-  # already know that `selected` must be a value instead of a label
-  if (needOptgroup(choices)) return(selected)
-
-  if (is.list(choices)) choices <- unlist(choices)
-
-  nms <- names(choices)
-  # labels and values are identical, no need to validate
-  if (identical(nms, unname(choices))) return(selected)
-  # when selected labels instead of values
-  i <- (selected %in% nms) & !(selected %in% choices)
-  if (any(i)) {
-    warnFun <- if (all(i)) {
-      # replace names with values
-      selected <- unname(choices[selected])
-      warning
-    } else stop  # stop when it is ambiguous (some labels == values)
-    warnFun("'selected' must be the values instead of names of 'choices' ",
-            "for the input '", inputId, "'")
+# This function takes in either a list or vector for `choices` (and
+# `choiceNames` and `choiceValues` are passed in as NULL) OR it takes
+# in a list or vector for both `choiceNames` and `choiceValues` (and
+# `choices` is passed as NULL) and returns a list of two elements:
+#    - `choiceNames` is a vector or list that holds the options names
+#      (each element can be arbitrary UI, or simple text)
+#    - `choiceValues` is a vector or list that holds the options values
+#       (each element must be simple text)
+normalizeChoicesArgs <- function(choices, choiceNames, choiceValues,
+  mustExist = TRUE) {
+  # if-else to check that either choices OR (choiceNames + choiceValues)
+  # were correctly provided
+  if (is.null(choices)) {
+    if (is.null(choiceNames) || is.null(choiceValues)) {
+      if (mustExist) {
+        stop("Please specify a non-empty vector for `choices` (or, ",
+             "alternatively, for both `choiceNames` AND `choiceValues`).")
+      } else {
+        if (is.null(choiceNames) && is.null(choiceValues)) {
+          # this is useful when we call this function from `updateInputOptions()`
+          # in which case, all three `choices`, `choiceNames` and `choiceValues`
+          # may legitimately be NULL
+          return(list(choiceNames = NULL, choiceValues = NULL))
+        } else {
+          stop("One of `choiceNames` or `choiceValues` was set to ",
+               "NULL, but either both or none should be NULL.")
+        }
+      }
+    }
+    if (length(choiceNames) != length(choiceValues)) {
+      stop("`choiceNames` and `choiceValues` must have the same length.")
+    }
+    if (anyNamed(choiceNames) || anyNamed(choiceValues)) {
+      stop("`choiceNames` and `choiceValues` must not be named.")
+    }
+  } else {
+    if (!is.null(choiceNames) || !is.null(choiceValues)) {
+      warning("Using `choices` argument; ignoring `choiceNames` and `choiceValues`.")
+    }
+    choices <- choicesWithNames(choices) # resolve names if not specified
+    choiceNames <- names(choices)
+    choiceValues <- unname(choices)
   }
-  selected
-}
 
+  return(list(choiceNames = as.list(choiceNames),
+              choiceValues = as.list(as.character(choiceValues))))
+}
 
 # generate options for radio buttons and checkbox groups (type = 'checkbox' or
 # 'radio')
-generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox') {
+generateOptions <- function(inputId, selected, inline, type = 'checkbox',
+                            choiceNames, choiceValues,
+                            session = getDefaultReactiveDomain()) {
   # generate a list of <input type=? [checked] />
   options <- mapply(
-    choices, names(choices),
+    choiceValues, choiceNames,
     FUN = function(value, name) {
       inputTag <- tags$input(
         type = type, name = inputId, value = value
@@ -48,14 +65,18 @@ generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox
       if (value %in% selected)
         inputTag$attribs$checked <- "checked"
 
+      # in case, the options include UI code other than text
+      # (arbitrary HTML using the tags() function or equivalent)
+      pd <- processDeps(name, session)
+
       # If inline, there's no wrapper div, and the label needs a class like
       # checkbox-inline.
       if (inline) {
-        tags$label(class = paste0(type, "-inline"), inputTag, tags$span(name))
+        tags$label(class = paste0(type, "-inline"), inputTag,
+                   tags$span(pd$html, pd$deps))
       } else {
-        tags$div(class = type,
-          tags$label(inputTag, tags$span(name))
-        )
+        tags$div(class = type, tags$label(inputTag,
+                 tags$span(pd$html, pd$deps)))
       }
     },
     SIMPLIFY = FALSE, USE.NAMES = FALSE
diff --git a/R/insert-tab.R b/R/insert-tab.R
new file mode 100644
index 0000000..2412c7c
--- /dev/null
+++ b/R/insert-tab.R
@@ -0,0 +1,325 @@
+#' Dynamically insert/remove a tabPanel
+#'
+#' Dynamically insert or remove a \code{\link{tabPanel}} (or a
+#' \code{\link{navbarMenu}}) from an existing \code{\link{tabsetPanel}},
+#' \code{\link{navlistPanel}} or \code{\link{navbarPage}}.
+#'
+#' When you want to insert a new tab before or after an existing tab, you
+#' should use \code{insertTab}. When you want to prepend a tab (i.e. add a
+#' tab to the beginning of the \code{tabsetPanel}), use \code{prependTab}.
+#' When you want to append a tab (i.e. add a tab to the end of the
+#' \code{tabsetPanel}), use \code{appendTab}.
+#'
+#' For \code{navbarPage}, you can insert/remove conventional
+#' \code{tabPanel}s (whether at the top level or nested inside a
+#' \code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
+#' For the latter case, \code{target} should be the \code{menuName} that
+#' you gave your \code{navbarMenu} when you first created it (by default,
+#' this is equal to the value of the \code{title} argument).
+#'
+#' @param inputId The \code{id} of the \code{tabsetPanel} (or
+#'   \code{navlistPanel} or \code{navbarPage}) into which \code{tab} will
+#'   be inserted/removed.
+#'
+#' @param tab The item to be added (must be created with \code{tabPanel},
+#'   or with \code{navbarMenu}).
+#'
+#' @param target If inserting: the \code{value} of an existing
+#'   \code{tabPanel}, next to which \code{tab} will be added.
+#'   If removing: the \code{value} of the \code{tabPanel} that
+#'   you want to remove. See Details if you want to insert next to/remove
+#'   an entire \code{navbarMenu} instead.
+#'
+#' @param position Should \code{tab} be added before or after the
+#'   \code{target} tab?
+#'
+#' @param select Should \code{tab} be selected upon being inserted?
+#'
+#' @param session The shiny session within which to call this function.
+#'
+#' @seealso \code{\link{showTab}}
+#'
+#' @examples
+#' ## Only run this example in interactive R sessions
+#' if (interactive()) {
+#'
+#' # example app for inserting/removing a tab
+#' ui <- fluidPage(
+#'   sidebarLayout(
+#'     sidebarPanel(
+#'       actionButton("add", "Add 'Dynamic' tab"),
+#'       actionButton("remove", "Remove 'Foo' tab")
+#'     ),
+#'     mainPanel(
+#'       tabsetPanel(id = "tabs",
+#'         tabPanel("Hello", "This is the hello tab"),
+#'         tabPanel("Foo", "This is the foo tab"),
+#'         tabPanel("Bar", "This is the bar tab")
+#'       )
+#'     )
+#'   )
+#' )
+#' server <- function(input, output, session) {
+#'   observeEvent(input$add, {
+#'     insertTab(inputId = "tabs",
+#'       tabPanel("Dynamic", "This a dynamically-added tab"),
+#'       target = "Bar"
+#'     )
+#'   })
+#'   observeEvent(input$remove, {
+#'     removeTab(inputId = "tabs", target = "Foo")
+#'   })
+#' }
+#'
+#' shinyApp(ui, server)
+#'
+#'
+#' # example app for prepending/appending a navbarMenu
+#' ui <- navbarPage("Navbar page", id = "tabs",
+#'   tabPanel("Home",
+#'     actionButton("prepend", "Prepend a navbarMenu"),
+#'     actionButton("append", "Append a navbarMenu")
+#'   )
+#' )
+#' server <- function(input, output, session) {
+#'   observeEvent(input$prepend, {
+#'     id <- paste0("Dropdown", input$prepend, "p")
+#'     prependTab(inputId = "tabs",
+#'       navbarMenu(id,
+#'         tabPanel("Drop1", paste("Drop1 page from", id)),
+#'         tabPanel("Drop2", paste("Drop2 page from", id)),
+#'         "------",
+#'         "Header",
+#'         tabPanel("Drop3", paste("Drop3 page from", id))
+#'       )
+#'     )
+#'   })
+#'   observeEvent(input$append, {
+#'     id <- paste0("Dropdown", input$append, "a")
+#'     appendTab(inputId = "tabs",
+#'       navbarMenu(id,
+#'         tabPanel("Drop1", paste("Drop1 page from", id)),
+#'         tabPanel("Drop2", paste("Drop2 page from", id)),
+#'         "------",
+#'         "Header",
+#'         tabPanel("Drop3", paste("Drop3 page from", id))
+#'       )
+#'     )
+#'   })
+#' }
+#'
+#' shinyApp(ui, server)
+#'
+#' }
+#' @export
+insertTab <- function(inputId, tab, target,
+                      position = c("before", "after"), select = FALSE,
+                      session = getDefaultReactiveDomain()) {
+  force(target)
+  force(select)
+  position <- match.arg(position)
+  inputId <- session$ns(inputId)
+
+  # Barbara -- August 2017
+  # Note: until now, the number of tabs in a tabsetPanel (or navbarPage
+  # or navlistPanel) was always fixed. So, an easy way to give an id to
+  # a tab was simply incrementing a counter. (Just like it was easy to
+  # give a random 4-digit number to identify the tabsetPanel). Since we
+  # can only know this in the client side, we'll just pass `id` and
+  # `tsid` (TabSetID) as dummy values that will be fixed in the JS code.
+  item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
+    textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
+
+  callback <- function() {
+    session$sendInsertTab(
+      inputId = inputId,
+      liTag = processDeps(item$liTag, session),
+      divTag = processDeps(item$divTag, session),
+      menuName = NULL,
+      target = target,
+      position = position,
+      select = select)
+  }
+  session$onFlush(callback, once = TRUE)
+}
+
+#' @param menuName This argument should only be used when you want to
+#'   prepend (or append) \code{tab} to the beginning (or end) of an
+#'   existing \code{\link{navbarMenu}} (which must itself be part of
+#'   an existing \code{\link{navbarPage}}). In this case, this argument
+#'   should be the \code{menuName} that you gave your \code{navbarMenu}
+#'   when you first created it (by default, this is equal to the value
+#'   of the \code{title} argument). Note that you still need to set the
+#'   \code{inputId} argument to whatever the \code{id} of the parent
+#'   \code{navbarPage} is. If \code{menuName} is left as \code{NULL},
+#'   \code{tab} will be prepended (or appended) to whatever
+#'   \code{inputId} is.
+#'
+#' @rdname insertTab
+#' @export
+prependTab <- function(inputId, tab, select = FALSE, menuName = NULL,
+                       session = getDefaultReactiveDomain()) {
+  force(select)
+  force(menuName)
+  inputId <- session$ns(inputId)
+
+  item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
+    textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
+
+  callback <- function() {
+    session$sendInsertTab(
+      inputId = inputId,
+      liTag = processDeps(item$liTag, session),
+      divTag = processDeps(item$divTag, session),
+      menuName = menuName,
+      target = NULL,
+      position = "after",
+      select = select)
+  }
+  session$onFlush(callback, once = TRUE)
+}
+
+#' @rdname insertTab
+#' @export
+appendTab <- function(inputId, tab, select = FALSE, menuName = NULL,
+                      session = getDefaultReactiveDomain()) {
+  force(select)
+  force(menuName)
+  inputId <- session$ns(inputId)
+
+  item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
+    textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
+
+  callback <- function() {
+    session$sendInsertTab(
+      inputId = inputId,
+      liTag = processDeps(item$liTag, session),
+      divTag = processDeps(item$divTag, session),
+      menuName = menuName,
+      target = NULL,
+      position = "before",
+      select = select)
+  }
+  session$onFlush(callback, once = TRUE)
+}
+
+#' @rdname insertTab
+#' @export
+removeTab <- function(inputId, target,
+                      session = getDefaultReactiveDomain()) {
+  force(target)
+  inputId <- session$ns(inputId)
+
+  callback <- function() {
+    session$sendRemoveTab(
+      inputId = inputId,
+      target = target)
+  }
+  session$onFlush(callback, once = TRUE)
+}
+
+
+#' Dynamically hide/show a tabPanel
+#'
+#' Dynamically hide or show a \code{\link{tabPanel}} (or a
+#' \code{\link{navbarMenu}})from an existing \code{\link{tabsetPanel}},
+#' \code{\link{navlistPanel}} or \code{\link{navbarPage}}.
+#'
+#' For \code{navbarPage}, you can hide/show conventional
+#' \code{tabPanel}s (whether at the top level or nested inside a
+#' \code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
+#' For the latter case, \code{target} should be the \code{menuName} that
+#' you gave your \code{navbarMenu} when you first created it (by default,
+#' this is equal to the value of the \code{title} argument).
+#'
+#' @param inputId The \code{id} of the \code{tabsetPanel} (or
+#'   \code{navlistPanel} or \code{navbarPage}) in which to find
+#'   \code{target}.
+#'
+#' @param target The \code{value} of the \code{tabPanel} to be
+#'   hidden/shown. See Details if you want to hide/show an entire
+#'   \code{navbarMenu} instead.
+#'
+#' @param select Should \code{target} be selected upon being shown?
+#'
+#' @param session The shiny session within which to call this function.
+#'
+#' @seealso \code{\link{insertTab}}
+#'
+#' @examples
+#' ## Only run this example in interactive R sessions
+#' if (interactive()) {
+#'
+#' ui <- navbarPage("Navbar page", id = "tabs",
+#'   tabPanel("Home",
+#'     actionButton("hideTab", "Hide 'Foo' tab"),
+#'     actionButton("showTab", "Show 'Foo' tab"),
+#'     actionButton("hideMenu", "Hide 'More' navbarMenu"),
+#'     actionButton("showMenu", "Show 'More' navbarMenu")
+#'   ),
+#'   tabPanel("Foo", "This is the foo tab"),
+#'   tabPanel("Bar", "This is the bar tab"),
+#'   navbarMenu("More",
+#'     tabPanel("Table", "Table page"),
+#'     tabPanel("About", "About page"),
+#'     "------",
+#'     "Even more!",
+#'     tabPanel("Email", "Email page")
+#'   )
+#' )
+#'
+#' server <- function(input, output, session) {
+#'   observeEvent(input$hideTab, {
+#'     hideTab(inputId = "tabs", target = "Foo")
+#'   })
+#'
+#'   observeEvent(input$showTab, {
+#'     showTab(inputId = "tabs", target = "Foo")
+#'   })
+#'
+#'   observeEvent(input$hideMenu, {
+#'     hideTab(inputId = "tabs", target = "More")
+#'   })
+#'
+#'   observeEvent(input$showMenu, {
+#'     showTab(inputId = "tabs", target = "More")
+#'   })
+#' }
+#'
+#' shinyApp(ui, server)
+#' }
+#'
+#' @export
+showTab <- function(inputId, target, select = FALSE,
+                    session = getDefaultReactiveDomain()) {
+  force(target)
+
+  if (select) updateTabsetPanel(session, inputId, selected = target)
+  inputId <- session$ns(inputId)
+
+  callback <- function() {
+    session$sendChangeTabVisibility(
+      inputId = inputId,
+      target = target,
+      type = "show"
+    )
+  }
+  session$onFlush(callback, once = TRUE)
+}
+
+#' @rdname showTab
+#' @export
+hideTab <- function(inputId, target,
+                    session = getDefaultReactiveDomain()) {
+  force(target)
+  inputId <- session$ns(inputId)
+
+  callback <- function() {
+    session$sendChangeTabVisibility(
+      inputId = inputId,
+      target = target,
+      type = "hide"
+    )
+  }
+  session$onFlush(callback, once = TRUE)
+}
diff --git a/R/middleware.R b/R/middleware.R
index 6210f60..640c11a 100644
--- a/R/middleware.R
+++ b/R/middleware.R
@@ -191,7 +191,7 @@ staticHandler <- function(root) {
     if (!identical(req$REQUEST_METHOD, 'GET'))
       return(NULL)
 
-    path <- req$PATH_INFO
+    path <- URLdecode(req$PATH_INFO)
 
     if (is.null(path))
       return(httpResponse(400, content="<h1>Bad Request</h1>"))
@@ -360,7 +360,9 @@ HandlerManager <- R6Class("HandlerManager",
 
           response <- filter(req, response)
           if (head_request) {
-            headers$`Content-Length` <- nchar(response$content, type = "bytes")
+
+            headers$`Content-Length` <- getResponseContentLength(response, deleteOwnedContent = TRUE)
+
             return(list(
               status = response$status,
               body = "",
@@ -383,6 +385,35 @@ HandlerManager <- R6Class("HandlerManager",
   )
 )
 
+# Safely get the Content-Length of a Rook response, or NULL if the length cannot
+# be determined for whatever reason (probably malformed response$content).
+# If deleteOwnedContent is TRUE, then the function should delete response
+# content that is of the form list(file=..., owned=TRUE).
+getResponseContentLength <- function(response, deleteOwnedContent) {
+  force(deleteOwnedContent)
+
+  result <- if (is.character(response$content) && length(response$content) == 1) {
+    nchar(response$content, type = "bytes")
+  } else if (is.raw(response$content)) {
+    length(response$content)
+  } else if (is.list(response$content) && !is.null(response$content$file)) {
+    if (deleteOwnedContent && isTRUE(response$content$owned)) {
+      on.exit(unlink(response$content$file, recursive = FALSE, force = FALSE), add = TRUE)
+    }
+    file.info(response$content$file)$size
+  } else {
+    warning("HEAD request for unexpected content class ", class(response$content)[[1]])
+    NULL
+  }
+
+  if (is.na(result)) {
+    # Mostly for missing file case
+    return(NULL)
+  } else {
+    return(result)
+  }
+}
+
 #
 # ## Next steps
 #
diff --git a/R/modules.R b/R/modules.R
index ee5d742..151b6f0 100644
--- a/R/modules.R
+++ b/R/modules.R
@@ -26,6 +26,11 @@ createSessionProxy <- function(parentSession, ...) {
 
 #' @export
 `$<-.session_proxy` <- function(x, name, value) {
+  # this line allows users to write into session$userData
+  # (e.g. it allows something like `session$userData$x <- TRUE`,
+  # but not `session$userData <- TRUE`) from within a module
+  # without any hacks (see PR #1732)
+  if (identical(x[[name]], value)) return(x)
   stop("Attempted to assign value on session proxy.")
 }
 
diff --git a/R/progress.R b/R/progress.R
index 7acf942..dea7bf9 100644
--- a/R/progress.R
+++ b/R/progress.R
@@ -55,7 +55,6 @@
 #'   de-emphasized appearance relative to \code{message}.
 #' @param value A numeric value at which to set
 #'   the progress bar, relative to \code{min} and \code{max}.
-#'   \code{NULL} hides the progress bar, if it is currently visible.
 #' @param style Progress display style. If \code{"notification"} (the default),
 #'   the progress indicator will show using Shiny's notification API. If
 #'   \code{"old"}, use the same HTML and CSS used in Shiny 0.13.2 and below
@@ -98,7 +97,6 @@
 #' @export
 Progress <- R6Class(
   'Progress',
-  portable = TRUE,
   public = list(
 
     initialize = function(session = getDefaultReactiveDomain(),
@@ -112,8 +110,8 @@ Progress <- R6Class(
       private$id <- createUniqueId(8)
       private$min <- min
       private$max <- max
-      private$style <- match.arg(style, choices = c("notification", "old"))
       private$value <- NULL
+      private$style <- match.arg(style, choices = c("notification", "old"))
       private$closed <- FALSE
 
       session$sendProgress('open', list(id = private$id, style = private$style))
@@ -125,15 +123,15 @@ Progress <- R6Class(
         return()
       }
 
-      if (is.null(value) || is.na(value)) {
+      if (is.null(value) || is.na(value))
         value <- NULL
-      } else {
+
+      if (!is.null(value)) {
+        private$value <- value
         # Normalize value to number between 0 and 1
         value <- min(1, max(0, (value - private$min) / (private$max - private$min)))
       }
 
-      private$value <- value
-
       data <- dropNulls(list(
         id = private$id,
         message = message,
@@ -142,11 +140,14 @@ Progress <- R6Class(
         style = private$style
       ))
 
-       private$session$sendProgress('update', data)
+      private$session$sendProgress('update', data)
     },
 
     inc = function(amount = 0.1, message = NULL, detail = NULL) {
-      value <- min(self$getValue() + amount, private$max)
+      if (is.null(private$value))
+        private$value <- private$min
+
+      value <- min(private$value + amount, private$max)
       self$set(value, message, detail)
     },
 
@@ -154,10 +155,7 @@ Progress <- R6Class(
 
     getMax = function() private$max,
 
-    # Return value (not the normalized 0-1 value, but in the original range)
-    getValue = function() {
-      private$value * (private$max - private$min) + private$min
-    },
+    getValue = function() private$value,
 
     close = function() {
       if (private$closed) {
@@ -173,12 +171,12 @@ Progress <- R6Class(
   ),
 
   private = list(
-    session = 'environment',
+    session = 'ShinySession',
     id = character(0),
     min = numeric(0),
     max = numeric(0),
     style = character(0),
-    value = NULL,
+    value = numeric(0),
     closed = logical(0)
   )
 )
@@ -239,8 +237,7 @@ Progress <- R6Class(
 #'   \code{"old"}, use the same HTML and CSS used in Shiny 0.13.2 and below
 #'   (this is for backward-compatibility).
 #' @param value Single-element numeric vector; the value at which to set the
-#'   progress bar, relative to \code{min} and \code{max}. \code{NULL} hides the
-#'   progress bar, if it is currently visible.
+#'   progress bar, relative to \code{min} and \code{max}.
 #'
 #' @examples
 #' ## Only run examples in interactive R sessions
diff --git a/R/reactives.R b/R/reactives.R
index 51a7100..8826879 100644
--- a/R/reactives.R
+++ b/R/reactives.R
@@ -38,6 +38,229 @@ Dependents <- R6Class(
 )
 
 
+# ReactiveVal ---------------------------------------------------------------
+
+ReactiveVal <- R6Class(
+  'ReactiveVal',
+  portable = FALSE,
+  private = list(
+    value = NULL,
+    label = NULL,
+    frozen = FALSE,
+    dependents = NULL
+  ),
+  public = list(
+    initialize = function(value, label = NULL) {
+      private$value <- value
+      private$label <- label
+      private$dependents <- Dependents$new()
+      .graphValueChange(private$label, value)
+    },
+    get = function() {
+      private$dependents$register(depLabel = private$label)
+
+      if (private$frozen)
+        reactiveStop()
+
+      private$value
+    },
+    set = function(value) {
+      if (identical(private$value, value)) {
+        return(invisible(FALSE))
+      }
+      private$value <- value
+      .graphValueChange(private$label, value)
+      private$dependents$invalidate()
+      invisible(TRUE)
+    },
+    freeze = function(session = getDefaultReactiveDomain()) {
+      if (is.null(session)) {
+        stop("Can't freeze a reactiveVal without a reactive domain")
+      }
+      session$onFlushed(function() {
+        self$thaw()
+      })
+      private$frozen <- TRUE
+    },
+    thaw = function() {
+      private$frozen <- FALSE
+    },
+    isFrozen = function() {
+      private$frozen
+    },
+    format = function(...) {
+      # capture.output(print()) is necessary because format() doesn't
+      # necessarily return a character vector, e.g. data.frame.
+      label <- capture.output(print(base::format(private$value, ...)))
+      if (length(label) == 1) {
+        paste0("reactiveVal: ", label)
+      } else {
+        c("reactiveVal:", label)
+      }
+    }
+  )
+)
+
+#' Create a (single) reactive value
+#'
+#' The \code{reactiveVal} function is used to construct a "reactive value"
+#' object. This is an object used for reading and writing a value, like a
+#' variable, but with special capabilities for reactive programming. When you
+#' read the value out of a reactiveVal object, the calling reactive expression
+#' takes a dependency, and when you change the value, it notifies any reactives
+#' that previously depended on that value.
+#'
+#' \code{reactiveVal} is very similar to \code{\link{reactiveValues}}, except
+#' that the former is for a single reactive value (like a variable), whereas the
+#' latter lets you conveniently use multiple reactive values by name (like a
+#' named list of variables). For a one-off reactive value, it's more natural to
+#' use \code{reactiveVal}. See the Examples section for an illustration.
+#'
+#' @param value An optional initial value.
+#' @param label An optional label, for debugging purposes (see
+#'   \code{\link{showReactLog}}). If missing, a label will be automatically
+#'   created.
+#'
+#' @return A function. Call the function with no arguments to (reactively) read
+#'   the value; call the function with a single argument to set the value.
+#'
+#' @examples
+#'
+#' \dontrun{
+#'
+#' # Create the object by calling reactiveVal
+#' r <- reactiveVal()
+#'
+#' # Set the value by calling with an argument
+#' r(10)
+#'
+#' # Read the value by calling without arguments
+#' r()
+#'
+#' }
+#'
+#' ## Only run examples in interactive R sessions
+#' if (interactive()) {
+#'
+#' ui <- fluidPage(
+#'   actionButton("minus", "-1"),
+#'   actionButton("plus", "+1"),
+#'   br(),
+#'   textOutput("value")
+#' )
+#'
+#' # The comments below show the equivalent logic using reactiveValues()
+#' server <- function(input, output, session) {
+#'   value <- reactiveVal(0)       # rv <- reactiveValues(value = 0)
+#'
+#'   observeEvent(input$minus, {
+#'     newValue <- value() - 1     # newValue <- rv$value - 1
+#'     value(newValue)             # rv$value <- newValue
+#'   })
+#'
+#'   observeEvent(input$plus, {
+#'     newValue <- value() + 1     # newValue <- rv$value + 1
+#'     value(newValue)             # rv$value <- newValue
+#'   })
+#'
+#'   output$value <- renderText({
+#'     value()                     # rv$value
+#'   })
+#' }
+#'
+#' shinyApp(ui, server)
+#'
+#' }
+#'
+#' @export
+reactiveVal <- function(value = NULL, label = NULL) {
+  if (missing(label)) {
+    call <- sys.call()
+    label <- rvalSrcrefToLabel(attr(call, "srcref", exact = TRUE))
+  }
+
+  rv <- ReactiveVal$new(value, label)
+  structure(
+    function(x) {
+      if (missing(x)) {
+        rv$get()
+      } else {
+        force(x)
+        rv$set(x)
+      }
+    },
+    class = c("reactiveVal", "reactive"),
+    label = label,
+    .impl = rv
+  )
+}
+
+#' @rdname freezeReactiveValue
+#' @export
+freezeReactiveVal <- function(x) {
+  domain <- getDefaultReactiveDomain()
+  if (is.null(domain)) {
+    stop("freezeReactiveVal() must be called when a default reactive domain is active.")
+  }
+  if (!inherits(x, "reactiveVal")) {
+    stop("x must be a reactiveVal object")
+  }
+
+  attr(x, ".impl", exact = TRUE)$freeze(domain)
+  invisible()
+}
+
+#' @export
+format.reactiveVal <- function(x, ...) {
+  attr(x, ".impl", exact = TRUE)$format(...)
+}
+
+# Attempts to extract the variable name that the reactiveVal object is being
+# assigned to (e.g. for `a <- reactiveVal()`, the result should be "a"). This
+# is a fragile, error-prone operation, so we default to a random label if
+# necessary.
+rvalSrcrefToLabel <- function(srcref,
+  defaultLabel = paste0("reactiveVal", createUniqueId(4))) {
+
+  if (is.null(srcref))
+    return(defaultLabel)
+
+  srcfile <- attr(srcref, "srcfile", exact = TRUE)
+  if (is.null(srcfile))
+    return(defaultLabel)
+
+  if (is.null(srcfile$lines))
+    return(defaultLabel)
+
+  lines <- srcfile$lines
+  # When pasting at the Console, srcfile$lines is not split
+  if (length(lines) == 1) {
+    lines <- strsplit(lines, "\n")[[1]]
+  }
+
+  if (length(lines) < srcref[1]) {
+    return(defaultLabel)
+  }
+
+  firstLine <- substring(lines[srcref[1]], srcref[2] - 1)
+
+  m <- regexec("\\s*([^[:space:]]+)\\s*(<-|=)\\s*reactiveVal\\b", firstLine)
+  if (m[[1]][1] == -1) {
+    return(defaultLabel)
+  }
+
+  sym <- regmatches(firstLine, m)[[1]][2]
+  res <- try(parse(text = sym), silent = TRUE)
+  if (inherits(res, "try-error"))
+    return(defaultLabel)
+
+  if (length(res) != 1)
+    return(defaultLabel)
+
+  return(as.character(res))
+}
+
+
 # ReactiveValues ------------------------------------------------------------
 
 ReactiveValues <- R6Class(
@@ -397,14 +620,17 @@ str.reactivevalues <- function(object, indent.str = " ", ...) {
 
 #' Freeze a reactive value
 #'
-#' This freezes a reactive value. If the value is accessed while frozen, a
+#' These functions freeze a \code{\link{reactiveVal}}, or an element of a
+#' \code{\link{reactiveValues}}. If the value is accessed while frozen, a
 #' "silent" exception is raised and the operation is stopped. This is the same
 #' thing that happens if \code{req(FALSE)} is called. The value is thawed
 #' (un-frozen; accessing it will no longer raise an exception) when the current
 #' reactive domain is flushed. In a Shiny application, this occurs after all of
 #' the observers are executed.
 #'
-#' @param x A \code{\link{reactiveValues}} object (like \code{input}).
+#' @param x For \code{freezeReactiveValue}, a \code{\link{reactiveValues}}
+#'   object (like \code{input}); for \code{freezeReactiveVal}, a
+#'   \code{\link{reactiveVal}} object.
 #' @param name The name of a value in the \code{\link{reactiveValues}} object.
 #'
 #' @seealso \code{\link{req}}
@@ -446,7 +672,7 @@ str.reactivevalues <- function(object, indent.str = " ", ...) {
 #' @export
 freezeReactiveValue <- function(x, name) {
   domain <- getDefaultReactiveDomain()
-  if (is.null(getDefaultReactiveDomain)) {
+  if (is.null(domain)) {
     stop("freezeReactiveValue() must be called when a default reactive domain is active.")
   }
 
@@ -461,6 +687,7 @@ Observable <- R6Class(
   'Observable',
   portable = FALSE,
   public = list(
+    .origFunc = 'function',
     .func = 'function',
     .label = character(0),
     .domain = NULL,
@@ -490,6 +717,7 @@ Observable <- R6Class(
         funcLabel <- paste0("<reactive:", label, ">")
       }
 
+      .origFunc <<- func
       .func <<- wrapFunctionLabel(func, funcLabel,
         ..stacktraceon = ..stacktraceon)
       .label <<- label
@@ -520,6 +748,10 @@ Observable <- R6Class(
       else
         invisible(.value)
     },
+    format = function() {
+      label <- sprintf('reactive(%s)', paste(deparse(body(.origFunc)), collapse='\n'))
+      strsplit(label, "\n")[[1]]
+    },
     .updateValue = function() {
       ctx <- Context$new(.domain, .label, type = 'observable',
                          prevId = .mostRecentCtxId)
@@ -629,13 +861,13 @@ reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL,
   # Attach a label and a reference to the original user source for debugging
   srcref <- attr(substitute(x), "srcref", exact = TRUE)
   if (is.null(label)) {
-    label <- srcrefToLabel(srcref[[1]],
+    label <- rexprSrcrefToLabel(srcref[[1]],
       sprintf('reactive(%s)', paste(deparse(body(fun)), collapse='\n')))
   }
   if (length(srcref) >= 2) attr(label, "srcref") <- srcref[[2]]
   attr(label, "srcfile") <- srcFileOfRef(srcref[[1]])
   o <- Observable$new(fun, label, domain, ..stacktraceon = ..stacktraceon)
-  structure(o$getValue, observable = o, class = "reactive")
+  structure(o$getValue, observable = o, class = c("reactiveExpr", "reactive"))
 }
 
 # Given the srcref to a reactive expression, attempts to figure out what the
@@ -643,7 +875,7 @@ reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL,
 # scans the line of code that started the reactive block and looks for something
 # that looks like assignment. If we fail, fall back to a default value (likely
 # the block of code in the body of the reactive).
-srcrefToLabel <- function(srcref, defaultLabel) {
+rexprSrcrefToLabel <- function(srcref, defaultLabel) {
   if (is.null(srcref))
     return(defaultLabel)
 
@@ -682,18 +914,24 @@ srcrefToLabel <- function(srcref, defaultLabel) {
 }
 
 #' @export
+format.reactiveExpr <- function(x, ...) {
+  attr(x, "observable", exact = TRUE)$format()
+}
+
+#' @export
 print.reactive <- function(x, ...) {
-  label <- attr(x, "observable", exact = TRUE)$.label
-  cat(label, "\n")
+  cat(paste(format(x), collapse = "\n"), "\n")
 }
 
 #' @export
 #' @rdname reactive
-is.reactive <- function(x) inherits(x, "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.reactive(x))
+  if (inherits(x, "reactiveExpr"))
     return(attr(x, "observable", exact = TRUE)$.execCount)
   else if (inherits(x, 'Observer'))
     return(x$.execCount)
@@ -1151,6 +1389,10 @@ setAutoflush <- local({
 #' }
 #' @export
 reactiveTimer <- function(intervalMs=1000, session = getDefaultReactiveDomain()) {
+  # Need to make sure that session is resolved at creation, not when the
+  # callback below is fired (see #1621).
+  force(session)
+
   dependents <- Map$new()
   timerCallbacks$schedule(intervalMs, function() {
     # Quit if the session is closed
@@ -1300,9 +1542,22 @@ coerceToFunc <- function(x) {
 #' @seealso \code{\link{reactiveFileReader}}
 #'
 #' @examples
-#' # Assume the existence of readTimestamp and readValue functions
 #' function(input, output, session) {
-#'   data <- reactivePoll(1000, session, readTimestamp, readValue)
+#'
+#'   data <- reactivePoll(1000, session,
+#'     # This function returns the time that log_file was last modified
+#'     checkFunc = function() {
+#'       if (file.exists(log_file))
+#'         file.info(log_file)$mtime[1]
+#'       else
+#'         ""
+#'     },
+#'     # This function returns the content of log_file
+#'     valueFunc = function() {
+#'       read.csv(log_file)
+#'     }
+#'   )
+#'
 #'   output$dataTable <- renderTable({
 #'     data()
 #'   })
@@ -1377,7 +1632,7 @@ reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
 #' # 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)
+#' fileData <- reactiveFileReader(1000, NULL, 'data.csv', read.csv)
 #' function(input, output, session) {
 #'   output$data <- renderTable({
 #'     fileData()
@@ -1531,7 +1786,7 @@ maskReactiveContext <- function(expr) {
 #' invalidations that come from its reactive dependencies; it only invalidates
 #' in response to the given event.
 #'
-#' @section \code{ignoreNULL} and \code{ignoreInit}:
+#' @section ignoreNULL and ignoreInit:
 #'
 #' Both \code{observeEvent} and \code{eventReactive} take an \code{ignoreNULL}
 #' parameter that affects behavior when the \code{eventExpr} evaluates to
diff --git a/R/render-plot.R b/R/render-plot.R
index 634d06b..a07e470 100644
--- a/R/render-plot.R
+++ b/R/render-plot.R
@@ -287,17 +287,30 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
 #   .. ..$ y: NULL
 #   ..$ mapping: Named list()
 #
-# For ggplot2, it might be something like:
-# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
-# str(getGgplotCoordmap(p, 1))
+# For ggplot2, first you need to define the print.ggplot function from inside
+# renderPlot, then use it to print the plot:
+# print.ggplot <- function(x) {
+#   grid::grid.newpage()
+#
+#   build <- ggplot2::ggplot_build(x)
+#
+#   gtable <- ggplot2::ggplot_gtable(build)
+#   grid::grid.draw(gtable)
+#
+#   structure(list(
+#     build = build,
+#     gtable = gtable
+#   ), class = "ggplot_build_gtable")
+# }
+#
+# p <- print(ggplot(mtcars, aes(wt, mpg)) + geom_point())
+# str(getGgplotCoordmap(p, 1, 72))
 # List of 1
 #  $ :List of 10
 #   ..$ panel     : int 1
 #   ..$ row       : int 1
 #   ..$ col       : int 1
 #   ..$ panel_vars: Named list()
-#   ..$ scale_x   : int 1
-#   ..$ scale_y   : int 1
 #   ..$ log       :List of 2
 #   .. ..$ x: NULL
 #   .. ..$ y: NULL
@@ -320,8 +333,8 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
 # can be up to two of them.
 # mtc <- mtcars
 # mtc$am <- factor(mtc$am)
-# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() + facet_wrap(~ am)
-# str(getGgplotCoordmap(p, 1))
+# p <- print(ggplot(mtc, aes(wt, mpg)) + geom_point() + facet_wrap(~ am))
+# str(getGgplotCoordmap(p, 1, 72))
 # List of 2
 #  $ :List of 10
 #   ..$ panel     : int 1
@@ -329,8 +342,6 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
 #   ..$ col       : int 1
 #   ..$ panel_vars:List of 1
 #   .. ..$ panelvar1: Factor w/ 2 levels "0","1": 1
-#   ..$ scale_x   : int 1
-#   ..$ scale_y   : int 1
 #   ..$ log       :List of 2
 #   .. ..$ x: NULL
 #   .. ..$ y: NULL
@@ -354,8 +365,6 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
 #   ..$ col       : int 2
 #   ..$ panel_vars:List of 1
 #   .. ..$ panelvar1: Factor w/ 2 levels "0","1": 2
-#   ..$ scale_x   : int 1
-#   ..$ scale_y   : int 1
 #   ..$ log       :List of 2
 #   .. ..$ x: NULL
 #   .. ..$ y: NULL
@@ -418,81 +427,191 @@ getPrevPlotCoordmap <- function(width, height) {
 
 # Given a ggplot_build_gtable object, return a coordmap for it.
 getGgplotCoordmap <- function(p, pixelratio, res) {
-  # Structure of ggplot objects changed after 2.1.0
-  new_ggplot <- (utils::packageVersion("ggplot2") > "2.1.0")
-
   if (!inherits(p, "ggplot_build_gtable"))
     return(NULL)
 
+  tryCatch({
+    # Get info from built ggplot object
+    info <- find_panel_info(p$build)
+
+    # Get ranges from gtable - it's possible for this to return more elements than
+    # info, because it calculates positions even for panels that aren't present.
+    # This can happen with facet_wrap.
+    ranges <- find_panel_ranges(p$gtable, pixelratio, res)
+
+    for (i in seq_along(info)) {
+      info[[i]]$range <- ranges[[i]]
+    }
+
+    return(info)
+
+  }, error = function(e) {
+    # If there was an error extracting info from the ggplot object, just return
+    # a list with the error message.
+    return(structure(list(), error = e$message))
+  })
+}
+
+
+find_panel_info <- function(b) {
+  # Structure of ggplot objects changed after 2.1.0. After 2.2.1, there was a
+  # an API for extracting the necessary information.
+  ggplot_ver <- utils::packageVersion("ggplot2")
+
+  if (ggplot_ver > "2.2.1") {
+    find_panel_info_api(b)
+  } else if (ggplot_ver > "2.1.0") {
+    find_panel_info_non_api(b, ggplot_format = "new")
+  } else {
+    find_panel_info_non_api(b, ggplot_format = "old")
+  }
+}
+
+# This is for ggplot2>2.2.1, after an API was introduced for extracting
+# information about the plot object.
+find_panel_info_api <- function(b) {
+  # Workaround for check NOTE, until ggplot2 >2.2.1 is released
+  colon_colon <- `::`
   # Given a built ggplot object, return x and y domains (data space coords) for
   # each panel.
-  find_panel_info <- function(b) {
-    if (new_ggplot) {
-      layout <- b$layout$panel_layout
-    } else {
-      layout <- b$panel$layout
-    }
-    # Convert factor to numbers
-    layout$PANEL <- as.integer(as.character(layout$PANEL))
-
-    # Names of facets
-    facet_vars <- NULL
-    if (new_ggplot) {
-      facet <- b$layout$facet
-      if (inherits(facet, "FacetGrid")) {
-        facet_vars <- vapply(c(facet$params$cols, facet$params$rows), as.character, character(1))
-      } else if (inherits(facet, "FacetWrap")) {
-        facet_vars <- vapply(facet$params$facets, as.character, character(1))
-      }
-    } else {
-      facet <- b$plot$facet
-      if (inherits(facet, "grid")) {
-        facet_vars <- vapply(c(facet$cols, facet$rows), as.character, character(1))
-      } else if (inherits(facet, "wrap")) {
-        facet_vars <- vapply(facet$facets, as.character, character(1))
+  layout <- colon_colon("ggplot2", "summarise_layout")(b)
+  coord  <- colon_colon("ggplot2", "summarise_coord")(b)
+  layers <- colon_colon("ggplot2", "summarise_layers")(b)
+
+  # Given x and y scale objects and a coord object, return a list that has
+  # the bases of log transformations for x and y, or NULL if it's not a
+  # log transform.
+  get_log_bases <- function(xscale, yscale, coord) {
+    # Given a transform object, find the log base; if the transform object is
+    # NULL, or if it's not a log transform, return NA.
+    get_log_base <- function(trans) {
+      if (!is.null(trans) && grepl("^log-", trans$name)) {
+        environment(trans$transform)$base
+      } else {
+        NA_real_
       }
     }
 
-    # Iterate over each row in the layout data frame
-    lapply(seq_len(nrow(layout)), function(i) {
-      # Slice out one row
-      l <- layout[i, ]
+    # First look for log base in scale, then coord; otherwise NULL.
+    list(
+      x = get_log_base(xscale$trans) %OR% coord$xlog %OR% NULL,
+      y = get_log_base(yscale$trans) %OR% coord$ylog %OR% NULL
+    )
+  }
 
-      scale_x <- l$SCALE_X
-      scale_y <- l$SCALE_Y
+  # Given x/y min/max, and the x/y scale objects, create a list that
+  # represents the domain. Note that the x/y min/max should be taken from
+  # the layout summary table, not the scale objects.
+  get_domain <- function(xmin, xmax, ymin, ymax, xscale, yscale) {
+    is_reverse <- function(scale) {
+      identical(scale$trans$name, "reverse")
+    }
 
-      mapping <- find_plot_mappings(b)
+    domain <- list(
+      left   = xmin,
+      right  = xmax,
+      bottom = ymin,
+      top    = ymax
+    )
 
-      # For each of the faceting variables, get the value of that variable in
-      # the current panel. Default to empty _named_ list so that it's sent as a
-      # JSON object, not array.
-      panel_vars <- list(a = NULL)[0]
-      for (i in seq_along(facet_vars)) {
-        var_name <- facet_vars[[i]]
-        vname <- paste0("panelvar", i)
+    if (is_reverse(xscale)) {
+      domain$left  <- -domain$left
+      domain$right <- -domain$right
+    }
+    if (is_reverse(yscale)) {
+      domain$top    <- -domain$top
+      domain$bottom <- -domain$bottom
+    }
 
-        mapping[[vname]] <- var_name
-        panel_vars[[vname]] <- l[[var_name]]
-      }
+    domain
+  }
 
-      list(
-        panel   = l$PANEL,
-        row     = l$ROW,
-        col     = l$COL,
-        panel_vars = panel_vars,
-        scale_x = scale_x,
-        scale_y = scale_x,
-        log     = check_log_scales(b, scale_x, scale_y),
-        domain  = find_panel_domain(b, l$PANEL, scale_x, scale_y),
-        mapping = mapping
-      )
-    })
+  # Rename the items in vars to have names like panelvar1, panelvar2.
+  rename_panel_vars <- function(vars) {
+    for (i in seq_along(vars)) {
+      names(vars)[i] <- paste0("panelvar", i)
+    }
+    vars
+  }
+
+  get_mappings <- function(layers, layout, coord) {
+    # For simplicity, we'll just use the mapping from the first layer of the
+    # ggplot object. The original uses quoted expressions; convert to
+    # character.
+    mapping <- layers$mapping[[1]]
+    # lapply'ing as.character results in unexpected behavior for expressions
+    # like `wt/2`; deparse handles it correctly.
+    mapping <- lapply(mapping, deparse)
+
+    # If either x or y is not present, give it a NULL entry.
+    mapping <- mergeVectors(list(x = NULL, y = NULL), mapping)
+
+    # The names (not values) of panel vars are the same across all panels,
+    # so just look at the first one. Also, the order of panel vars needs
+    # to be reversed.
+    vars <- rev(layout$vars[[1]])
+    for (i in seq_along(vars)) {
+      mapping[[paste0("panelvar", i)]] <- names(vars)[i]
+    }
+
+    if (isTRUE(coord$flip)) {
+      mapping[c("x", "y")] <- mapping[c("y", "x")]
+    }
+
+    mapping
+  }
+
+  # Mapping is constant across all panels, so get it here and reuse later.
+  mapping <- get_mappings(layers, layout, coord)
+
+  # If coord_flip is used, these need to be swapped
+  flip_xy <- function(layout) {
+    l <- layout
+    l$xscale <- layout$yscale
+    l$yscale <- layout$xscale
+    l$xmin <- layout$ymin
+    l$xmax <- layout$ymax
+    l$ymin <- layout$xmin
+    l$ymax <- layout$xmax
+    l
+  }
+  if (coord$flip) {
+    layout <- flip_xy(layout)
   }
 
+  # Iterate over each row in the layout data frame
+  lapply(seq_len(nrow(layout)), function(i) {
+    # Slice out one row, use it as a list. The (former) list-cols are still
+    # in lists, so we need to unwrap them.
+    l <- as.list(layout[i, ])
+    l$vars   <- l$vars[[1]]
+    l$xscale <- l$xscale[[1]]
+    l$yscale <- l$yscale[[1]]
+
+    list(
+      panel   = as.numeric(l$panel),
+      row     = l$row,
+      col     = l$col,
+      # Rename panel vars. They must also be in reversed order.
+      panel_vars = rename_panel_vars(rev(l$vars)),
+      log     = get_log_bases(l$xscale, l$yscale, coord),
+      domain  = get_domain(l$xmin, l$xmax, l$ymin, l$ymax, l$xscale, l$yscale),
+      mapping = mapping
+    )
+  })
+}
+
+
+# This is for ggplot2<=2.2.1, before an API was introduced for extracting
+# information about the plot object. The "old" format was used before 2.1.0.
+# The "new" format was used after 2.1.0, up to 2.2.1. The reason these two
+# formats are mixed together in a single function is historical, and it's not
+# worthwhile to separate them at this point.
+find_panel_info_non_api <- function(b, ggplot_format) {
   # Given a single range object (representing the data domain) from a built
   # ggplot object, return the domain.
   find_panel_domain <- function(b, panel_num, scalex_num = 1, scaley_num = 1) {
-    if (new_ggplot) {
+    if (ggplot_format == "new") {
       range <- b$layout$panel_ranges[[panel_num]]
     } else {
       range <- b$panel$ranges[[panel_num]]
@@ -505,7 +624,7 @@ getGgplotCoordmap <- function(p, pixelratio, res) {
     )
 
     # Check for reversed scales
-    if (new_ggplot) {
+    if (ggplot_format == "new") {
       xscale <- b$layout$panel_scales$x[[scalex_num]]
       yscale <- b$layout$panel_scales$y[[scaley_num]]
     } else {
@@ -546,7 +665,7 @@ getGgplotCoordmap <- function(p, pixelratio, res) {
     y_names <- character(0)
 
     # Continuous scales have a trans; discrete ones don't
-    if (new_ggplot) {
+    if (ggplot_format == "new") {
       if (!is.null(b$layout$panel_scales$x[[scalex_num]]$trans))
         x_names <- b$layout$panel_scales$x[[scalex_num]]$trans$name
       if (!is.null(b$layout$panel_scales$y[[scaley_num]]$trans))
@@ -620,129 +739,220 @@ getGgplotCoordmap <- function(p, pixelratio, res) {
     mappings
   }
 
-  # Given a gtable object, return the x and y ranges (in pixel dimensions)
-  find_panel_ranges <- function(g, pixelratio) {
-    # Given a vector of unit objects, return logical vector indicating which ones
-    # are "null" units. These units use the remaining available width/height --
-    # that is, the space not occupied by elements that have an absolute size.
-    is_null_unit <- function(x) {
-      # A vector of units can be either a list of individual units (a unit.list
-      # object), each with their own set of attributes, or an atomic vector with
-      # one set of attributes. ggplot2 switched from the former (in version
-      # 1.0.1) to the latter. We need to make sure that we get the correct
-      # result in both cases.
-      if (inherits(x, "unit.list")) {
-        # For ggplot2 <= 1.0.1
-        vapply(x, FUN.VALUE = logical(1), function(u) {
-          isTRUE(attr(u, "unit", exact = TRUE) == "null")
-        })
-      } else {
-        # For later versions of ggplot2
-        attr(x, "unit", exact = TRUE) == "null"
-      }
+  if (ggplot_format == "new") {
+    layout <- b$layout$panel_layout
+  } else {
+    layout <- b$panel$layout
+  }
+  # Convert factor to numbers
+  layout$PANEL <- as.integer(as.character(layout$PANEL))
+
+  # Names of facets
+  facet_vars <- NULL
+  if (ggplot_format == "new") {
+    facet <- b$layout$facet
+    if (inherits(facet, "FacetGrid")) {
+      facet_vars <- vapply(c(facet$params$cols, facet$params$rows), as.character, character(1))
+    } else if (inherits(facet, "FacetWrap")) {
+      facet_vars <- vapply(facet$params$facets, as.character, character(1))
     }
-
-    # Workaround for a bug in the quartz device. If you have a 400x400 image and
-    # run `convertWidth(unit(1, "npc"), "native")`, the result will depend on
-    # res setting of the device. If res=72, then it returns 400 (as expected),
-    # but if, e.g., res=96, it will return 300, which is incorrect.
-    devScaleFactor <- 1
-    if (grepl("quartz", names(grDevices::dev.cur()), fixed = TRUE)) {
-      devScaleFactor <- res / 72
+  } else {
+    facet <- b$plot$facet
+    if (inherits(facet, "grid")) {
+      facet_vars <- vapply(c(facet$cols, facet$rows), as.character, character(1))
+    } else if (inherits(facet, "wrap")) {
+      facet_vars <- vapply(facet$facets, as.character, character(1))
     }
+  }
 
-    # Convert a unit (or vector of units) to a numeric vector of pixel sizes
-    h_px <- function(x) {
-      devScaleFactor * grid::convertHeight(x, "native", valueOnly = TRUE)
-    }
-    w_px <- function(x) {
-      devScaleFactor * grid::convertWidth(x, "native", valueOnly = TRUE)
-    }
+  # Iterate over each row in the layout data frame
+  lapply(seq_len(nrow(layout)), function(i) {
+    # Slice out one row
+    l <- layout[i, ]
 
-    # Given a vector of relative sizes (in grid units), and a function for
-    # converting grid units to numeric pixels, return a numeric vector of
-    # pixel sizes.
-    find_px_sizes <- function(rel_sizes, unit_to_px) {
-      # Total pixels (in height or width)
-      total_px <- unit_to_px(grid::unit(1, "npc"))
-      # Calculate size of all panel(s) together. Panels (and only panels) have
-      # null size.
-      null_idx <- is_null_unit(rel_sizes)
-      # All the absolute heights. At this point, null heights are 0. We need to
-      # calculate them separately and add them in later.
-      px_sizes <- unit_to_px(rel_sizes)
-      # Total size for panels is image size minus absolute (non-panel) elements
-      panel_px_total <- total_px - sum(px_sizes)
-      # Divide up the total panel size up into the panels (scaled by size)
-      panel_sizes_rel <- as.numeric(rel_sizes[null_idx])
-      panel_sizes_rel <- panel_sizes_rel / sum(panel_sizes_rel)
-      px_sizes[null_idx] <- panel_px_total * panel_sizes_rel
-      abs(px_sizes)
+    scale_x <- l$SCALE_X
+    scale_y <- l$SCALE_Y
+
+    mapping <- find_plot_mappings(b)
+
+    # For each of the faceting variables, get the value of that variable in
+    # the current panel. Default to empty _named_ list so that it's sent as a
+    # JSON object, not array.
+    panel_vars <- list(a = NULL)[0]
+    for (i in seq_along(facet_vars)) {
+      var_name <- facet_vars[[i]]
+      vname <- paste0("panelvar", i)
+
+      mapping[[vname]] <- var_name
+      panel_vars[[vname]] <- l[[var_name]]
     }
 
-    px_heights <- find_px_sizes(g$heights, h_px)
-    px_widths <- find_px_sizes(g$widths, w_px)
-
-    # Convert to absolute pixel positions
-    x_pos <- cumsum(px_widths)
-    y_pos <- cumsum(px_heights)
-
-    # Match up the pixel dimensions to panels
-    layout <- g$layout
-    # For panels:
-    # * For facet_wrap, they'll be named "panel-1", "panel-2", etc.
-    # * For no facet or facet_grid, they'll just be named "panel". For
-    #   facet_grid, we need to re-order the layout table. Assume that panel
-    #   numbers go from left to right, then next row.
-    # Assign a number to each panel, corresponding to PANEl in the built ggplot
-    # object.
-    layout <- layout[grepl("^panel", layout$name), ]
-    layout <- layout[order(layout$t, layout$l), ]
-    layout$panel <- seq_len(nrow(layout))
-
-    # When using a HiDPI client on a Linux server, the pixel
-    # dimensions are doubled, so we have to divide the dimensions by
-    # `pixelratio`. When a HiDPI client is used on a Mac server (with
-    # the quartz device), the pixel dimensions _aren't_ doubled, even though
-    # the image has double size. In the latter case we don't have to scale the
-    # numbers down.
-    pix_ratio <- 1
-    if (!grepl("^quartz", names(grDevices::dev.cur()))) {
-      pix_ratio <- pixelratio
+    list(
+      panel   = l$PANEL,
+      row     = l$ROW,
+      col     = l$COL,
+      panel_vars = panel_vars,
+      scale_x = scale_x,
+      scale_y = scale_x,
+      log     = check_log_scales(b, scale_x, scale_y),
+      domain  = find_panel_domain(b, l$PANEL, scale_x, scale_y),
+      mapping = mapping
+    )
+  })
+}
+
+
+# Given a gtable object, return the x and y ranges (in pixel dimensions)
+find_panel_ranges <- function(g, pixelratio, res) {
+  # Given a vector of unit objects, return logical vector indicating which ones
+  # are "null" units. These units use the remaining available width/height --
+  # that is, the space not occupied by elements that have an absolute size.
+  is_null_unit <- function(x) {
+    # A vector of units can be either a list of individual units (a unit.list
+    # object), each with their own set of attributes, or an atomic vector with
+    # one set of attributes. ggplot2 switched from the former (in version
+    # 1.0.1) to the latter. We need to make sure that we get the correct
+    # result in both cases.
+    if (inherits(x, "unit.list")) {
+      # For ggplot2 <= 1.0.1
+      vapply(x, FUN.VALUE = logical(1), function(u) {
+        isTRUE(attr(u, "unit", exact = TRUE) == "null")
+      })
+    } else {
+      # For later versions of ggplot2
+      attr(x, "unit", exact = TRUE) == "null"
     }
+  }
 
-    # Return list of lists, where each inner list has left, right, top, bottom
-    # values for a panel
-    lapply(seq_len(nrow(layout)), function(i) {
-      p <- layout[i, , drop = FALSE]
-      list(
-        left   = x_pos[p$l - 1] / pix_ratio,
-        right  = x_pos[p$r] / pix_ratio,
-        bottom = y_pos[p$b] / pix_ratio,
-        top    = y_pos[p$t - 1] / pix_ratio
-      )
-    })
+  # Workaround for a bug in the quartz device. If you have a 400x400 image and
+  # run `convertWidth(unit(1, "npc"), "native")`, the result will depend on
+  # res setting of the device. If res=72, then it returns 400 (as expected),
+  # but if, e.g., res=96, it will return 300, which is incorrect.
+  devScaleFactor <- 1
+  if (grepl("quartz", names(grDevices::dev.cur()), fixed = TRUE)) {
+    devScaleFactor <- res / 72
   }
 
+  # Convert a unit (or vector of units) to a numeric vector of pixel sizes
+  h_px <- function(x) {
+    devScaleFactor * grid::convertHeight(x, "native", valueOnly = TRUE)
+  }
+  w_px <- function(x) {
+    devScaleFactor * grid::convertWidth(x, "native", valueOnly = TRUE)
+  }
 
-  tryCatch({
-    # Get info from built ggplot object
-    info <- find_panel_info(p$build)
+  # Given a vector of relative sizes (in grid units), and a function for
+  # converting grid units to numeric pixels, return a list with: known pixel
+  # dimensions, scalable dimensions, and the overall space for the scalable
+  # objects.
+  find_size_info <- function(rel_sizes, unit_to_px) {
+    # Total pixels (in height or width)
+    total_px <- unit_to_px(grid::unit(1, "npc"))
+    # Calculate size of all panel(s) together. Panels (and only panels) have
+    # null size.
+    null_idx <- is_null_unit(rel_sizes)
+
+    # All the absolute heights. At this point, null heights are 0. We need to
+    # calculate them separately and add them in later.
+    px_sizes <- unit_to_px(rel_sizes)
+    # Mark the null heights as NA.
+    px_sizes[null_idx] <- NA_real_
+
+    # The plotting panels all are 'null' units.
+    null_sizes <- rep(NA_real_, length(rel_sizes))
+    null_sizes[null_idx] <- as.numeric(rel_sizes[null_idx])
+
+    # Total size allocated for panels is the total image size minus absolute
+    # (non-panel) elements.
+    panel_px_total <- total_px - sum(px_sizes, na.rm = TRUE)
+
+    # Size of a 1null unit
+    null_px <- abs(panel_px_total / sum(null_sizes, na.rm = TRUE))
+
+    # This returned list contains:
+    # * px_sizes: A vector of known pixel dimensions. The values that were
+    #   null units will be assigned NA. The null units are ones that scale
+    #   when the plotting area is resized.
+    # * null_sizes: A vector of the null units. All others will be assigned
+    #   NA. The null units often are 1, but they may be any value, especially
+    #   when using coord_fixed.
+    # * null_px: The size (in pixels) of a 1null unit.
+    # * null_px_scaled: The size (in pixels) of a 1null unit when scaled to
+    #   fit a smaller dimension (used for plots with coord_fixed).
+    list(
+      px_sizes       = abs(px_sizes),
+      null_sizes     = null_sizes,
+      null_px        = null_px,
+      null_px_scaled = null_px
+    )
+  }
 
-    # Get ranges from gtable - it's possible for this to return more elements than
-    # info, because it calculates positions even for panels that aren't present.
-    # This can happen with facet_wrap.
-    ranges <- find_panel_ranges(p$gtable, pixelratio)
+  # Given a size_info, return absolute pixel positions
+  size_info_to_px <- function(info) {
+    px_sizes <- info$px_sizes
 
-    for (i in seq_along(info)) {
-      info[[i]]$range <- ranges[[i]]
-    }
+    null_idx <- !is.na(info$null_sizes)
+    px_sizes[null_idx] <- info$null_sizes[null_idx] * info$null_px_scaled
 
-    return(info)
+    # If this direction is scaled down because of coord_fixed, we need to add an
+    # offset so that the pixel locations are centered.
+    offset <- (info$null_px - info$null_px_scaled) *
+              sum(info$null_sizes, na.rm = TRUE) / 2
 
-  }, error = function(e) {
-    # If there was an error extracting info from the ggplot object, just return
-    # a list with the error message.
-    return(structure(list(), error = e$message))
+    # Get absolute pixel positions
+    cumsum(px_sizes) + offset
+  }
+
+  heights_info <- find_size_info(g$heights, h_px)
+  widths_info  <- find_size_info(g$widths,  w_px)
+
+  if (g$respect) {
+    # This is a plot with coord_fixed. The grid 'respect' option means to use
+    # the same pixel value for 1null, for width and height. We want the
+    # smaller of the two values -- that's what makes the plot fit in the
+    # viewport.
+    null_px_min <- min(heights_info$null_px, widths_info$null_px)
+    heights_info$null_px_scaled <- null_px_min
+    widths_info$null_px_scaled  <- null_px_min
+  }
+
+  # Convert to absolute pixel positions
+  y_pos <- size_info_to_px(heights_info)
+  x_pos <- size_info_to_px(widths_info)
+
+  # Match up the pixel dimensions to panels
+  layout <- g$layout
+  # For panels:
+  # * For facet_wrap, they'll be named "panel-1", "panel-2", etc.
+  # * For no facet or facet_grid, they'll just be named "panel". For
+  #   facet_grid, we need to re-order the layout table. Assume that panel
+  #   numbers go from left to right, then next row.
+  # Assign a number to each panel, corresponding to PANEl in the built ggplot
+  # object.
+  layout <- layout[grepl("^panel", layout$name), ]
+  layout <- layout[order(layout$t, layout$l), ]
+  layout$panel <- seq_len(nrow(layout))
+
+  # When using a HiDPI client on a Linux server, the pixel
+  # dimensions are doubled, so we have to divide the dimensions by
+  # `pixelratio`. When a HiDPI client is used on a Mac server (with
+  # the quartz device), the pixel dimensions _aren't_ doubled, even though
+  # the image has double size. In the latter case we don't have to scale the
+  # numbers down.
+  pix_ratio <- 1
+  if (!grepl("^quartz", names(grDevices::dev.cur()))) {
+    pix_ratio <- pixelratio
+  }
+
+  # Return list of lists, where each inner list has left, right, top, bottom
+  # values for a panel
+  lapply(seq_len(nrow(layout)), function(i) {
+    p <- layout[i, , drop = FALSE]
+    list(
+      left   = x_pos[p$l - 1] / pix_ratio,
+      right  = x_pos[p$r] / pix_ratio,
+      bottom = y_pos[p$b] / pix_ratio,
+      top    = y_pos[p$t - 1] / pix_ratio
+    )
   })
 }
diff --git a/R/render-table.R b/R/render-table.R
index 1d3128d..dcceb5c 100644
--- a/R/render-table.R
+++ b/R/render-table.R
@@ -176,7 +176,12 @@ renderTable <- function(expr, striped = FALSE, hover = FALSE,
           else ""
         }, " ",
         "class = '", htmlEscape(classNames, TRUE), "' ",
-        "style = 'width:", validateCssUnit(width), ";'"))
+        "style = 'width:", validateCssUnit(width), ";'"),
+      comment = {
+        if ("comment" %in% names(dots)) dots$comment
+        else FALSE
+      }
+    )
 
     print_args <- c(print_args, non_xtable_args)
     print_args <- print_args[unique(names(print_args))]
diff --git a/R/serializers.R b/R/serializers.R
index 1fdb8b0..35f3b46 100644
--- a/R/serializers.R
+++ b/R/serializers.R
@@ -1,3 +1,22 @@
+#' Add a function for serializing an input before bookmarking application state
+#'
+#' @param inputId Name of the input value.
+#' @param fun A function that takes the input value and returns a modified
+#'   value. The returned value will be used for the test snapshot.
+#' @param session A Shiny session object.
+#'
+#' @keywords internal
+#' @export
+setSerializer <- function(inputId, fun, session = getDefaultReactiveDomain()) {
+  if (is.null(session)) {
+    stop("setSerializer() needs a session object.")
+  }
+
+  input_impl <- .subset2(session$input, "impl")
+  input_impl$setMeta(inputId, "shiny.serializer", fun)
+}
+
+
 # For most types of values, simply return the value unchanged.
 serializerDefault <- function(value, stateDir) {
   value
@@ -58,12 +77,12 @@ serializeReactiveValues <- function(values, exclude, stateDir = NULL) {
 
     # Get the serializer function for this input value. If none specified, use
     # the default.
-    serializer <- impl$getMeta(name, "shiny.serializer")
-    if (is.null(serializer))
-      serializer <- serializerDefault
+    serializer_fun <- impl$getMeta(name, "shiny.serializer")
+    if (is.null(serializer_fun))
+      serializer_fun <- serializerDefault
 
     # Apply serializer function.
-    serializer(val, stateDir)
+    serializer_fun(val, stateDir)
   })
 
   # Filter out any values that were marked as unserializable.
diff --git a/R/server-input-handlers.R b/R/server-input-handlers.R
index 0d7865b..cc602e5 100644
--- a/R/server-input-handlers.R
+++ b/R/server-input-handlers.R
@@ -148,7 +148,7 @@ registerInputHandler("shiny.number", function(val, ...){
 
 registerInputHandler("shiny.password", function(val, shinysession, name) {
   # Mark passwords as not serializable
-  .subset2(shinysession$input, "impl")$setMeta(name, "shiny.serializer", serializerUnserializable)
+  setSerializer(name, serializerUnserializable)
   val
 })
 
@@ -214,7 +214,9 @@ registerInputHandler("shiny.file", function(val, shinysession, name) {
   # Need to mark this input value with the correct serializer. When a file is
   # uploaded the usual way (instead of being restored), this occurs in
   # session$`@uploadEnd`.
-  .subset2(shinysession$input, "impl")$setMeta(name, "shiny.serializer", serializerFileInput)
+  setSerializer(name, serializerFileInput)
+
+  snapshotPreprocessInput(name, snapshotPreprocessorFileInput)
 
   val
 })
diff --git a/R/server.R b/R/server.R
index fcf52b6..3e2cb93 100644
--- a/R/server.R
+++ b/R/server.R
@@ -155,7 +155,7 @@ decodeMessage <- function(data) {
     # Treat message as UTF-8
     charData <- rawToChar(data)
     Encoding(charData) <- 'UTF-8'
-    return(jsonlite::fromJSON(charData, simplifyVector=FALSE))
+    return(safeFromJSON(charData, simplifyVector=FALSE))
   }
 
   i <- 5
@@ -226,7 +226,7 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
               message("RECV ", rawToChar(msg))
           }
 
-          if (identical(charToRaw("\003\xe9"), msg))
+          if (isEmptyMessage(msg))
             return()
 
           msg <- decodeMessage(msg)
@@ -370,9 +370,9 @@ argsForServerFunc <- function(serverFunc, session) {
 }
 
 getEffectiveBody <- function(func) {
-  # Note: NULL values are OK. isS4(NULL) returns FALSE, body(NULL)
-  # returns NULL.
-  if (isS4(func) && class(func) == "functionWithTrace")
+  if (is.null(func))
+    NULL
+  else if (isS4(func) && class(func) == "functionWithTrace")
     body(func at original)
   else
     body(func)
@@ -465,6 +465,17 @@ serviceApp <- function() {
 # Global flag that's TRUE whenever we're inside of the scope of a call to runApp
 .globals$running <- FALSE
 
+#' Check whether a Shiny application is running
+#'
+#' This function tests whether a Shiny application is currently running.
+#'
+#' @return \code{TRUE} if a Shiny application is currently running. Otherwise,
+#'   \code{FALSE}.
+#' @export
+isRunning <- function() {
+  .globals$running
+}
+
 #' Run Shiny Application
 #'
 #' Runs a Shiny application. This function normally does not return; interrupt R
@@ -561,7 +572,7 @@ runApp <- function(appDir=getwd(),
   }, add = TRUE)
 
   if (.globals$running) {
-    stop("Can't call `runApp()` from within `runApp()`. If your ,",
+    stop("Can't call `runApp()` from within `runApp()`. If your ",
          "application code contains `runApp()`, please remove it.")
   }
   .globals$running <- TRUE
@@ -577,7 +588,11 @@ runApp <- function(appDir=getwd(),
 
   # Make warnings print immediately
   # Set pool.scheduler to support pool package
-  ops <- options(warn = 1, pool.scheduler = scheduleTask)
+  ops <- options(
+    # Raise warn level to 1, but don't lower it
+    warn = max(1, getOption("warn", default = 1)),
+    pool.scheduler = scheduleTask
+  )
   on.exit(options(ops), add = TRUE)
 
   appParts <- as.shiny.appobj(appDir)
@@ -732,15 +747,22 @@ runApp <- function(appDir=getwd(),
     }
   }
 
+  # Invoke user-defined onStop callbacks, before the application's internal
+  # onStop callbacks.
+  on.exit({
+    .globals$onStopCallbacks$invoke()
+    .globals$onStopCallbacks <- Callbacks$new()
+  }, add = TRUE)
+
   # Extract appOptions (which is a list) and store them as shinyOptions, for
   # this app. (This is the only place we have to store settings that are
   # accessible both the UI and server portion of the app.)
   unconsumeAppOptions(appParts$appOptions)
 
-  # Set up the onEnd before we call onStart, so that it gets called even if an
+  # Set up the onStop before we call onStart, so that it gets called even if an
   # error happens in onStart.
-  if (!is.null(appParts$onEnd))
-    on.exit(appParts$onEnd(), add = TRUE)
+  if (!is.null(appParts$onStop))
+    on.exit(appParts$onStop(), add = TRUE)
   if (!is.null(appParts$onStart))
     appParts$onStart()
 
@@ -1022,3 +1044,9 @@ browserViewer <- function(browser = getOption("browser")) {
 inShinyServer <- function() {
   nzchar(Sys.getenv('SHINY_PORT'))
 }
+
+# This check was moved out of the main function body because of an issue with
+# the RStudio debugger. (#1474)
+isEmptyMessage <- function(msg) {
+  identical(charToRaw("\003\xe9"), msg)
+}
diff --git a/R/shiny.R b/R/shiny.R
index f30bd1a..2bfbc3e 100644
--- a/R/shiny.R
+++ b/R/shiny.R
@@ -5,7 +5,7 @@ NULL
 #'
 #' 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
+#' prebuilt widgets make it possible to build beautiful, responsive, and
 #' powerful applications with minimal effort.
 #'
 #' The Shiny tutorial at \url{http://shiny.rstudio.com/tutorial/} explains
@@ -142,6 +142,15 @@ toJSON <- function(x, ...,  dataframe = "columns", null = "null", na = "null",
    keep_vec_names = keep_vec_names, json_verbatim = TRUE, ...)
 }
 
+# If the input to jsonlite::fromJSON is not valid JSON, it will try to fetch a
+# URL or read a file from disk. We don't want to allow that.
+safeFromJSON <- function(txt, ...) {
+  if (!jsonlite::validate(txt)) {
+    stop("Argument 'txt' is not a valid JSON string.")
+  }
+  jsonlite::fromJSON(txt, ...)
+}
+
 # Call the workerId func with no args to get the worker id, and with an arg to
 # set it.
 #
@@ -201,12 +210,13 @@ workerId <- local({
 #'     }
 #'     \item{\code{singletons} - for internal use}
 #'     \item{\code{url_protocol}, \code{url_hostname}, \code{url_port},
-#'       \code{url_pathname}, \code{url_search}, and \code{url_hash_initial}
-#'       can be used to get the components of the URL that was requested by the
-#'       browser to load the Shiny app page. These values are from the
-#'       browser's perspective, so neither HTTP proxies nor Shiny Server will
-#'       affect these values. The \code{url_search} value may be used with
-#'       \code{\link{parseQueryString}} to access query string parameters.
+#'       \code{url_pathname}, \code{url_search}, \code{url_hash_initial}
+#'       and \code{url_hash} can be used to get the components of the URL
+#'       that was requested by the browser to load the Shiny app page.
+#'       These values are from the browser's perspective, so neither HTTP
+#'       proxies nor Shiny Server will affect these values. The
+#'       \code{url_search} value may be used with \code{\link{parseQueryString}}
+#'       to access query string parameters.
 #'     }
 #'   }
 #'   \code{clientData} also contains information about each output.
@@ -374,12 +384,24 @@ NULL
 #' @seealso \url{http://shiny.rstudio.com/articles/modules.html}
 #' @export
 NS <- function(namespace, id = NULL) {
+  if (length(namespace) == 0)
+    ns_prefix <- character(0)
+  else
+    ns_prefix <- paste(namespace, collapse = ns.sep)
+
+  f <- function(id) {
+    if (length(id) == 0)
+      return(ns_prefix)
+    if (length(ns_prefix) == 0)
+      return(id)
+
+    paste(ns_prefix, id, sep = ns.sep)
+  }
+
   if (missing(id)) {
-    function(id) {
-      paste(c(namespace, id), collapse = ns.sep)
-    }
+    f
   } else {
-    paste(c(namespace, id), collapse = ns.sep)
+    f(id)
   }
 }
 
@@ -415,6 +437,7 @@ ShinySession <- R6Class(
     restoreCallbacks = 'Callbacks',
     restoredCallbacks = 'Callbacks',
     bookmarkExclude = character(0),  # Names of inputs to exclude from bookmarking
+    getBookmarkExcludeFuns = list(),
 
     testMode = FALSE,                # Are we running in test mode?
     testExportExprs = list(),
@@ -579,6 +602,16 @@ ShinySession <- R6Class(
       }) # withReactiveDomain
     },
 
+    # Modules (scopes) call this to register a function that returns a vector
+    # of names to exclude from bookmarking. The function should return
+    # something like c("scope1-x", "scope1-y"). This doesn't use a Callback
+    # object because the return values of the functions are needed, but
+    # Callback$invoke() discards return values.
+    registerBookmarkExclude = function(fun) {
+      len <- length(private$getBookmarkExcludeFuns) + 1
+      private$getBookmarkExcludeFuns[[len]] <- fun
+    },
+
     # Save output values and errors. This is only used for testing mode.
     storeOutputValues = function(values = NULL) {
       private$outputValues <- mergeVectors(private$outputValues, values)
@@ -615,6 +648,15 @@ ShinySession <- R6Class(
               values$input <- allInputs[items]
             }
 
+            # Apply preprocessor functions for inputs that have them.
+            values$input <- lapply(
+              setNames(names(values$input), names(values$input)),
+              function(name) {
+                preprocess <- private$getSnapshotPreprocessInput(name)
+                preprocess(values$input[[name]])
+              }
+            )
+
             values$input <- sortByName(values$input)
           }
 
@@ -628,6 +670,21 @@ ShinySession <- R6Class(
               values$output <- private$outputValues[items]
             }
 
+            # Filter out those outputs that have the snapshotExclude attribute.
+            exclude_idx <- vapply(names(values$output), function(name) {
+              isTRUE(attr(private$.outputs[[name]], "snapshotExclude", TRUE))
+            }, logical(1))
+            values$output <- values$output[!exclude_idx]
+
+            # Apply snapshotPreprocess functions for outputs that have them.
+            values$output <- lapply(
+              setNames(names(values$output), names(values$output)),
+              function(name) {
+                preprocess <- private$getSnapshotPreprocessOutput(name)
+                preprocess(values$output[[name]])
+              }
+            )
+
             values$output <- sortByName(values$output)
           }
 
@@ -682,6 +739,20 @@ ShinySession <- R6Class(
           }
         }
       )
+    },
+
+    # Get the snapshotPreprocessOutput function for an output name. If no preprocess
+    # function has been set, return the identity function.
+    getSnapshotPreprocessOutput = function(name) {
+      fun <- attr(private$.outputs[[name]], "snapshotPreprocess", exact = TRUE)
+      fun %OR% identity
+    },
+
+    # Get the snapshotPreprocessInput function for an input name. If no preprocess
+    # function has been set, return the identity function.
+    getSnapshotPreprocessInput = function(name) {
+      fun <- private$.input$getMeta(name, "shiny.snapshot.preprocess")
+      fun %OR% identity
     }
   ),
   public = list(
@@ -743,7 +814,7 @@ ShinySession <- R6Class(
 
       if (!is.null(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)) {
         try({
-          creds <- jsonlite::fromJSON(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)
+          creds <- safeFromJSON(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)
           self$user <- creds$user
           self$groups <- creds$groups
         }, silent=FALSE)
@@ -757,7 +828,8 @@ ShinySession <- R6Class(
       private$sendMessage(
         config = list(
           workerId = workerId(),
-          sessionId = self$token
+          sessionId = self$token,
+          user = self$user
         )
       )
     },
@@ -825,7 +897,7 @@ ShinySession <- R6Class(
           if (anyUnnamed(dots))
             stop("exportTestValues: all arguments must be named.")
 
-          names(dots) <- vapply(names(dots), ns, character(1))
+          names(dots) <- ns(names(dots))
 
           do.call(
             .subset2(self, "exportTestValues"),
@@ -939,6 +1011,12 @@ ShinySession <- R6Class(
         restoredCallbacks$invoke(scopeState)
       })
 
+      # Returns the excluded names with the scope's ns prefix on them.
+      private$registerBookmarkExclude(function() {
+        excluded <- scope$getBookmarkExclude()
+        ns(excluded)
+      })
+
       scope
     },
     ns = function(id) {
@@ -1022,6 +1100,10 @@ ShinySession <- R6Class(
       }
 
       if (is.function(func)) {
+        # Extract any output attributes attached to the render function. These
+        # will be attached to the observer after it's created.
+        outputAttrs <- attr(func, "outputAttrs", TRUE)
+
         funcFormals <- formals(func)
         # ..stacktraceon matches with the top-level ..stacktraceoff.., because
         # the observer we set up below has ..stacktraceon=FALSE
@@ -1099,6 +1181,12 @@ ShinySession <- R6Class(
             private$invalidatedOutputValues$set(name, value)
         }, suspended=private$shouldSuspend(name), label=label)
 
+        # If any output attributes were added to the render function attach
+        # them to observer.
+        lapply(names(outputAttrs), function(name) {
+          attr(obs, name) <- outputAttrs[[name]]
+        })
+
         obs$onInvalidate(function() {
           self$showProgress(name)
         })
@@ -1145,6 +1233,13 @@ ShinySession <- R6Class(
       })
 
       if (!hasPendingUpdates()) {
+        # Normally, if there are no updates, simply return without sending
+        # anything to the client. But if we are in test mode, we still want to
+        # send a message with blank `values`, so that the client knows that
+        # any changed inputs have been received by the server and processed.
+        if (isTRUE(private$testMode)) {
+          private$sendMessage( values = list() )
+        }
         return(invisible())
       }
 
@@ -1260,8 +1355,12 @@ ShinySession <- R6Class(
       private$bookmarkExclude <- names
     },
     getBookmarkExclude = function() {
-      private$bookmarkExclude
+      scopedExcludes <- lapply(private$getBookmarkExcludeFuns, function(f) f())
+      scopedExcludes <- unlist(scopedExcludes)
+
+      c(private$bookmarkExclude, scopedExcludes)
     },
+
     onBookmark = function(fun) {
       if (!is.function(fun) || length(fun) != 1) {
         stop("`fun` must be a function that takes one argument")
@@ -1402,8 +1501,40 @@ ShinySession <- R6Class(
         )
       )
     },
-    updateQueryString = function(queryString) {
-      private$sendMessage(updateQueryString = list(queryString = queryString))
+    sendInsertTab = function(inputId, liTag, divTag, menuName,
+                             target, position, select) {
+      private$sendMessage(
+        `shiny-insert-tab` = list(
+          inputId = inputId,
+          liTag = liTag,
+          divTag = divTag,
+          menuName = menuName,
+          target = target,
+          position = position,
+          select = select
+        )
+      )
+    },
+    sendRemoveTab = function(inputId, target) {
+      private$sendMessage(
+        `shiny-remove-tab` = list(
+          inputId = inputId,
+          target = target
+        )
+      )
+    },
+    sendChangeTabVisibility = function(inputId, target, type) {
+      private$sendMessage(
+        `shiny-change-tab-visibility` = list(
+          inputId = inputId,
+          target = target,
+          type = type
+        )
+      )
+    },
+    updateQueryString = function(queryString, mode) {
+      private$sendMessage(updateQueryString = list(
+        queryString = queryString, mode = mode))
     },
     resetBrush = function(brushId) {
       private$sendMessage(
@@ -1439,7 +1570,8 @@ ShinySession <- R6Class(
       fileData <- private$fileUploadContext$getUploadOperation(jobId)$finish()
       private$.input$set(inputId, fileData)
 
-      private$.input$setMeta(inputId, "shiny.serializer", serializerFileInput)
+      setSerializer(inputId, serializerFileInput)
+      snapshotPreprocessInput(inputId, snapshotPreprocessorFileInput)
 
       invisible()
     },
@@ -1482,9 +1614,30 @@ ShinySession <- R6Class(
         }
       }
 
+      # @description Only applicable to files uploaded via IE. When possible,
+      #   adds the appropriate extension to temporary files created by
+      #   \code{mime::parse_multipart}.
+      # @param multipart A named list as returned by
+      #   \code{mime::parse_multipart}
+      # @return A named list with datapath updated to point to the new location
+      #   of the file, if an extension was added.
+      maybeMoveIEUpload <- function(multipart) {
+        if (is.null(multipart)) return(NULL)
+
+        lapply(multipart, function(input) {
+          oldPath <- input$datapath
+          newPath <- paste0(oldPath, maybeGetExtension(input$name))
+          if (oldPath != newPath) {
+            file.rename(oldPath, newPath)
+            input$datapath <- newPath
+          }
+          input
+        })
+      }
+
       if (matches[2] == 'uploadie' && identical(req$REQUEST_METHOD, "POST")) {
         id <- URLdecode(matches[3])
-        res <- mime::parse_multipart(req)
+        res <- maybeMoveIEUpload(mime::parse_multipart(req))
         private$.input$set(id, res[[id]])
         return(httpResponse(200, 'text/plain', 'OK'))
       }
@@ -1809,7 +1962,6 @@ outputOptions <- function(x, name, ...) {
   .subset2(x, 'impl')$outputOptions(name, ...)
 }
 
-
 #' Add callbacks for Shiny session events
 #'
 #' These functions are for registering callbacks on Shiny session events.
@@ -1841,6 +1993,9 @@ onFlushed <- function(fun, once = TRUE, session = getDefaultReactiveDomain()) {
 }
 
 #' @rdname onFlush
+#'
+#' @seealso \code{\link{onStop}()} for registering callbacks that will be
+#'   invoked when the application exits, or when a session ends.
 #' @export
 onSessionEnded <- function(fun, session = getDefaultReactiveDomain()) {
   session$onSessionEnded(fun)
@@ -1865,3 +2020,86 @@ flushAllSessions <- function() {
     NULL
   })
 }
+
+.globals$onStopCallbacks <- Callbacks$new()
+
+#' Run code after an application or session ends
+#'
+#' This function registers callback functions that are invoked when the
+#' application exits (when \code{\link{runApp}} exits), or after each user
+#' session ends (when a client disconnects).
+#'
+#' @param fun A function that will be called after the app has finished running.
+#' @param session A scope for when the callback will run. If \code{onStop} is
+#'   called from within the server function, this will default to the current
+#'   session, and the callback will be invoked when the current session ends. If
+#'   \code{onStop} is called outside a server function, then the callback will
+#'   be invoked with the application exits.
+#'
+#'
+#' @seealso \code{\link{onSessionEnded}()} for the same functionality, but at
+#'   the session level only.
+#'
+#' @return A function which, if invoked, will cancel the callback.
+#' @examples
+#' ## Only run this example in interactive R sessions
+#' if (interactive()) {
+#'   # Open this application in multiple browsers, then close the browsers.
+#'   shinyApp(
+#'     ui = basicPage("onStop demo"),
+#'
+#'     server = function(input, output, session) {
+#'       onStop(function() cat("Session stopped\n"))
+#'     },
+#'
+#'     onStart = function() {
+#'       cat("Doing application setup\n")
+#'
+#'       onStop(function() {
+#'         cat("Doing application cleanup\n")
+#'       })
+#'     }
+#'   )
+#' }
+#' # In the example above, onStop() is called inside of onStart(). This is
+#' # the pattern that should be used when creating a shinyApp() object from
+#' # a function, or at the console. If instead you are writing an app.R which
+#' # will be invoked with runApp(), you can do it that way, or put the onStop()
+#' # before the shinyApp() call, as shown below.
+#'
+#' \dontrun{
+#' # ==== app.R ====
+#' cat("Doing application setup\n")
+#' onStop(function() {
+#'   cat("Doing application cleanup\n")
+#' })
+#'
+#' shinyApp(
+#'   ui = basicPage("onStop demo"),
+#'
+#'   server = function(input, output, session) {
+#'     onStop(function() cat("Session stopped\n"))
+#'   }
+#' )
+#' # ==== end app.R ====
+#'
+#'
+#' # Similarly, if you have a global.R, you can call onStop() from there.
+#' # ==== global.R ====
+#' cat("Doing application setup\n")
+#' onStop(function() {
+#'   cat("Doing application cleanup\n")
+#' })
+#' # ==== end global.R ====
+#' }
+#' @export
+onStop <- function(fun, session = getDefaultReactiveDomain()) {
+  if (is.null(getDefaultReactiveDomain())) {
+    return(.globals$onStopCallbacks$register(fun))
+  } else {
+    # Note: In the future if we allow scoping the onStop() callback to modules
+    # and allow modules to be stopped, then session_proxy objects will need
+    # its own implementation of $onSessionEnded.
+    return(session$onSessionEnded(fun))
+  }
+}
diff --git a/R/shinyui.R b/R/shinyui.R
index c3f4a5b..cdc1e77 100644
--- a/R/shinyui.R
+++ b/R/shinyui.R
@@ -14,7 +14,7 @@ NULL
 #' # now we can just write "static" content without withMathJax()
 #' div("more math here $$\\sqrt{2}$$")
 withMathJax <- function(...) {
-  path <- 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
+  path <- 'https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
   tagList(
     tags$head(
       singleton(tags$script(src = path, type = 'text/javascript'))
@@ -45,7 +45,6 @@ renderPage <- function(ui, connection, showcase=0, testMode=FALSE) {
   shiny_deps <- list(
     htmlDependency("json2", "2014.02.04", c(href="shared"), script = "json2-min.js"),
     htmlDependency("jquery", "1.12.4", c(href="shared"), script = "jquery.min.js"),
-    htmlDependency("babel-polyfill", "6.7.2", c(href="shared"), script = "babel-polyfill.min.js"),
     htmlDependency("shiny", utils::packageVersion("shiny"), c(href="shared"),
       script = if (getOption("shiny.minified", TRUE)) "shiny.min.js" else "shiny.js",
       stylesheet = "shiny.css")
diff --git a/R/shinywrappers.R b/R/shinywrappers.R
index c12f939..fb9c888 100644
--- a/R/shinywrappers.R
+++ b/R/shinywrappers.R
@@ -88,6 +88,34 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
   useRenderFunction(x, inline = inline)
 }
 
+
+#' Mark a render function with attributes that will be used by the output
+#'
+#' @inheritParams markRenderFunction
+#' @param snapshotExclude If TRUE, exclude the output from test snapshots.
+#' @param snapshotPreprocess A function for preprocessing the value before
+#'   taking a test snapshot.
+#'
+#' @keywords internal
+markOutputAttrs <- function(renderFunc, snapshotExclude = NULL,
+  snapshotPreprocess = NULL)
+{
+  # Add the outputAttrs attribute if necessary
+  if (is.null(attr(renderFunc, "outputAttrs", TRUE))) {
+    attr(renderFunc, "outputAttrs") <- list()
+  }
+
+  if (!is.null(snapshotExclude)) {
+    attr(renderFunc, "outputAttrs")$snapshotExclude <- snapshotExclude
+  }
+
+  if (!is.null(snapshotPreprocess)) {
+    attr(renderFunc, "outputAttrs")$snapshotPreprocess <- snapshotPreprocess
+  }
+
+  renderFunc
+}
+
 #' Image file output
 #'
 #' Renders a reactive image that is suitable for assigning to an \code{output}
@@ -410,7 +438,9 @@ downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()
   renderFunc <- function(shinysession, name, ...) {
     shinysession$registerDownload(name, filename, contentType, content)
   }
-  markRenderFunction(downloadButton, renderFunc, outputArgs = outputArgs)
+  snapshotExclude(
+    markRenderFunction(downloadButton, renderFunc, outputArgs = outputArgs)
+  )
 }
 
 #' Table output with the JavaScript library DataTables
@@ -511,7 +541,18 @@ renderDataTable <- function(expr, options = NULL, searchDelay = 500,
     )
   }
 
-  markRenderFunction(dataTableOutput, renderFunc, outputArgs = outputArgs)
+  renderFunc <- markRenderFunction(dataTableOutput, renderFunc, outputArgs = outputArgs)
+
+  renderFunc <- snapshotPreprocessOutput(renderFunc, function(value) {
+    # Remove the action field so that it's not saved in test snapshots. It
+    # contains a value that changes every time an app is run, and shouldn't be
+    # stored for test snapshots. It will be something like:
+    # "session/e0d14d3fe97f672f9655a127f2a1e079/dataobj/table?w=&nonce=7f5d6d54e22450a3"
+    value$action <- NULL
+    value
+  })
+
+  renderFunc
 }
 
 # a data frame containing the DataTables 1.9 and 1.10 names
diff --git a/R/showcase.R b/R/showcase.R
index fcdd0e9..d215f90 100644
--- a/R/showcase.R
+++ b/R/showcase.R
@@ -18,7 +18,8 @@ licenseLink <- function(licenseName) {
     "Artistic-2.0" = "http://www.r-project.org/Licenses/Artistic-2.0",
     "BSD_2_clause" = "http://www.r-project.org/Licenses/BSD_2_clause",
     "BSD_3_clause" = "http://www.r-project.org/Licenses/BSD_3_clause",
-    "MIT" = "http://www.r-project.org/Licenses/MIT")
+    "MIT" = "http://www.r-project.org/Licenses/MIT",
+    "CC-BY-SA-4.0" = "https://www.r-project.org/Licenses/CC-BY-SA-4.0")
   if (exists(licenseName, where = licenses)) {
     tags$a(href=licenses[[licenseName]], licenseName)
   } else {
diff --git a/R/snapshot.R b/R/snapshot.R
new file mode 100644
index 0000000..176378a
--- /dev/null
+++ b/R/snapshot.R
@@ -0,0 +1,44 @@
+#' Mark an output to be excluded from test snapshots
+#'
+#' @param x A reactive which will be assigned to an output.
+#'
+#' @export
+snapshotExclude <- function(x) {
+  markOutputAttrs(x, snapshotExclude = TRUE)
+}
+
+#' Add a function for preprocessing an output before taking a test snapshot
+#'
+#' @param x A reactive which will be assigned to an output.
+#' @param fun A function that takes the output value as an input and returns a
+#'   modified value. The returned value will be used for the test snapshot.
+#'
+#' @export
+snapshotPreprocessOutput <- function(x, fun) {
+  markOutputAttrs(x, snapshotPreprocess = fun)
+}
+
+
+#' Add a function for preprocessing an input before taking a test snapshot
+#'
+#' @param inputId Name of the input value.
+#' @param fun A function that takes the input value and returns a modified
+#'   value. The returned value will be used for the test snapshot.
+#' @param session A Shiny session object.
+#'
+#' @export
+snapshotPreprocessInput <- function(inputId, fun, session = getDefaultReactiveDomain()) {
+  if (is.null(session)) {
+    stop("snapshotPreprocessInput() needs a session object.")
+  }
+
+  input_impl <- .subset2(session$input, "impl")
+  input_impl$setMeta(inputId, "shiny.snapshot.preprocess", fun)
+}
+
+
+# Strip out file path from fileInput value
+snapshotPreprocessorFileInput <- function(value) {
+  value$datapath <- basename(value$datapath)
+  value
+}
diff --git a/R/update-input.R b/R/update-input.R
index fa58f68..00330a3 100644
--- a/R/update-input.R
+++ b/R/update-input.R
@@ -2,6 +2,7 @@
 #'
 #' @template update-input
 #' @param value The value to set for the input object.
+#' @param placeholder The placeholder to set for the input object.
 #'
 #' @seealso \code{\link{textInput}}
 #'
@@ -34,15 +35,15 @@
 #' shinyApp(ui, server)
 #' }
 #' @export
-updateTextInput <- function(session, inputId, label = NULL, value = NULL) {
-  message <- dropNulls(list(label=label, value=value))
+updateTextInput <- function(session, inputId, label = NULL, value = NULL, placeholder = NULL) {
+  message <- dropNulls(list(label=label, value=value, placeholder=placeholder))
   session$sendInputMessage(inputId, message)
 }
 
 #' Change the value of a textarea input on the client
 #'
 #' @template update-input
-#' @param value The value to set for the input object.
+#' @inheritParams updateTextInput
 #'
 #' @seealso \code{\link{textAreaInput}}
 #'
@@ -106,7 +107,10 @@ updateTextAreaInput <- updateTextInput
 #' shinyApp(ui, server)
 #' }
 #' @export
-updateCheckboxInput <- updateTextInput
+updateCheckboxInput <- function(session, inputId, label = NULL, value = NULL) {
+  message <- dropNulls(list(label=label, value=value))
+  session$sendInputMessage(inputId, message)
+}
 
 
 #' Change the label or icon of an action button on the client
@@ -452,16 +456,18 @@ updateSliderInput <- function(session, inputId, label = NULL, value = NULL,
 
 
 updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
-                               selected = NULL, inline = FALSE,
-                               type = 'checkbox') {
-  if (!is.null(choices))
-    choices <- choicesWithNames(choices)
-  if (!is.null(selected))
-    selected <- validateSelected(selected, choices, session$ns(inputId))
+                               selected = NULL, inline = FALSE, type = NULL,
+                               choiceNames = NULL, choiceValues = NULL) {
+  if (is.null(type)) stop("Please specify the type ('checkbox' or 'radio')")
+
+  args <- normalizeChoicesArgs(choices, choiceNames, choiceValues, mustExist = FALSE)
+
+  if (!is.null(selected)) selected <- as.character(selected)
 
-  options <- if (!is.null(choices)) {
+  options <- if (!is.null(args$choiceValues)) {
     format(tagList(
-      generateOptions(session$ns(inputId), choices, selected, inline, type = type)
+      generateOptions(session$ns(inputId), selected, inline, type,
+        args$choiceNames, args$choiceValues)
     ))
   }
 
@@ -510,9 +516,10 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
 #' }
 #' @export
 updateCheckboxGroupInput <- function(session, inputId, label = NULL,
-                                     choices = NULL, selected = NULL,
-                                     inline = FALSE) {
-  updateInputOptions(session, inputId, label, choices, selected, inline)
+  choices = NULL, selected = NULL, inline = FALSE,
+  choiceNames = NULL, choiceValues = NULL) {
+  updateInputOptions(session, inputId, label, choices, selected,
+                     inline, "checkbox", choiceNames, choiceValues)
 }
 
 
@@ -552,10 +559,15 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL,
 #' }
 #' @export
 updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
-                               selected = NULL, inline = FALSE) {
+                               selected = NULL, inline = FALSE,
+                               choiceNames = NULL, choiceValues = NULL) {
   # you must select at least one radio button
-  if (is.null(selected) && !is.null(choices)) selected <- choices[[1]]
-  updateInputOptions(session, inputId, label, choices, selected, inline, type = 'radio')
+  if (is.null(selected)) {
+    if (!is.null(choices)) selected <- choices[[1]]
+    else if (!is.null(choiceValues)) selected <- choiceValues[[1]]
+  }
+  updateInputOptions(session, inputId, label, choices, selected,
+    inline, 'radio', choiceNames, choiceValues)
 }
 
 
@@ -601,8 +613,7 @@ updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
 updateSelectInput <- function(session, inputId, label = NULL, choices = NULL,
                               selected = NULL) {
   choices <- if (!is.null(choices)) choicesWithNames(choices)
-  if (!is.null(selected))
-    selected <- validateSelected(selected, choices, inputId)
+  if (!is.null(selected)) selected <- as.character(selected)
   options <- if (!is.null(choices)) selectOptions(choices, selected)
   message <- dropNulls(list(label = label, options = options, value = selected))
   session$sendInputMessage(inputId, message)
@@ -644,7 +655,7 @@ updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
 selectizeJSON <- function(data, req) {
   query <- parseQueryString(req$QUERY_STRING)
   # extract the query variables, conjunction (and/or), search string, maximum options
-  var <- c(jsonlite::fromJSON(query$field))
+  var <- c(safeFromJSON(query$field))
   cjn <- if (query$conju == 'and') all else any
   # all keywords in lower-case, for case-insensitive matching
   key <- unique(strsplit(tolower(query$query), '\\s+')[[1]])
diff --git a/R/utils.R b/R/utils.R
index bf324ce..06b6be8 100644
--- a/R/utils.R
+++ b/R/utils.R
@@ -43,53 +43,43 @@ repeatable <- function(rngfunc, seed = stats::runif(1, 0, .Machine$integer.max))
   }
 }
 
-# Temporarily set x in env to value, evaluate expr, and
-# then restore x to its original state
-withTemporary <- function(env, x, value, expr, unset = FALSE) {
-
-  if (exists(x, envir = env, inherits = FALSE)) {
-    oldValue <- get(x, envir = env, inherits = FALSE)
-    on.exit(
-      assign(x, oldValue, envir = env, inherits = FALSE),
-      add = TRUE)
+.globals$ownSeed <- NULL
+# Evaluate an expression using Shiny's own private stream of
+# randomness (not affected by set.seed).
+withPrivateSeed <- function(expr) {
+  # Save the old seed if present.
+  if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
+    hasOrigSeed <- TRUE
+    origSeed <- .GlobalEnv$.Random.seed
   } else {
-    on.exit(
-      rm(list = x, envir = env, inherits = FALSE),
-      add = TRUE
-    )
+    hasOrigSeed <- FALSE
   }
 
-  if (!missing(value) && !isTRUE(unset))
-    assign(x, value, envir = env, inherits = FALSE)
-  else {
-    if (exists(x, envir = env, inherits = FALSE))
-      rm(list = x, envir = env, inherits = FALSE)
+  # Swap in the private seed.
+  if (is.null(.globals$ownSeed)) {
+    if (hasOrigSeed) {
+      # Move old seed out of the way if present.
+      rm(.Random.seed, envir = .GlobalEnv, inherits = FALSE)
+    }
+  } else {
+    .GlobalEnv$.Random.seed <- .globals$ownSeed
   }
-  force(expr)
-}
 
-.globals$ownSeed <- NULL
-# Evaluate an expression using Shiny's own private stream of
-# randomness (not affected by set.seed).
-withPrivateSeed <- function(expr) {
-  withTemporary(.GlobalEnv, ".Random.seed",
-    .globals$ownSeed, unset=is.null(.globals$ownSeed), {
-      tryCatch({
-        expr
-      }, finally = {
-        .globals$ownSeed <- getExists('.Random.seed', 'numeric', globalenv())
-      })
+  # On exit, save the modified private seed, and put the old seed back.
+  on.exit({
+    .globals$ownSeed <- .GlobalEnv$.Random.seed
+
+    if (hasOrigSeed) {
+      .GlobalEnv$.Random.seed <- origSeed
+    } else {
+      rm(.Random.seed, envir = .GlobalEnv, inherits = FALSE)
     }
-  )
-}
+    # Need to call this to make sure that the value of .Random.seed gets put
+    # into R's internal RNG state. (Issue #1763)
+    httpuv::getRNGState()
+  })
 
-# a homemade version of set.seed(NULL) for backward compatibility with R 2.15.x
-reinitializeSeed <- if (getRversion() >= '3.0.0') {
-  function() set.seed(NULL)
-} else function() {
-  if (exists('.Random.seed', globalenv()))
-    rm(list = '.Random.seed', pos = globalenv())
-  stats::runif(1)  # generate any random numbers so R can reinitialize the seed
+  expr
 }
 
 # Version of runif that runs with private seed
@@ -225,7 +215,7 @@ sortByName <- function(x) {
 # R >=3.2.0, this wrapper is not necessary.
 list2env2 <- function(x, ...) {
   # Ensure that zero-length lists have a name attribute
-   if (length(x) == 0)
+  if (length(x) == 0)
     attr(x, "names") <- character(0)
 
   list2env(x, ...)
@@ -672,6 +662,9 @@ Callbacks <- R6Class(
       .callbacks <<- Map$new()
     },
     register = function(callback) {
+      if (!is.function(callback)) {
+        stop("callback must be a function")
+      }
       id <- as.character(.nextId)
       .nextId <<- .nextId - 1L
       .callbacks$set(id, callback)
diff --git a/README.md b/README.md
index fee1779..77e92cc 100644
--- a/README.md
+++ b/README.md
@@ -14,9 +14,9 @@ For an introduction and examples, visit the [Shiny Dev Center](http://shiny.rstu
 * Shiny applications are automatically "live" in the same way that spreadsheets are live. Outputs change instantly as users modify inputs, without requiring a reload of the browser.
 * Shiny user interfaces can be built entirely using R, or can be written directly in HTML, CSS, and JavaScript for more flexibility.
 * Works in any R environment (Console R, Rgui for Windows or Mac, ESS, StatET, RStudio, etc.).
-* Attractive default UI theme based on [Bootstrap](http://getbootstrap.com/2.3.2/).
+* Attractive default UI theme based on [Bootstrap](http://getbootstrap.com/).
 * 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.
+* Prebuilt output widgets for displaying plots, tables, and printed output of R objects.
 * 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!).
diff --git a/inst/examples/01_hello/Readme.md b/inst/examples/01_hello/Readme.md
index 23d5810..a2450ac 100644
--- a/inst/examples/01_hello/Readme.md
+++ b/inst/examples/01_hello/Readme.md
@@ -1,4 +1,3 @@
-This small Shiny application demonstrates Shiny's automatic UI updates. Move
-the *Number of bins* slider and notice how the `renderPlot` expression is
-automatically re-evaluated when its dependant, `input$bins`, changes,
-causing a histogram with a new number of bins to be rendered.
+This small Shiny application demonstrates Shiny's automatic UI updates. 
+
+Move the *Number of bins* slider and notice how the `renderPlot` expression is automatically re-evaluated when its dependant, `input$bins`, changes, causing a histogram with a new number of bins to be rendered.
diff --git a/inst/examples/01_hello/app.R b/inst/examples/01_hello/app.R
new file mode 100644
index 0000000..887c505
--- /dev/null
+++ b/inst/examples/01_hello/app.R
@@ -0,0 +1,59 @@
+library(shiny)
+
+# Define UI for app that draws a histogram ----
+ui <- fluidPage(
+
+  # App title ----
+  titlePanel("Hello Shiny!"),
+
+  # Sidebar layout with input and output definitions ----
+  sidebarLayout(
+
+    # Sidebar panel for inputs ----
+    sidebarPanel(
+
+      # Input: Slider for the number of bins ----
+      sliderInput(inputId = "bins",
+                  label = "Number of bins:",
+                  min = 1,
+                  max = 50,
+                  value = 30)
+
+    ),
+
+    # Main panel for displaying outputs ----
+    mainPanel(
+
+      # Output: Histogram ----
+      plotOutput(outputId = "distPlot")
+
+    )
+  )
+)
+
+# Define server logic required to draw a histogram ----
+server <- function(input, output) {
+
+  # Histogram of the Old Faithful Geyser Data ----
+  # with requested number of bins
+  # This expression that generates a histogram is wrapped in a call
+  # to renderPlot to indicate that:
+  #
+  # 1. It is "reactive" and therefore should be automatically
+  #    re-executed when inputs (input$bins) change
+  # 2. Its output type is a plot
+  output$distPlot <- renderPlot({
+
+    x    <- faithful$waiting
+    bins <- seq(min(x), max(x), length.out = input$bins + 1)
+
+    hist(x, breaks = bins, col = "#75AADB", border = "white",
+         xlab = "Waiting time to next eruption (in mins)",
+         main = "Histogram of waiting times")
+
+    })
+
+}
+
+# Create Shiny app ----
+shinyApp(ui = ui, server = server)
diff --git a/inst/examples/01_hello/server.R b/inst/examples/01_hello/server.R
deleted file mode 100644
index 8cece54..0000000
--- a/inst/examples/01_hello/server.R
+++ /dev/null
@@ -1,21 +0,0 @@
-library(shiny)
-
-# Define server logic required to draw a histogram
-function(input, output) {
-
-  # Expression that generates a histogram. The expression is
-  # wrapped in a call to renderPlot to indicate that:
-  #
-  #  1) It is "reactive" and therefore should be automatically
-  #     re-executed when inputs change
-  #  2) Its output type is a plot
-
-  output$distPlot <- renderPlot({
-    x    <- faithful[, 2]  # Old Faithful Geyser data
-    bins <- seq(min(x), max(x), length.out = input$bins + 1)
-
-    # draw the histogram with the specified number of bins
-    hist(x, breaks = bins, col = 'darkgray', border = 'white')
-  })
-
-}
diff --git a/inst/examples/01_hello/ui.R b/inst/examples/01_hello/ui.R
deleted file mode 100644
index a4aa6f4..0000000
--- a/inst/examples/01_hello/ui.R
+++ /dev/null
@@ -1,24 +0,0 @@
-library(shiny)
-
-# Define UI for application that draws a histogram
-fluidPage(
-
-  # Application title
-  titlePanel("Hello Shiny!"),
-
-  # Sidebar with a slider input for the number of bins
-  sidebarLayout(
-    sidebarPanel(
-      sliderInput("bins",
-                  "Number of bins:",
-                  min = 1,
-                  max = 50,
-                  value = 30)
-    ),
-
-    # Show a plot of the generated distribution
-    mainPanel(
-      plotOutput("distPlot")
-    )
-  )
-)
diff --git a/inst/examples/02_text/Readme.md b/inst/examples/02_text/Readme.md
index dba7dab..7aa04e5 100644
--- a/inst/examples/02_text/Readme.md
+++ b/inst/examples/02_text/Readme.md
@@ -1 +1 @@
-This example demonstrates output of raw text from R using the `renderPrint` function in `server.R` and the `verbatimTextOutput` function in `ui.R`. In this case, a textual summary of the data is shown using R's built-in `summary` function. 
+This example demonstrates output of raw text from R using the `renderPrint` function in `server` and the `verbatimTextOutput` function in `ui`. In this case, a textual summary of the data is shown using R's built-in `summary` function. 
diff --git a/inst/examples/02_text/app.R b/inst/examples/02_text/app.R
new file mode 100644
index 0000000..9aff1e8
--- /dev/null
+++ b/inst/examples/02_text/app.R
@@ -0,0 +1,64 @@
+library(shiny)
+
+# Define UI for dataset viewer app ----
+ui <- fluidPage(
+
+  # App title ----
+  titlePanel("Shiny Text"),
+
+  # Sidebar layout with a input and output definitions ----
+  sidebarLayout(
+
+    # Sidebar panel for inputs ----
+    sidebarPanel(
+
+      # Input: Selector for choosing dataset ----
+      selectInput(inputId = "dataset",
+                  label = "Choose a dataset:",
+                  choices = c("rock", "pressure", "cars")),
+
+      # Input: Numeric entry for number of obs to view ----
+      numericInput(inputId = "obs",
+                   label = "Number of observations to view:",
+                   value = 10)
+    ),
+
+    # Main panel for displaying outputs ----
+    mainPanel(
+
+      # Output: Verbatim text for data summary ----
+      verbatimTextOutput("summary"),
+
+      # Output: HTML table with requested number of observations ----
+      tableOutput("view")
+
+    )
+  )
+)
+
+# Define server logic to summarize and view selected dataset ----
+server <- function(input, output) {
+
+  # Return the requested dataset ----
+  datasetInput <- reactive({
+    switch(input$dataset,
+           "rock" = rock,
+           "pressure" = pressure,
+           "cars" = cars)
+  })
+
+  # Generate a summary of the dataset ----
+  output$summary <- renderPrint({
+    dataset <- datasetInput()
+    summary(dataset)
+  })
+
+  # Show the first "n" observations ----
+  output$view <- renderTable({
+    head(datasetInput(), n = input$obs)
+  })
+
+}
+
+# Create Shiny app ----
+shinyApp(ui = ui, server = server)
diff --git a/inst/examples/02_text/server.R b/inst/examples/02_text/server.R
deleted file mode 100644
index cacf883..0000000
--- a/inst/examples/02_text/server.R
+++ /dev/null
@@ -1,26 +0,0 @@
-library(shiny)
-library(datasets)
-
-# Define server logic required to summarize and view the selected
-# dataset
-function(input, output) {
-  
-  # Return the requested dataset
-  datasetInput <- reactive({
-    switch(input$dataset,
-           "rock" = rock,
-           "pressure" = pressure,
-           "cars" = cars)
-  })
-  
-  # Generate a summary of the dataset
-  output$summary <- renderPrint({
-    dataset <- datasetInput()
-    summary(dataset)
-  })
-  
-  # Show the first "n" observations
-  output$view <- renderTable({
-    head(datasetInput(), n = input$obs)
-  })
-}
diff --git a/inst/examples/02_text/ui.R b/inst/examples/02_text/ui.R
deleted file mode 100644
index 35b793e..0000000
--- a/inst/examples/02_text/ui.R
+++ /dev/null
@@ -1,27 +0,0 @@
-library(shiny)
-
-# Define UI for dataset viewer application
-fluidPage(
-  
-  # Application title
-  titlePanel("Shiny Text"),
-  
-  # Sidebar with controls to select a dataset and specify the
-  # number of observations to view
-  sidebarLayout(
-    sidebarPanel(
-      selectInput("dataset", "Choose a dataset:", 
-                  choices = c("rock", "pressure", "cars")),
-      
-      numericInput("obs", "Number of observations to view:", 10)
-    ),
-    
-    # Show a summary of the dataset and an HTML table with the 
-	 # requested number of observations
-    mainPanel(
-      verbatimTextOutput("summary"),
-      
-      tableOutput("view")
-    )
-  )
-)
diff --git a/inst/examples/03_reactivity/Readme.md b/inst/examples/03_reactivity/Readme.md
index 1ba576c..8eaee27 100644
--- a/inst/examples/03_reactivity/Readme.md
+++ b/inst/examples/03_reactivity/Readme.md
@@ -1,5 +1,5 @@
-This example demonstrates a core feature of Shiny: **reactivity**. In `server.R`, a reactive called `datasetInput` is declared. 
+This example demonstrates a core feature of Shiny: **reactivity**. In the `server` function, a reactive called `datasetInput` is declared. 
 
-Notice that the reactive expression depends on the input expression `input$dataset`, and that it's used by both the output expression `output$summary` and `output$view`. Try changing the dataset (using *Choose a dataset*) while looking at the reactive and then at the outputs; you will see first the reactive and then its dependencies flash. 
+Notice that the reactive expression depends on the input expression `input$dataset`, and that it's used by two output expressions: `output$summary` and `output$view`. Try changing the dataset (using *Choose a dataset*) while looking at the reactive and then at the outputs; you will see first the reactive and then its dependencies flash. 
 
 Notice also that the reactive expression doesn't just update whenever anything changes--only the inputs it depends on will trigger an update. Change the "Caption" field and notice how only the `output$caption` expression is re-evaluated; the reactive and its dependents are left alone.
diff --git a/inst/examples/03_reactivity/app.R b/inst/examples/03_reactivity/app.R
new file mode 100644
index 0000000..393ebfe
--- /dev/null
+++ b/inst/examples/03_reactivity/app.R
@@ -0,0 +1,102 @@
+library(shiny)
+
+# Define UI for dataset viewer app ----
+ui <- fluidPage(
+
+  # App title ----
+  titlePanel("Reactivity"),
+
+  # Sidebar layout with input and output definitions ----
+  sidebarLayout(
+
+    # Sidebar panel for inputs ----
+    sidebarPanel(
+
+      # Input: Text for providing a caption ----
+      # Note: Changes made to the caption in the textInput control
+      # are updated in the output area immediately as you type
+      textInput(inputId = "caption",
+                label = "Caption:",
+                value = "Data Summary"),
+
+      # Input: Selector for choosing dataset ----
+      selectInput(inputId = "dataset",
+                  label = "Choose a dataset:",
+                  choices = c("rock", "pressure", "cars")),
+
+      # Input: Numeric entry for number of obs to view ----
+      numericInput(inputId = "obs",
+                   label = "Number of observations to view:",
+                   value = 10)
+
+    ),
+
+    # Main panel for displaying outputs ----
+    mainPanel(
+
+      # Output: Formatted text for caption ----
+      h3(textOutput("caption", container = span)),
+
+      # Output: Verbatim text for data summary ----
+      verbatimTextOutput("summary"),
+
+      # Output: HTML table with requested number of observations ----
+      tableOutput("view")
+
+    )
+  )
+)
+
+# Define server logic to summarize and view selected dataset ----
+server <- function(input, output) {
+
+  # Return the requested dataset ----
+  # By declaring datasetInput as a reactive expression we ensure
+  # that:
+  #
+  # 1. It is only called when the inputs it depends on changes
+  # 2. The computation and result are shared by all the callers,
+  #    i.e. it only executes a single time
+  datasetInput <- reactive({
+    switch(input$dataset,
+           "rock" = rock,
+           "pressure" = pressure,
+           "cars" = cars)
+  })
+
+  # Create caption ----
+  # The output$caption is computed based on a reactive expression
+  # that returns input$caption. When the user changes the
+  # "caption" field:
+  #
+  # 1. This function is automatically called to recompute the output
+  # 2. New caption is pushed back to the browser for re-display
+  #
+  # Note that because the data-oriented reactive expressions
+  # below don't depend on input$caption, those expressions are
+  # NOT called when input$caption changes
+  output$caption <- renderText({
+    input$caption
+  })
+
+  # Generate a summary of the dataset ----
+  # The output$summary depends on the datasetInput reactive
+  # expression, so will be re-executed whenever datasetInput is
+  # invalidated, i.e. whenever the input$dataset changes
+  output$summary <- renderPrint({
+    dataset <- datasetInput()
+    summary(dataset)
+  })
+
+  # Show the first "n" observations ----
+  # The output$view depends on both the databaseInput reactive
+  # expression and input$obs, so it will be re-executed whenever
+  # input$dataset or input$obs is changed
+  output$view <- renderTable({
+    head(datasetInput(), n = input$obs)
+  })
+
+}
+
+# Create Shiny app ----
+shinyApp(ui, server)
diff --git a/inst/examples/03_reactivity/server.R b/inst/examples/03_reactivity/server.R
deleted file mode 100644
index 752c351..0000000
--- a/inst/examples/03_reactivity/server.R
+++ /dev/null
@@ -1,53 +0,0 @@
-library(shiny)
-library(datasets)
-
-# Define server logic required to summarize and view the selected
-# dataset
-function(input, output) {
-
-  # By declaring datasetInput as a reactive expression we ensure 
-  # that:
-  #
-  #  1) It is only called when the inputs it depends on changes
-  #  2) The computation and result are shared by all the callers 
-  #	  (it only executes a single time)
-  #
-  datasetInput <- reactive({
-    switch(input$dataset,
-           "rock" = rock,
-           "pressure" = pressure,
-           "cars" = cars)
-  })
-  
-  # The output$caption is computed based on a reactive expression
-  # that returns input$caption. When the user changes the
-  # "caption" field:
-  #
-  #  1) This function is automatically called to recompute the 
-  #     output 
-  #  2) The new caption is pushed back to the browser for 
-  #     re-display
-  # 
-  # Note that because the data-oriented reactive expressions
-  # below don't depend on input$caption, those expressions are
-  # NOT called when input$caption changes.
-  output$caption <- renderText({
-    input$caption
-  })
-  
-  # The output$summary depends on the datasetInput reactive
-  # expression, so will be re-executed whenever datasetInput is
-  # invalidated
-  # (i.e. whenever the input$dataset changes)
-  output$summary <- renderPrint({
-    dataset <- datasetInput()
-    summary(dataset)
-  })
-  
-  # The output$view depends on both the databaseInput reactive
-  # expression and input$obs, so will be re-executed whenever
-  # input$dataset or input$obs is changed. 
-  output$view <- renderTable({
-    head(datasetInput(), n = input$obs)
-  })
-}
diff --git a/inst/examples/03_reactivity/ui.R b/inst/examples/03_reactivity/ui.R
deleted file mode 100644
index c0824a7..0000000
--- a/inst/examples/03_reactivity/ui.R
+++ /dev/null
@@ -1,34 +0,0 @@
-library(shiny)
-
-# Define UI for dataset viewer application
-fluidPage(
-  
-  # Application title
-  titlePanel("Reactivity"),
-  
-  # Sidebar with controls to provide a caption, select a dataset,
-  # and specify the number of observations to view. Note that
-  # changes made to the caption in the textInput control are
-  # updated in the output area immediately as you type
-  sidebarLayout(
-    sidebarPanel(
-      textInput("caption", "Caption:", "Data Summary"),
-      
-      selectInput("dataset", "Choose a dataset:", 
-                  choices = c("rock", "pressure", "cars")),
-      
-      numericInput("obs", "Number of observations to view:", 10)
-    ),
-    
-    
-    # Show the caption, a summary of the dataset and an HTML 
-	 # table with the requested number of observations
-    mainPanel(
-      h3(textOutput("caption", container = span)),
-      
-      verbatimTextOutput("summary"), 
-      
-      tableOutput("view")
-    )
-  )
-)
diff --git a/inst/examples/04_mpg/Readme.md b/inst/examples/04_mpg/Readme.md
index 105b239..33c0fe0 100644
--- a/inst/examples/04_mpg/Readme.md
+++ b/inst/examples/04_mpg/Readme.md
@@ -1,4 +1,4 @@
 This example demonstrates the following concepts:
 
-* **Global variables**: The `mpgData` variable is declared outside the `shinyServer` function. This makes it available anywhere inside `shinyServer`. The code in `server.R` outside `shinyServer` is only run once when the app starts up, so it can't contain user input.
-* **Reactive expressions**: `formulaText` is a reactive expression. Note how it re-evaluates when the Variable field is changed, but not when the Show Outliers box is ticked. 
+- **Global variables**: The `mpgData` variable is declared outside of the `ui` and `server` function definitions. This makes it available anywhere inside `app.R`. The code in `app.R` outside of `ui` and `server` function definitions is only run once when the app starts up, so it can't contain user input.
+- **Reactive expressions**: `formulaText` is a reactive expression. Note how it re-evaluates when the Variable field is changed, but not when the Show Outliers box is unchecked. 
diff --git a/inst/examples/04_mpg/app.R b/inst/examples/04_mpg/app.R
new file mode 100644
index 0000000..e789bed
--- /dev/null
+++ b/inst/examples/04_mpg/app.R
@@ -0,0 +1,75 @@
+library(shiny)
+library(datasets)
+
+# Data pre-processing ----
+# Tweak the "am" variable to have nicer factor labels -- since this
+# doesn't rely on any user inputs, we can do this once at startup
+# and then use the value throughout the lifetime of the app
+mpgData <- mtcars
+mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
+
+
+# Define UI for miles per gallon app ----
+ui <- fluidPage(
+
+  # App title ----
+  titlePanel("Miles Per Gallon"),
+
+  # Sidebar layout with input and output definitions ----
+  sidebarLayout(
+
+    # Sidebar panel for inputs ----
+    sidebarPanel(
+
+      # Input: Selector for variable to plot against mpg ----
+      selectInput("variable", "Variable:",
+                  c("Cylinders" = "cyl",
+                    "Transmission" = "am",
+                    "Gears" = "gear")),
+
+      # Input: Checkbox for whether outliers should be included ----
+      checkboxInput("outliers", "Show outliers", TRUE)
+
+    ),
+
+    # Main panel for displaying outputs ----
+    mainPanel(
+
+      # Output: Formatted text for caption ----
+      h3(textOutput("caption")),
+
+      # Output: Plot of the requested variable against mpg ----
+      plotOutput("mpgPlot")
+
+    )
+  )
+)
+
+# Define server logic to plot various variables against mpg ----
+server <- function(input, output) {
+
+  # Compute the formula text ----
+  # This is in a reactive expression since it is shared by the
+  # output$caption and output$mpgPlot functions
+  formulaText <- reactive({
+    paste("mpg ~", input$variable)
+  })
+
+  # Return the formula text for printing as a caption ----
+  output$caption <- renderText({
+    formulaText()
+  })
+
+  # Generate a plot of the requested variable against mpg ----
+  # and only exclude outliers if requested
+  output$mpgPlot <- renderPlot({
+    boxplot(as.formula(formulaText()),
+            data = mpgData,
+            outline = input$outliers,
+            col = "#75AADB", pch = 19)
+  })
+
+}
+
+# Create Shiny app ----
+shinyApp(ui, server)
diff --git a/inst/examples/04_mpg/server.R b/inst/examples/04_mpg/server.R
deleted file mode 100644
index 10b7620..0000000
--- a/inst/examples/04_mpg/server.R
+++ /dev/null
@@ -1,34 +0,0 @@
-library(shiny)
-library(datasets)
-
-# We tweak the "am" field to have nicer factor labels. Since
-# this doesn't rely on any user inputs we can do this once at
-# startup and then use the value throughout the lifetime of the
-# application
-mpgData <- mtcars
-mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
-
-
-# Define server logic required to plot various variables against
-# mpg
-function(input, output) {
-
-  # Compute the formula text in a reactive expression since it is
-  # shared by the output$caption and output$mpgPlot functions
-  formulaText <- reactive({
-    paste("mpg ~", input$variable)
-  })
-
-  # Return the formula text for printing as a caption
-  output$caption <- renderText({
-    formulaText()
-  })
-
-  # Generate a plot of the requested variable against mpg and
-  # only include outliers if requested
-  output$mpgPlot <- renderPlot({
-    boxplot(as.formula(formulaText()),
-            data = mpgData,
-            outline = input$outliers)
-  })
-}
diff --git a/inst/examples/04_mpg/ui.R b/inst/examples/04_mpg/ui.R
deleted file mode 100644
index 323a169..0000000
--- a/inst/examples/04_mpg/ui.R
+++ /dev/null
@@ -1,29 +0,0 @@
-library(shiny)
-
-# Define UI for miles per gallon application
-fluidPage(
-  
-  # Application title
-  titlePanel("Miles Per Gallon"),
-  
-  # Sidebar with controls to select the variable to plot against
-  # mpg and to specify whether outliers should be included
-  sidebarLayout(
-    sidebarPanel(
-      selectInput("variable", "Variable:",
-                  c("Cylinders" = "cyl",
-                    "Transmission" = "am",
-                    "Gears" = "gear")),
-  
-      checkboxInput("outliers", "Show outliers", FALSE)
-    ),
-    
-	 # Show the caption and plot of the requested variable against
-	 # mpg
-    mainPanel(
-      h3(textOutput("caption")),
-      
-      plotOutput("mpgPlot")
-    )
-  )
-)
diff --git a/inst/examples/05_sliders/app.R b/inst/examples/05_sliders/app.R
new file mode 100644
index 0000000..d9aa05c
--- /dev/null
+++ b/inst/examples/05_sliders/app.R
@@ -0,0 +1,86 @@
+library(shiny)
+
+# Define UI for slider demo app ----
+ui <- fluidPage(
+
+  # App title ----
+  titlePanel("Sliders"),
+
+  # Sidebar layout with input and output definitions ----
+  sidebarLayout(
+
+    # Sidebar to demonstrate various slider options ----
+    sidebarPanel(
+
+      # Input: Simple integer interval ----
+      sliderInput("integer", "Integer:",
+                  min = 0, max = 1000,
+                  value = 500),
+
+      # Input: Decimal interval with step value ----
+      sliderInput("decimal", "Decimal:",
+                  min = 0, max = 1,
+                  value = 0.5, step = 0.1),
+
+      # Input: Specification of range within an interval ----
+      sliderInput("range", "Range:",
+                  min = 1, max = 1000,
+                  value = c(200,500)),
+
+      # Input: Custom currency format for with basic animation ----
+      sliderInput("format", "Custom Format:",
+                  min = 0, max = 10000,
+                  value = 0, step = 2500,
+                  pre = "$", sep = ",",
+                  animate = TRUE),
+
+      # Input: Animation with custom interval (in ms) ----
+      # to control speed, plus looping
+      sliderInput("animation", "Looping Animation:",
+                  min = 1, max = 2000,
+                  value = 1, step = 10,
+                  animate =
+                    animationOptions(interval = 300, loop = TRUE))
+
+    ),
+
+    # Main panel for displaying outputs ----
+    mainPanel(
+
+      # Output: Table summarizing the values entered ----
+      tableOutput("values")
+
+    )
+  )
+)
+
+# Define server logic for slider examples ----
+server <- function(input, output) {
+
+  # Reactive expression to create data frame of all input values ----
+  sliderValues <- reactive({
+
+    data.frame(
+      Name = c("Integer",
+               "Decimal",
+               "Range",
+               "Custom Format",
+               "Animation"),
+      Value = as.character(c(input$integer,
+                             input$decimal,
+                             paste(input$range, collapse = " "),
+                             input$format,
+                             input$animation)),
+      stringsAsFactors = FALSE)
+
+  })
+
+  # Show the values in an HTML table ----
+  output$values <- renderTable({
+    sliderValues()
+  })
+
+}
+
+# Create Shiny app ----
+shinyApp(ui, server)
diff --git a/inst/examples/05_sliders/server.R b/inst/examples/05_sliders/server.R
deleted file mode 100644
index a099d20..0000000
--- a/inst/examples/05_sliders/server.R
+++ /dev/null
@@ -1,29 +0,0 @@
-library(shiny)
-
-# Define server logic for slider examples
-function(input, output) {
-  
-  # Reactive expression to compose a data frame containing all of
-  # the values
-  sliderValues <- reactive({
-    
-    # Compose data frame
-    data.frame(
-      Name = c("Integer", 
-               "Decimal",
-               "Range",
-               "Custom Format",
-               "Animation"),
-      Value = as.character(c(input$integer, 
-                             input$decimal,
-                             paste(input$range, collapse=' '),
-                             input$format,
-                             input$animation)), 
-      stringsAsFactors=FALSE)
-  }) 
-  
-  # Show the values using an HTML table
-  output$values <- renderTable({
-    sliderValues()
-  })
-}
diff --git a/inst/examples/05_sliders/ui.R b/inst/examples/05_sliders/ui.R
deleted file mode 100644
index cbb7c63..0000000
--- a/inst/examples/05_sliders/ui.R
+++ /dev/null
@@ -1,43 +0,0 @@
-library(shiny)
-
-# Define UI for slider demo application
-fluidPage(
-
-  #  Application title
-  titlePanel("Sliders"),
-
-  # Sidebar with sliders that demonstrate various available
-  # options
-  sidebarLayout(
-    sidebarPanel(
-      # Simple integer interval
-      sliderInput("integer", "Integer:",
-                  min=0, max=1000, value=500),
-
-      # Decimal interval with step value
-      sliderInput("decimal", "Decimal:",
-                  min = 0, max = 1, value = 0.5, step= 0.1),
-
-      # Specification of range within an interval
-      sliderInput("range", "Range:",
-                  min = 1, max = 1000, value = c(200,500)),
-
-      # Provide a custom currency format for value display,
-		# with basic animation
-      sliderInput("format", "Custom Format:",
-                  min = 0, max = 10000, value = 0, step = 2500,
-                  pre = "$", sep = ",", animate=TRUE),
-
-      # Animation with custom interval (in ms) to control speed,
-		# plus looping
-      sliderInput("animation", "Looping Animation:", 1, 2000, 1,
-					  	step = 10, animate=
-							animationOptions(interval=300, loop=TRUE))
-    ),
-
-    # Show a table summarizing the values entered
-    mainPanel(
-      tableOutput("values")
-    )
-  )
-)
diff --git a/inst/examples/06_tabsets/Readme.md b/inst/examples/06_tabsets/Readme.md
index b451246..c548d30 100644
--- a/inst/examples/06_tabsets/Readme.md
+++ b/inst/examples/06_tabsets/Readme.md
@@ -2,7 +2,7 @@ This example demonstrates the `tabsetPanel` and `tabPanel` widgets.
 
 Notice that outputs that are not visible are not re-evaluated until they become visible. Try this: 
 
-1. Scroll to the bottom of `server.R`
+1. Scroll to the bottom of the `server` function. You might need to use the *show with app* option so you can easily view the code and interact with the app at the same time.
 2. Change the number of observations, and observe that only `output$plot` is evaluated.
 3. Click the Summary tab, and observe that `output$summary` is evaluated.
 4. Change the number of observations again, and observe that now only `output$summary` is evaluated.
diff --git a/inst/examples/06_tabsets/app.R b/inst/examples/06_tabsets/app.R
new file mode 100644
index 0000000..e1b89d6
--- /dev/null
+++ b/inst/examples/06_tabsets/app.R
@@ -0,0 +1,92 @@
+library(shiny)
+
+# Define UI for random distribution app ----
+ui <- fluidPage(
+
+  # App title ----
+  titlePanel("Tabsets"),
+
+  # Sidebar layout with input and output definitions ----
+  sidebarLayout(
+
+    # Sidebar panel for inputs ----
+    sidebarPanel(
+
+      # Input: Select the random distribution type ----
+      radioButtons("dist", "Distribution type:",
+                   c("Normal" = "norm",
+                     "Uniform" = "unif",
+                     "Log-normal" = "lnorm",
+                     "Exponential" = "exp")),
+
+      # br() element to introduce extra vertical spacing ----
+      br(),
+
+      # Input: Slider for the number of observations to generate ----
+      sliderInput("n",
+                  "Number of observations:",
+                  value = 500,
+                  min = 1,
+                  max = 1000)
+
+    ),
+
+    # Main panel for displaying outputs ----
+    mainPanel(
+
+      # Output: Tabset w/ plot, summary, and table ----
+      tabsetPanel(type = "tabs",
+                  tabPanel("Plot", plotOutput("plot")),
+                  tabPanel("Summary", verbatimTextOutput("summary")),
+                  tabPanel("Table", tableOutput("table"))
+      )
+
+    )
+  )
+)
+
+# Define server logic for random distribution app ----
+server <- function(input, output) {
+
+  # Reactive expression to generate the requested distribution ----
+  # This is called whenever the inputs change. The output functions
+  # defined below then use the value computed from this expression
+  d <- reactive({
+    dist <- switch(input$dist,
+                   norm = rnorm,
+                   unif = runif,
+                   lnorm = rlnorm,
+                   exp = rexp,
+                   rnorm)
+
+    dist(input$n)
+  })
+
+  # Generate a plot of the data ----
+  # Also uses the inputs to build the plot label. Note that the
+  # dependencies on the inputs and the data reactive expression are
+  # both tracked, and all expressions are called in the sequence
+  # implied by the dependency graph.
+  output$plot <- renderPlot({
+    dist <- input$dist
+    n <- input$n
+
+    hist(d(),
+         main = paste("r", dist, "(", n, ")", sep = ""),
+         col = "#75AADB", border = "white")
+  })
+
+  # Generate a summary of the data ----
+  output$summary <- renderPrint({
+    summary(d())
+  })
+
+  # Generate an HTML table view of the data ----
+  output$table <- renderTable({
+    d()
+  })
+
+}
+
+# Create Shiny app ----
+shinyApp(ui, server)
diff --git a/inst/examples/06_tabsets/server.R b/inst/examples/06_tabsets/server.R
deleted file mode 100644
index db07a2d..0000000
--- a/inst/examples/06_tabsets/server.R
+++ /dev/null
@@ -1,44 +0,0 @@
-library(shiny)
-
-# Define server logic for random distribution application
-function(input, output) {
-  
-  # Reactive expression to generate the requested distribution.
-  # This is called whenever the inputs change. The output
-  # functions defined below then all use the value computed from
-  # this expression
-  data <- reactive({
-    dist <- switch(input$dist,
-                   norm = rnorm,
-                   unif = runif,
-                   lnorm = rlnorm,
-                   exp = rexp,
-                   rnorm)
-    
-    dist(input$n)
-  })
-  
-  # Generate a plot of the data. Also uses the inputs to build
-  # the plot label. Note that the dependencies on both the inputs
-  # and the data reactive expression are both tracked, and
-  # all expressions are called in the sequence implied by the
-  # dependency graph
-  output$plot <- renderPlot({
-    dist <- input$dist
-    n <- input$n
-    
-    hist(data(), 
-         main=paste('r', dist, '(', n, ')', sep=''))
-  })
-  
-  # Generate a summary of the data
-  output$summary <- renderPrint({
-    summary(data())
-  })
-  
-  # Generate an HTML table view of the data
-  output$table <- renderTable({
-    data.frame(x=data())
-  })
-  
-}
diff --git a/inst/examples/06_tabsets/ui.R b/inst/examples/06_tabsets/ui.R
deleted file mode 100644
index 732d74f..0000000
--- a/inst/examples/06_tabsets/ui.R
+++ /dev/null
@@ -1,38 +0,0 @@
-library(shiny)
-
-# Define UI for random distribution application 
-fluidPage(
-    
-  # Application title
-  titlePanel("Tabsets"),
-  
-  # Sidebar with controls to select the random distribution type
-  # and number of observations to generate. Note the use of the
-  # br() element to introduce extra vertical spacing
-  sidebarLayout(
-    sidebarPanel(
-      radioButtons("dist", "Distribution type:",
-                   c("Normal" = "norm",
-                     "Uniform" = "unif",
-                     "Log-normal" = "lnorm",
-                     "Exponential" = "exp")),
-      br(),
-      
-      sliderInput("n", 
-                  "Number of observations:", 
-                   value = 500,
-                   min = 1, 
-                   max = 1000)
-    ),
-    
-    # Show a tabset that includes a plot, summary, and table view
-    # of the generated distribution
-    mainPanel(
-      tabsetPanel(type = "tabs", 
-        tabPanel("Plot", plotOutput("plot")), 
-        tabPanel("Summary", verbatimTextOutput("summary")), 
-        tabPanel("Table", tableOutput("table"))
-      )
-    )
-  )
-)
diff --git a/inst/examples/07_widgets/app.R b/inst/examples/07_widgets/app.R
new file mode 100644
index 0000000..4ad8e88
--- /dev/null
+++ b/inst/examples/07_widgets/app.R
@@ -0,0 +1,82 @@
+library(shiny)
+
+# Define UI for dataset viewer app ----
+ui <- fluidPage(
+
+  # App title ----
+  titlePanel("More Widgets"),
+
+  # Sidebar layout with input and output definitions ----
+  sidebarLayout(
+
+    # Sidebar panel for inputs ----
+    sidebarPanel(
+
+      # Input: Select a dataset ----
+      selectInput("dataset", "Choose a dataset:",
+                  choices = c("rock", "pressure", "cars")),
+
+      # Input: Specify the number of observations to view ----
+      numericInput("obs", "Number of observations to view:", 10),
+
+      # Include clarifying text ----
+      helpText("Note: while the data view will show only the specified",
+               "number of observations, the summary will still be based",
+               "on the full dataset."),
+
+      # Input: actionButton() to defer the rendering of output ----
+      # until the user explicitly clicks the button (rather than
+      # doing it immediately when inputs change). This is useful if
+      # the computations required to render output are inordinately
+      # time-consuming.
+      actionButton("update", "Update View")
+
+    ),
+
+    # Main panel for displaying outputs ----
+    mainPanel(
+
+      # Output: Header + summary of distribution ----
+      h4("Summary"),
+      verbatimTextOutput("summary"),
+
+      # Output: Header + table of distribution ----
+      h4("Observations"),
+      tableOutput("view")
+    )
+
+  )
+)
+
+# Define server logic to summarize and view selected dataset ----
+server <- function(input, output) {
+
+  # Return the requested dataset ----
+  # Note that we use eventReactive() here, which depends on
+  # input$update (the action button), so that the output is only
+  # updated when the user clicks the button
+  datasetInput <- eventReactive(input$update, {
+    switch(input$dataset,
+           "rock" = rock,
+           "pressure" = pressure,
+           "cars" = cars)
+  }, ignoreNULL = FALSE)
+
+  # Generate a summary of the dataset ----
+  output$summary <- renderPrint({
+    dataset <- datasetInput()
+    summary(dataset)
+  })
+
+  # Show the first "n" observations ----
+  # The use of isolate() is necessary because we don't want the table
+  # to update whenever input$obs changes (only when the user clicks
+  # the action button)
+  output$view <- renderTable({
+    head(datasetInput(), n = isolate(input$obs))
+  })
+
+}
+
+# Create Shiny app ----
+shinyApp(ui, server)
diff --git a/inst/examples/07_widgets/server.R b/inst/examples/07_widgets/server.R
deleted file mode 100644
index 238b5bc..0000000
--- a/inst/examples/07_widgets/server.R
+++ /dev/null
@@ -1,32 +0,0 @@
-library(shiny)
-library(datasets)
-
-# Define server logic required to summarize and view the
-# selected dataset
-function(input, output) {
-
-  # Return the requested dataset. Note that we use `eventReactive()`
-  # here, which takes a dependency on input$update (the action
-  # button), so that the output is only updated when the user
-  # clicks the button.
-  datasetInput <- eventReactive(input$update, {
-    switch(input$dataset,
-           "rock" = rock,
-           "pressure" = pressure,
-           "cars" = cars)
-  }, ignoreNULL = FALSE)
-
-  # Generate a summary of the dataset
-  output$summary <- renderPrint({
-    dataset <- datasetInput()
-    summary(dataset)
-  })
-
-  # Show the first "n" observations. The use of `isolate()` here
-  # is necessary because we don't want the table to update
-  # whenever input$obs changes (only when the user clicks the
-  # action button).
-  output$view <- renderTable({
-    head(datasetInput(), n = isolate(input$obs))
-  })
-}
diff --git a/inst/examples/07_widgets/ui.R b/inst/examples/07_widgets/ui.R
deleted file mode 100644
index be59cf1..0000000
--- a/inst/examples/07_widgets/ui.R
+++ /dev/null
@@ -1,43 +0,0 @@
-library(shiny)
-
-# Define UI for dataset viewer application
-fluidPage(
-
-  # Application title.
-  titlePanel("More Widgets"),
-
-  # Sidebar with controls to select a dataset and specify the
-  # number of observations to view. The helpText function is
-  # also used to include clarifying text. Most notably, the
-  # inclusion of an actionButton defers the rendering of output
-  # until the user explicitly clicks the button (rather than
-  # doing it immediately when inputs change). This is useful if
-  # the computations required to render output are inordinately
-  # time-consuming.
-  sidebarLayout(
-    sidebarPanel(
-      selectInput("dataset", "Choose a dataset:",
-                  choices = c("rock", "pressure", "cars")),
-
-      numericInput("obs", "Number of observations to view:", 10),
-
-      helpText("Note: while the data view will show only the specified",
-               "number of observations, the summary will still be based",
-               "on the full dataset."),
-
-      actionButton("update", "Update View")
-    ),
-
-    # Show a summary of the dataset and an HTML table with the
-    # requested number of observations. Note the use of the h4
-    # function to provide an additional header above each output
-    # section.
-    mainPanel(
-      h4("Summary"),
-      verbatimTextOutput("summary"),
-
-      h4("Observations"),
-      tableOutput("view")
-    )
-  )
-)
diff --git a/inst/examples/08_html/Readme.md b/inst/examples/08_html/Readme.md
index c862507..c2c898d 100644
--- a/inst/examples/08_html/Readme.md
+++ b/inst/examples/08_html/Readme.md
@@ -1,4 +1 @@
-Normally we use the built-in functions, such as `textInput()`, to generate
-the HTML UI in the R script `ui.R`. Actually **shiny** also works with a
-custom HTML page `www/index.html`. See [the
-tutorial](http://rstudio.github.io/shiny/tutorial/#html-ui) for more details.
+Normally we use the built-in functions, such as `textInput()`, to generate the HTML UI in the R script `ui.R`. Actually **shiny** also works with a custom HTML page `www/index.html`. See [the tutorial](http://shiny.rstudio.com/tutorial/) for more details.
diff --git a/inst/examples/08_html/app.R b/inst/examples/08_html/app.R
new file mode 100644
index 0000000..be8d378
--- /dev/null
+++ b/inst/examples/08_html/app.R
@@ -0,0 +1,47 @@
+library(shiny)
+
+# Define server logic for random distribution app ----
+server <- function(input, output) {
+
+  # Reactive expression to generate the requested distribution ----
+  # This is called whenever the inputs change. The output functions
+  # defined below then use the value computed from this expression
+  d <- reactive({
+    dist <- switch(input$dist,
+                   norm = rnorm,
+                   unif = runif,
+                   lnorm = rlnorm,
+                   exp = rexp,
+                   rnorm)
+
+    dist(input$n)
+  })
+
+  # Generate a plot of the data ----
+  # Also uses the inputs to build the plot label. Note that the
+  # dependencies on the inputs and the data reactive expression are
+  # both tracked, and all expressions are called in the sequence
+  # implied by the dependency graph.
+  output$plot <- renderPlot({
+    dist <- input$dist
+    n <- input$n
+
+    hist(d(),
+         main = paste("r", dist, "(", n, ")", sep = ""),
+         col = "#75AADB", border = "white")
+  })
+
+  # Generate a summary of the data ----
+  output$summary <- renderPrint({
+    summary(d())
+  })
+
+  # Generate an HTML table view of the head of the data ----
+  output$table <- renderTable({
+    head(data.frame(x = d()))
+  })
+
+}
+
+# Create Shiny app ----
+shinyApp(ui = htmlTemplate("www/index.html"), server)
diff --git a/inst/examples/08_html/server.R b/inst/examples/08_html/server.R
deleted file mode 100644
index 6abf41a..0000000
--- a/inst/examples/08_html/server.R
+++ /dev/null
@@ -1,42 +0,0 @@
-library(shiny)
-
-# Define server logic for random distribution application
-function(input, output) {
-  
-  # Reactive expression to generate the requested distribution. This is 
-  # called whenever the inputs change. The output expressions defined 
-  # below then all used the value computed from this expression
-  data <- reactive({
-    dist <- switch(input$dist,
-                   norm = rnorm,
-                   unif = runif,
-                   lnorm = rlnorm,
-                   exp = rexp,
-                   rnorm)
-    
-    dist(input$n)
-  })
-  
-  # Generate a plot of the data. Also uses the inputs to build the 
-  # plot label. Note that the dependencies on both the inputs and
-  # the data reactive expression are both tracked, and all expressions 
-  # are called in the sequence implied by the dependency graph
-  output$plot <- renderPlot({
-    dist <- input$dist
-    n <- input$n
-    
-    hist(data(), 
-         main=paste('r', dist, '(', n, ')', sep=''))
-  })
-  
-  # Generate a summary of the data
-  output$summary <- renderPrint({
-    summary(data())
-  })
-  
-  # Generate an HTML table view of the data
-  output$table <- renderTable({
-    data.frame(x=data())
-  })
-  
-}
diff --git a/inst/examples/08_html/www/index.html b/inst/examples/08_html/www/index.html
index bbf298d..b32adc3 100644
--- a/inst/examples/08_html/www/index.html
+++ b/inst/examples/08_html/www/index.html
@@ -3,13 +3,13 @@
 <head>
   <script src="shared/jquery.js" type="text/javascript"></script>
   <script src="shared/shiny.js" type="text/javascript"></script>
-  <link rel="stylesheet" type="text/css" href="shared/shiny.css"/> 
+  <link rel="stylesheet" type="text/css" href="shared/shiny.css"/>
 </head>
- 
+
 <body>
 
   <h1>HTML UI</h1>
- 
+
   <p>
     <label>Distribution type:</label><br />
     <select name="dist">
@@ -17,22 +17,25 @@
       <option value="unif">Uniform</option>
       <option value="lnorm">Log-normal</option>
       <option value="exp">Exponential</option>
-    </select> 
+    </select>
   </p>
- 
+
   <p>
- 
-    <label>Number of observations:</label><br /> 
+
+    <label>Number of observations:</label><br />
     <input type="number" name="n" value="500" min="1" max="1000" />
 
   </p>
- 
-  <pre id="summary" class="shiny-text-output"></pre> 
-  
-  <div id="plot" class="shiny-plot-output" 
-       style="width: 100%; height: 400px"></div> 
-  
+
+  <h3>Summary of data:</h3>
+  <pre id="summary" class="shiny-text-output"></pre>
+
+  <h3>Plot of data:</h3>
+  <div id="plot" class="shiny-plot-output"
+       style="width: 100%; height: 300px"></div>
+
+  <h3>Head of data:</h3>
   <div id="table" class="shiny-html-output"></div>
- 
+
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/inst/examples/09_upload/Readme.md b/inst/examples/09_upload/Readme.md
index f1310cd..3c45f36 100644
--- a/inst/examples/09_upload/Readme.md
+++ b/inst/examples/09_upload/Readme.md
@@ -1,4 +1,3 @@
 We can add a file upload input in the UI using the function `fileInput()`,
-e.g. `fileInput('foo')`. In `server.R`, we can access the uploaded files via
-`input$foo`. See [the
-tutorial](http://rstudio.github.io/shiny/tutorial/#uploads) for more details.
+e.g. `fileInput('foo')`. In the `server` function, we can access the 
+uploaded files via `input$foo`.
diff --git a/inst/examples/09_upload/app.R b/inst/examples/09_upload/app.R
new file mode 100644
index 0000000..64d5ead
--- /dev/null
+++ b/inst/examples/09_upload/app.R
@@ -0,0 +1,92 @@
+library(shiny)
+
+# Define UI for data upload app ----
+ui <- fluidPage(
+
+  # App title ----
+  titlePanel("Uploading Files"),
+
+  # Sidebar layout with input and output definitions ----
+  sidebarLayout(
+
+    # Sidebar panel for inputs ----
+    sidebarPanel(
+
+      # Input: Select a file ----
+      fileInput("file1", "Choose CSV File",
+                multiple = TRUE,
+                accept = c("text/csv",
+                         "text/comma-separated-values,text/plain",
+                         ".csv")),
+
+      # Horizontal line ----
+      tags$hr(),
+
+      # Input: Checkbox if file has header ----
+      checkboxInput("header", "Header", TRUE),
+
+      # Input: Select separator ----
+      radioButtons("sep", "Separator",
+                   choices = c(Comma = ",",
+                               Semicolon = ";",
+                               Tab = "\t"),
+                   selected = ","),
+
+      # Input: Select quotes ----
+      radioButtons("quote", "Quote",
+                   choices = c(None = "",
+                               "Double Quote" = '"',
+                               "Single Quote" = "'"),
+                   selected = '"'),
+
+      # Horizontal line ----
+      tags$hr(),
+
+      # Input: Select number of rows to display ----
+      radioButtons("disp", "Display",
+                   choices = c(Head = "head",
+                               All = "all"),
+                   selected = "head")
+
+    ),
+
+    # Main panel for displaying outputs ----
+    mainPanel(
+
+      # Output: Data file ----
+      tableOutput("contents")
+
+    )
+
+  )
+)
+
+# Define server logic to read selected file ----
+server <- function(input, output) {
+
+  output$contents <- renderTable({
+
+    # input$file1 will be NULL initially. After the user selects
+    # and uploads a file, head of that data file by default,
+    # or all rows if selected, will be shown.
+
+    req(input$file1)
+
+    df <- read.csv(input$file1$datapath,
+             header = input$header,
+             sep = input$sep,
+             quote = input$quote)
+
+    if(input$disp == "head") {
+      return(head(df))
+    }
+    else {
+      return(df)
+    }
+
+  })
+
+}
+
+# Create Shiny app ----
+shinyApp(ui, server)
diff --git a/inst/examples/09_upload/server.R b/inst/examples/09_upload/server.R
deleted file mode 100644
index 869f7fe..0000000
--- a/inst/examples/09_upload/server.R
+++ /dev/null
@@ -1,20 +0,0 @@
-library(shiny)
-
-function(input, output) {
-  output$contents <- renderTable({
-    
-    # input$file1 will be NULL initially. After the user selects
-    # and uploads a file, it will be a data frame with 'name',
-    # 'size', 'type', and 'datapath' columns. The 'datapath'
-    # column will contain the local filenames where the data can
-    # be found.
-
-    inFile <- input$file1
-
-    if (is.null(inFile))
-      return(NULL)
-    
-    read.csv(inFile$datapath, header=input$header, sep=input$sep, 
-				 quote=input$quote)
-  })
-}
diff --git a/inst/examples/09_upload/ui.R b/inst/examples/09_upload/ui.R
deleted file mode 100644
index a53bf1d..0000000
--- a/inst/examples/09_upload/ui.R
+++ /dev/null
@@ -1,28 +0,0 @@
-library(shiny)
-
-fluidPage(
-  titlePanel("Uploading Files"),
-  sidebarLayout(
-    sidebarPanel(
-      fileInput('file1', 'Choose CSV File',
-                accept=c('text/csv', 
-								 'text/comma-separated-values,text/plain', 
-								 '.csv')),
-      tags$hr(),
-      checkboxInput('header', 'Header', TRUE),
-      radioButtons('sep', 'Separator',
-                   c(Comma=',',
-                     Semicolon=';',
-                     Tab='\t'),
-                   ','),
-      radioButtons('quote', 'Quote',
-                   c(None='',
-                     'Double Quote'='"',
-                     'Single Quote'="'"),
-                   '"')
-    ),
-    mainPanel(
-      tableOutput('contents')
-    )
-  )
-)
diff --git a/inst/examples/10_download/Readme.md b/inst/examples/10_download/Readme.md
index 7c9ca3c..fceb331 100644
--- a/inst/examples/10_download/Readme.md
+++ b/inst/examples/10_download/Readme.md
@@ -1,4 +1,2 @@
 We can add a download button to the UI using `downloadButton()`, and write
-the content of the file in `downloadHandler()` in `server.R`. See [the
-tutorial](http://rstudio.github.io/shiny/tutorial/#downloads) for more
-details.
+the content of the file in `downloadHandler()` in the `server` function.
diff --git a/inst/examples/10_download/app.R b/inst/examples/10_download/app.R
new file mode 100644
index 0000000..b5a6dd1
--- /dev/null
+++ b/inst/examples/10_download/app.R
@@ -0,0 +1,63 @@
+library(shiny)
+
+# Define UI for data download app ----
+ui <- fluidPage(
+
+  # App title ----
+  titlePanel("Downloading Data"),
+
+  # Sidebar layout with input and output definitions ----
+  sidebarLayout(
+
+    # Sidebar panel for inputs ----
+    sidebarPanel(
+
+      # Input: Choose dataset ----
+      selectInput("dataset", "Choose a dataset:",
+                  choices = c("rock", "pressure", "cars")),
+
+      # Button
+      downloadButton("downloadData", "Download")
+
+    ),
+
+    # Main panel for displaying outputs ----
+    mainPanel(
+
+      tableOutput("table")
+
+    )
+
+  )
+)
+
+# Define server logic to display and download selected file ----
+server <- function(input, output) {
+
+  # Reactive value for selected dataset ----
+  datasetInput <- reactive({
+    switch(input$dataset,
+           "rock" = rock,
+           "pressure" = pressure,
+           "cars" = cars)
+  })
+
+  # Table of selected dataset ----
+  output$table <- renderTable({
+    datasetInput()
+  })
+
+  # Downloadable csv of selected dataset ----
+  output$downloadData <- downloadHandler(
+    filename = function() {
+      paste(input$dataset, ".csv", sep = "")
+    },
+    content = function(file) {
+      write.csv(datasetInput(), file, row.names = FALSE)
+    }
+  )
+
+}
+
+# Create Shiny app ----
+shinyApp(ui, server)
diff --git a/inst/examples/10_download/server.R b/inst/examples/10_download/server.R
deleted file mode 100644
index 9cb0f06..0000000
--- a/inst/examples/10_download/server.R
+++ /dev/null
@@ -1,21 +0,0 @@
-function(input, output) {
-  datasetInput <- reactive({
-    switch(input$dataset,
-           "rock" = rock,
-           "pressure" = pressure,
-           "cars" = cars)
-  })
-  
-  output$table <- renderTable({
-    datasetInput()
-  })
-  
-  output$downloadData <- downloadHandler(
-    filename = function() { 
-		 paste(input$dataset, '.csv', sep='') 
-	 },
-    content = function(file) {
-      write.csv(datasetInput(), file)
-    }
-  )
-}
diff --git a/inst/examples/10_download/ui.R b/inst/examples/10_download/ui.R
deleted file mode 100644
index 0ec8f89..0000000
--- a/inst/examples/10_download/ui.R
+++ /dev/null
@@ -1,13 +0,0 @@
-fluidPage(
-  titlePanel('Downloading Data'),
-  sidebarLayout(
-    sidebarPanel(
-      selectInput("dataset", "Choose a dataset:", 
-                  choices = c("rock", "pressure", "cars")),
-      downloadButton('downloadData', 'Download')
-    ),
-    mainPanel(
-      tableOutput('table')
-    )
-  )
-)
diff --git a/inst/examples/11_timer/app.R b/inst/examples/11_timer/app.R
new file mode 100644
index 0000000..6aaf379
--- /dev/null
+++ b/inst/examples/11_timer/app.R
@@ -0,0 +1,21 @@
+library(shiny)
+
+# Define UI for displaying current time ----
+ui <- fluidPage(
+
+  h2(textOutput("currentTime"))
+
+)
+
+# Define server logic to show current time, update every second ----
+server <- function(input, output, session) {
+
+  output$currentTime <- renderText({
+    invalidateLater(1000, session)
+    paste("The current time is", Sys.time())
+  })
+
+}
+
+# Create Shiny app ----
+shinyApp(ui, server)
diff --git a/inst/examples/11_timer/server.R b/inst/examples/11_timer/server.R
deleted file mode 100644
index 845d990..0000000
--- a/inst/examples/11_timer/server.R
+++ /dev/null
@@ -1,6 +0,0 @@
-function(input, output, session) {
-  output$currentTime <- renderText({
-    invalidateLater(1000, session)
-    paste("The current time is", Sys.time())
-  })
-}
diff --git a/inst/examples/11_timer/ui.R b/inst/examples/11_timer/ui.R
deleted file mode 100644
index 3955c68..0000000
--- a/inst/examples/11_timer/ui.R
+++ /dev/null
@@ -1,3 +0,0 @@
-fluidPage(
-  textOutput("currentTime")
-)
diff --git a/inst/staticdocs/index.r b/inst/staticdocs/index.r
index a50447e..1bcefaa 100644
--- a/inst/staticdocs/index.r
+++ b/inst/staticdocs/index.r
@@ -57,9 +57,12 @@ sd_section("UI Inputs",
     "updateSelectInput",
     "updateSliderInput",
     "updateTabsetPanel",
+    "insertTab",
+    "showTab",
     "updateTextInput",
     "updateTextAreaInput",
-    "updateQueryString"
+    "updateQueryString",
+    "getQueryString"
   )
 )
 sd_section("UI Outputs",
@@ -121,6 +124,7 @@ sd_section("Reactive programming",
     "reactive",
     "observe",
     "observeEvent",
+    "reactiveVal",
     "reactiveValues",
     "reactiveValuesToList",
     "is.reactivevalues",
@@ -152,7 +156,8 @@ sd_section("Running",
     "runGadget",
     "runUrl",
     "stopApp",
-    "viewer"
+    "viewer",
+    "isRunning"
   )
 )
 sd_section("Bookmarking state",
@@ -191,10 +196,16 @@ sd_section("Utility functions",
     "parseQueryString",
     "plotPNG",
     "exportTestValues",
+    "setSerializer",
+    "snapshotExclude",
+    "snapshotPreprocessInput",
+    "snapshotPreprocessOutput",
+    "markOutputAttrs",
     "repeatable",
     "shinyDeprecated",
     "serverInfo",
-    "shiny-options"
+    "shiny-options",
+    "onStop"
   )
 )
 sd_section("Plot interaction",
diff --git a/inst/www/shared/ionrangeslider/css/ion.rangeSlider.css b/inst/www/shared/ionrangeslider/css/ion.rangeSlider.css
index dd56bef..68fd119 100644
--- a/inst/www/shared/ionrangeslider/css/ion.rangeSlider.css
+++ b/inst/www/shared/ionrangeslider/css/ion.rangeSlider.css
@@ -141,6 +141,7 @@
     line-height: 0 !important;
     padding: 0 !important;
     margin: 0 !important;
+    overflow: hidden;
     outline: none !important;
     z-index: -9999 !important;
     background: none !important;
diff --git a/inst/www/shared/ionrangeslider/js/ion.rangeSlider.js b/inst/www/shared/ionrangeslider/js/ion.rangeSlider.js
index d8a36b4..e998866 100644
--- a/inst/www/shared/ionrangeslider/js/ion.rangeSlider.js
+++ b/inst/www/shared/ionrangeslider/js/ion.rangeSlider.js
@@ -1,6 +1,6 @@
-// Ion.RangeSlider
-// version 2.1.2 Build: 350
-// © Denis Ineshin, 2015
+// Ion.RangeSlider
+// version 2.1.6 Build: 369
+// © Denis Ineshin, 2016
 // https://github.com/IonDen
 //
 // Project page:    http://ionden.com/a/plugins/ion.rangeSlider/en.html
@@ -10,7 +10,17 @@
 // http://ionden.com/a/plugins/licence-en.html
 // =====================================================================================================================
 
-;(function ($, document, window, navigator, undefined) {
+;(function(factory) {
+    if (typeof define === "function" && define.amd) {
+        define(["jquery"], function (jQuery) {
+            return factory(jQuery, document, window, navigator);
+        });
+    } else if (typeof exports === "object") {
+        factory(require("jquery"), document, window, navigator);
+    } else {
+        factory(jQuery, document, window, navigator);
+    }
+} (function ($, document, window, navigator, undefined) {
     "use strict";
 
     // =================================================================================================================
@@ -146,7 +156,7 @@
      * @constructor
      */
     var IonRangeSlider = function (input, options, plugin_count) {
-        this.VERSION = "2.1.2";
+        this.VERSION = "2.1.6";
         this.input = input;
         this.plugin_count = plugin_count;
         this.current_plugin = 0;
@@ -161,12 +171,15 @@
         this.no_diapason = false;
         this.is_key = false;
         this.is_update = false;
+        this.is_first_update = true;
         this.is_start = true;
         this.is_finish = false;
         this.is_active = false;
         this.is_resize = false;
         this.is_click = false;
 
+        options = options || {};
+
         // cache for links to all DOM elements
         this.$cache = {
             win: $(window),
@@ -318,6 +331,11 @@
         };
 
 
+        // check if base element is input
+        if ($inp[0].nodeName !== "INPUT") {
+            console && console.warn && console.warn("Base element should be <input>!", $inp[0]);
+        }
+
 
         // config from data-attributes extends js config
         config_from_data = {
@@ -375,16 +393,15 @@
 
         for (prop in config_from_data) {
             if (config_from_data.hasOwnProperty(prop)) {
-                if (!config_from_data[prop] && config_from_data[prop] !== 0) {
+                if (config_from_data[prop] === undefined || config_from_data[prop] === "") {
                     delete config_from_data[prop];
                 }
             }
         }
 
 
-
         // input value extends default config
-        if (val) {
+        if (val !== undefined && val !== "") {
             val = val.split(config_from_data.input_values_separator || options.input_values_separator || ";");
 
             if (val[0] && val[0] == +val[0]) {
@@ -416,6 +433,7 @@
 
 
         // validate config, to be sure that all data types are correct
+        this.update_check = {};
         this.validate();
 
 
@@ -447,7 +465,7 @@
         /**
          * Starts or updates the plugin instance
          *
-         * @param is_update {boolean}
+         * @param [is_update] {boolean}
          */
         init: function (is_update) {
             this.no_diapason = false;
@@ -734,7 +752,6 @@
 
             // callbacks call
             if ($.contains(this.$cache.cont[0], e.target) || this.dragging) {
-                this.is_finish = true;
                 this.callOnFinish();
             }
             
@@ -761,7 +778,7 @@
             }
 
             if (!target) {
-                target = this.target;
+                target = this.target || "from";
             }
 
             this.current_plugin = this.plugin_count;
@@ -948,6 +965,12 @@
             this.calcPointerPercent();
             var handle_x = this.getHandleX();
 
+
+            if (this.target === "both") {
+                this.coords.p_gap = 0;
+                handle_x = this.getHandleX();
+            }
+
             if (this.target === "click") {
                 this.coords.p_gap = this.coords.p_handle / 2;
                 handle_x = this.getHandleX();
@@ -1035,7 +1058,7 @@
                         break;
                     }
 
-                    handle_x = this.toFixed(handle_x + (this.coords.p_handle * 0.1));
+                    handle_x = this.toFixed(handle_x + (this.coords.p_handle * 0.001));
 
                     this.coords.p_from_real = this.convertToRealPercent(handle_x) - this.coords.p_gap_left;
                     this.coords.p_from_real = this.calcWithStep(this.coords.p_from_real);
@@ -1313,13 +1336,6 @@
                     this.$cache.s_single[0].style.left = this.coords.p_single_fake + "%";
 
                     this.$cache.single[0].style.left = this.labels.p_single_left + "%";
-
-                    if (this.options.values.length) {
-                        this.$cache.input.prop("value", this.result.from_value);
-                    } else {
-                        this.$cache.input.prop("value", this.result.from);
-                    }
-                    this.$cache.input.data("from", this.result.from);
                 } else {
                     this.$cache.s_from[0].style.left = this.coords.p_from_fake + "%";
                     this.$cache.s_to[0].style.left = this.coords.p_to_fake + "%";
@@ -1332,18 +1348,13 @@
                     }
 
                     this.$cache.single[0].style.left = this.labels.p_single_left + "%";
-
-                    if (this.options.values.length) {
-                        this.$cache.input.prop("value", this.result.from_value + this.options.input_values_separator + this.result.to_value);
-                    } else {
-                        this.$cache.input.prop("value", this.result.from + this.options.input_values_separator + this.result.to);
-                    }
-                    this.$cache.input.data("from", this.result.from);
-                    this.$cache.input.data("to", this.result.to);
                 }
 
+                this.writeToInput();
+
                 if ((this.old_from !== this.result.from || this.old_to !== this.result.to) && !this.is_start) {
                     this.$cache.input.trigger("change");
+                    this.$cache.input.trigger("input");
                 }
 
                 this.old_from = this.result.from;
@@ -1353,9 +1364,10 @@
                 if (!this.is_resize && !this.is_update && !this.is_start && !this.is_finish) {
                     this.callOnChange();
                 }
-                if (this.is_key || this.is_click) {
+                if (this.is_key || this.is_click || this.is_first_update) {
                     this.is_key = false;
                     this.is_click = false;
+                    this.is_first_update = false;
                     this.callOnFinish();
                 }
 
@@ -1467,6 +1479,8 @@
                             this.$cache.from[0].style.visibility = "visible";
                         } else if (this.target === "to") {
                             this.$cache.to[0].style.visibility = "visible";
+                        } else if (!this.target) {
+                            this.$cache.from[0].style.visibility = "visible";
                         }
                         this.$cache.single[0].style.visibility = "hidden";
                         max = to_left;
@@ -1561,25 +1575,57 @@
 
 
 
+        /**
+         * Write values to input element
+         */
+        writeToInput: function () {
+            if (this.options.type === "single") {
+                if (this.options.values.length) {
+                    this.$cache.input.prop("value", this.result.from_value);
+                } else {
+                    this.$cache.input.prop("value", this.result.from);
+                }
+                this.$cache.input.data("from", this.result.from);
+            } else {
+                if (this.options.values.length) {
+                    this.$cache.input.prop("value", this.result.from_value + this.options.input_values_separator + this.result.to_value);
+                } else {
+                    this.$cache.input.prop("value", this.result.from + this.options.input_values_separator + this.result.to);
+                }
+                this.$cache.input.data("from", this.result.from);
+                this.$cache.input.data("to", this.result.to);
+            }
+        },
+
+
+
         // =============================================================================================================
         // Callbacks
 
         callOnStart: function () {
+            this.writeToInput();
+
             if (this.options.onStart && typeof this.options.onStart === "function") {
                 this.options.onStart(this.result);
             }
         },
         callOnChange: function () {
+            this.writeToInput();
+
             if (this.options.onChange && typeof this.options.onChange === "function") {
                 this.options.onChange(this.result);
             }
         },
         callOnFinish: function () {
+            this.writeToInput();
+
             if (this.options.onFinish && typeof this.options.onFinish === "function") {
                 this.options.onFinish(this.result);
             }
         },
         callOnUpdate: function () {
+            this.writeToInput();
+
             if (this.options.onUpdate && typeof this.options.onUpdate === "function") {
                 this.options.onUpdate(this.result);
             }
@@ -1587,6 +1633,7 @@
 
 
 
+
         // =============================================================================================================
         // Service methods
 
@@ -1796,7 +1843,7 @@
         },
 
         toFixed: function (num) {
-            num = num.toFixed(9);
+            num = num.toFixed(20);
             return +num;
         },
 
@@ -1884,32 +1931,37 @@
                 o.from = o.min;
             }
 
-            if (typeof o.to !== "number" || isNaN(o.from)) {
+            if (typeof o.to !== "number" || isNaN(o.to)) {
                 o.to = o.max;
             }
 
             if (o.type === "single") {
 
-                if (o.from < o.min) {
-                    o.from = o.min;
-                }
-
-                if (o.from > o.max) {
-                    o.from = o.max;
-                }
+                if (o.from < o.min) o.from = o.min;
+                if (o.from > o.max) o.from = o.max;
 
             } else {
 
-                if (o.from < o.min || o.from > o.max) {
-                    o.from = o.min;
-                }
-                if (o.to > o.max || o.to < o.min) {
-                    o.to = o.max;
-                }
-                if (o.from > o.to) {
-                    o.from = o.to;
+                if (o.from < o.min) o.from = o.min;
+                if (o.from > o.max) o.from = o.max;
+
+                if (o.to < o.min) o.to = o.min;
+                if (o.to > o.max) o.to = o.max;
+
+                if (this.update_check.from) {
+
+                    if (this.update_check.from !== o.from) {
+                        if (o.from > o.to) o.from = o.to;
+                    }
+                    if (this.update_check.to !== o.to) {
+                        if (o.to < o.from) o.to = o.from;
+                    }
+
                 }
 
+                if (o.from > o.to) o.from = o.to;
+                if (o.to < o.from) o.to = o.from;
+
             }
 
             if (typeof o.step !== "number" || isNaN(o.step) || !o.step || o.step < 0) {
@@ -2167,7 +2219,10 @@
 
             for (i = 0; i < num; i++) {
                 label = this.$cache.grid_labels[i][0];
-                label.style.marginLeft = -this.coords.big_x[i] + "%";
+
+                if (this.coords.big_x[i] !== Number.POSITIVE_INFINITY) {
+                    label.style.marginLeft = -this.coords.big_x[i] + "%";
+                }
             }
         },
 
@@ -2229,6 +2284,8 @@
 
             this.options.from = this.result.from;
             this.options.to = this.result.to;
+            this.update_check.from = this.result.from;
+            this.update_check.to = this.result.to;
 
             this.options = $.extend(this.options, options);
             this.validate();
@@ -2306,4 +2363,4 @@
             };
     }());
 
-} (jQuery, document, window, navigator));
+}));
diff --git a/inst/www/shared/ionrangeslider/js/ion.rangeSlider.min.js b/inst/www/shared/ionrangeslider/js/ion.rangeSlider.min.js
index f70c628..f86fdc5 100644
--- a/inst/www/shared/ionrangeslider/js/ion.rangeSlider.min.js
+++ b/inst/www/shared/ionrangeslider/js/ion.rangeSlider.min.js
@@ -1,2 +1,2 @@
-!function(a,b,c,d,e){"use strict";var f=0,g=function(){var b,c=d.userAgent,e=/msie\s\d+/i;return c.search(e)>0&&(b=e.exec(c).toString(),b=b.split(" ")[1],b<9)&&(a("html").addClass("lt-ie9"),!0)}();Function.prototype.bind||(Function.prototype.bind=function(a){var b=this,c=[].slice;if("function"!=typeof b)throw new TypeError;var d=c.call(arguments,1),e=function(){if(this instanceof e){var f=function(){};f.prototype=b.prototype;var g=new f,h=b.apply(g,d.concat(c.call(arguments)));return Obj [...]
-d<b&&(d=b),d>c&&(d=c),this.convertToPercent(d)},toFixed:function(a){return a=a.toFixed(9),+a},_prettify:function(a){return this.options.prettify_enabled?this.options.prettify&&"function"==typeof this.options.prettify?this.options.prettify(a):this.prettify(a):a},prettify:function(a){var b=a.toString();return b.replace(/(\d{1,3}(?=(?:\d\d\d)+(?!\d)))/g,"$1"+this.options.prettify_separator)},checkEdges:function(a,b){return this.options.force_edges?(a<0?a=0:a>100-b&&(a=100-b),this.toFixed(a) [...]
\ No newline at end of file
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],function(b){return a(b,document,window,navigator)}):"object"==typeof exports?a(require("jquery"),document,window,navigator):a(jQuery,document,window,navigator)}(function(a,b,c,d,e){"use strict";var f=0,g=function(){var b,c=d.userAgent,e=/msie\s\d+/i;return c.search(e)>0&&(b=e.exec(c).toString(),b=b.split(" ")[1],b<9)&&(a("html").addClass("lt-ie9"),!0)}();Function.prototype.bind||(Function.prototype.bind=function(a){var  [...]
+var b=Math.round(a/this.coords.p_step)*this.coords.p_step;return b>100&&(b=100),100===a&&(b=100),this.toFixed(b)},checkMinInterval:function(a,b,c){var d,e,f=this.options;return f.min_interval?(d=this.convertToValue(a),e=this.convertToValue(b),"from"===c?e-d<f.min_interval&&(d=e-f.min_interval):d-e<f.min_interval&&(d=e+f.min_interval),this.convertToPercent(d)):a},checkMaxInterval:function(a,b,c){var d,e,f=this.options;return f.max_interval?(d=this.convertToValue(a),e=this.convertToValue(b [...]
\ No newline at end of file
diff --git a/inst/www/shared/shiny-showcase.css b/inst/www/shared/shiny-showcase.css
index 9691f3e..15753a6 100644
--- a/inst/www/shared/shiny-showcase.css
+++ b/inst/www/shared/shiny-showcase.css
@@ -6,6 +6,7 @@
 
 .shiny-code {
   background-color: white;
+  margin-bottom: 0;
 }
 
 .shiny-code code {
diff --git a/inst/www/shared/shiny-showcase.js b/inst/www/shared/shiny-showcase.js
index a2ecb89..8af0966 100644
--- a/inst/www/shared/shiny-showcase.js
+++ b/inst/www/shared/shiny-showcase.js
@@ -217,7 +217,7 @@
     var app = document.getElementById("showcase-app-container");
     $(app).animate({
         width: appWidth + "px",
-        zoom: zoom
+        zoom: (zoom*100) + "%"
       }, animate ? animateMs : 0);
   };
 
diff --git a/inst/www/shared/shiny.css b/inst/www/shared/shiny.css
index 1784010..e049b11 100644
--- a/inst/www/shared/shiny.css
+++ b/inst/www/shared/shiny.css
@@ -95,6 +95,13 @@ pre.shiny-text-output.noplaceholder:empty {
   font-weight: inherit;
 }
 
+/* Work around MS Edge transition bug (issue #1637) */
+ at supports (-ms-ime-align:auto) {
+  .shiny-bound-output {
+    transition: 0;
+  }
+}
+
 .recalculating {
   opacity: 0.3;
   transition: opacity 250ms ease 500ms;
@@ -366,3 +373,11 @@ pre.shiny-text-output.noplaceholder:empty {
   text-decoration: underline;
   font-weight: bold;
 }
+
+.shiny-file-input-active {
+  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
+}
+
+.shiny-file-input-over {
+  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(76, 174, 76, .6);
+}
diff --git a/inst/www/shared/shiny.js b/inst/www/shared/shiny.js
index e5f34b2..6633393 100644
--- a/inst/www/shared/shiny.js
+++ b/inst/www/shared/shiny.js
@@ -2,6 +2,10 @@
 
 var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
 
+var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr [...]
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
 //---------------------------------------------------------------------
 // Source file: ../srcjs/_start.js
 
@@ -10,6 +14,13 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
 
   var exports = window.Shiny = window.Shiny || {};
 
+  var origPushState = window.history.pushState;
+  window.history.pushState = function () {
+    var result = origPushState.apply(this, arguments);
+    $(document).trigger("pushstate");
+    return result;
+  };
+
   $(document).on('submit', 'form:not([action])', function (e) {
     e.preventDefault();
   });
@@ -18,7 +29,18 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
   // Source file: ../srcjs/utils.js
 
   function escapeHTML(str) {
-    return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "&#x2F;");
+    var escaped = {
+      "&": "&",
+      "<": "<",
+      ">": ">",
+      '"': """,
+      "'": "'",
+      "/": "&#x2F;"
+    };
+
+    return str.replace(/[&<>'"\/]/g, function (m) {
+      return escaped[m];
+    });
   }
 
   function randomId() {
@@ -60,6 +82,18 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     }return str;
   }
 
+  // Round to a specified number of significant digits.
+  function roundSignif(x) {
+    var digits = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
+
+    if (digits < 1) throw "Significant digits must be at least 1.";
+
+    // This converts to a string and back to a number, which is inelegant, but
+    // is less prone to FP rounding error than an alternate method which used
+    // Math.round().
+    return parseFloat(x.toPrecision(digits));
+  }
+
   // Take a string with format "YYYY-MM-DD" and return a Date object.
   // IE8 and QTWebKit don't support YYYY-MM-DD, but they support YYYY/MM/DD
   function parseDate(dateString) {
@@ -145,7 +179,17 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
   // "with" on the argument value, and return the result.
   function scopeExprToFunc(expr) {
     /*jshint evil: true */
-    var func = new Function("with (this) {return (" + expr + ");}");
+    var expr_escaped = expr.replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0').replace(/\n/g, '\\n').replace(/\r/g, '\\r')
+    // \b has a special meaning; need [\b] to match backspace char.
+    .replace(/[\b]/g, '\\b');
+
+    try {
+      var func = new Function('with (this) {\n        try {\n          return (' + expr + ');\n        } catch (e) {\n          console.error(\'Error evaluating expression: ' + expr_escaped + '\');\n          throw e;\n        }\n      }');
+    } catch (e) {
+      console.error("Error parsing expression: " + expr);
+      throw e;
+    }
+
     return function (scope) {
       return func.call(scope);
     };
@@ -200,6 +244,271 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     return val.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g, '\\$1');
   };
 
+  // Maps a function over an object, preserving keys. Like the mapValues
+  // function from lodash.
+  function mapValues(obj, f) {
+    var newObj = {};
+    for (var key in obj) {
+      if (obj.hasOwnProperty(key)) newObj[key] = f(obj[key]);
+    }
+    return newObj;
+  }
+
+  // Binary equality function used by the equal function.
+  function _equal(x, y) {
+    if ($.type(x) === "object" && $.type(y) === "object") {
+      if (Object.keys(x).length !== Object.keys(y).length) return false;
+      for (var prop in x) {
+        if (!y.hasOwnProperty(prop) || !_equal(x[prop], y[prop])) return false;
+      }return true;
+    } else if ($.type(x) === "array" && $.type(y) === "array") {
+      if (x.length !== y.length) return false;
+      for (var i = 0; i < x.length; i++) {
+        if (!_equal(x[i], y[i])) return false;
+      }return true;
+    } else {
+      return x === y;
+    }
+  }
+
+  // Structural or "deep" equality predicate. Tests two or more arguments for
+  // equality, traversing arrays and objects (as determined by $.type) as
+  // necessary.
+  //
+  // Objects other than objects and arrays are tested for equality using ===.
+  function equal() {
+    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+      args[_key] = arguments[_key];
+    }
+
+    if (args.length < 2) throw new Error("equal requires at least two arguments.");
+    for (var i = 0; i < args.length - 1; i++) {
+      if (!_equal(args[i], args[i + 1])) return false;
+    }
+    return true;
+  };
+
+  // multimethod: Creates functions — "multimethods" — that are polymorphic on one
+  // or more of their arguments.
+  //
+  // Multimethods can take any number of arguments. Arguments are passed to an
+  // applicable function or "method", returning its result. By default, if no
+  // method was applicable, an exception is thrown.
+  //
+  // Methods are searched in the order that they were added, and the first
+  // applicable method found is the one used.
+  //
+  // A method is applicable when the "dispatch value" associated with it
+  // corresponds to the value returned by the dispatch function. The dispatch
+  // function defaults to the value of the first argument passed to the
+  // multimethod.
+  //
+  // The correspondence between the value returned by the dispatch function and
+  // any method's dispatch value is determined by the test function, which is
+  // user-definable and defaults to `equal` or deep equality.
+  //
+  // # Chainable Functions
+  //
+  // The function returned by `multimethod()` exposes functions as properties.
+  // These functions generally return the multimethod, and so can be chained.
+  //
+  // - dispatch([function newDispatch]): Sets the dispatch function. The dispatch
+  //   function can take any number of arguments, but must return a dispatch
+  //   value. The default dispatch function returns the first argument passed to
+  //   the multimethod.
+  //
+  // - test([function newTest]): Sets the test function. The test function takes
+  //   two arguments: the dispatch value produced by the dispatch function, and
+  //   the dispatch value associated with some method. It must return a boolean
+  //   indicating whether or not to select the method. The default test function
+  //   is `equal`.
+  //
+  // - when(object dispatchVal, function method): Adds a new dispatch value/method
+  //   combination.
+  //
+  // - whenAny(array<object> dispatchVals, function method): Like `when`, but
+  //   associates the method with every dispatch value in the `dispatchVals`
+  //   array.
+  //
+  // - else(function newDefaultMethod): Sets the default function. This function
+  //   is invoked when no methods apply. If left unset, the multimethod will throw
+  //   an exception when no methods are applicable.
+  //
+  // - clone(): Returns a new, functionally-equivalent multimethod. This is a way
+  //   to extend an existing multimethod in a local context — such as inside a
+  //   function — without modifying the original. NOTE: The array of methods is
+  //   copied, but the dispatch values themselves are not.
+  //
+  // # Self-reference
+  //
+  // The multimethod function can be obtained inside its method bodies without
+  // referring to it by name.
+  //
+  // This makes it possible for one method to call another, or to pass the
+  // multimethod to other functions as a callback from within methods.
+  //
+  // The mechanism is: the multimethod itself is bound as `this` to methods when
+  // they are called. Since arrow functions cannot be bound to objects, **self-reference
+  // is only possible within methods created using the `function` keyword**.
+  //
+  // # Tail recursion
+  //
+  // A method can call itself in a way that will not overflow the stack by using
+  // `this.recur`.
+  //
+  // `this.recur` is a function available in methods created using `function`.
+  // When the return value of a call to `this.recur` is returned by a method, the
+  // arguments that were supplied to `this.recur` are used to call the
+  // multimethod.
+  //
+  // # Examples
+  //
+  // Handling events:
+  //
+  //    var handle = multimethod()
+  //     .dispatch(e => [e.target.tagName.toLowerCase(), e.type])
+  //     .when(["h1", "click"], e => "you clicked on an h1")
+  //     .when(["p", "mouseover"], e => "you moused over a p"})
+  //     .else(e => {
+  //       let tag = e.target.tagName.toLowerCase();
+  //       return `you did ${e.type} to an ${tag}`;
+  //     });
+  //
+  //    $(document).on("click mouseover mouseup mousedown", e => console.log(handle(e)))
+  //
+  // Self-calls:
+  //
+  //    var demoSelfCall = multimethod()
+  //     .when(0, function(n) {
+  //       this(1);
+  //     })
+  //     .when(1, function(n) {
+  //       doSomething(this);
+  //     })
+  //     .when(2, _ => console.log("tada"));
+  //
+  // Using (abusing?) the test function:
+  //
+  //    var fizzBuzz = multimethod()
+  //     .test((x, divs) => divs.map(d => x % d === 0).every(Boolean))
+  //     .when([3, 5], x => "FizzBuzz")
+  //     .when([3], x => "Fizz")
+  //     .when([5], x => "Buzz")
+  //     .else(x => x);
+  //
+  //    for(let i = 0; i <= 100; i++) console.log(fizzBuzz(i));
+  //
+  // Getting carried away with tail recursion:
+  //
+  //    var factorial = multimethod()
+  //     .when(0, () => 1)
+  //     .when(1, (_, prod = 1) => prod)
+  //     .else(function(n, prod = 1) {
+  //       return this.recur(n-1, n*prod);
+  //     });
+  //
+  //    var fibonacci = multimethod()
+  //     .when(0, (_, a = 0) => a)
+  //     .else(function(n, a = 0, b = 1) {
+  //       return this.recur(n-1, b, a+b);
+  //     });
+  function multimethod() {
+    var dispatch = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function (firstArg) {
+      return firstArg;
+    };
+    var test = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : equal;
+    var defaultMethod = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
+    var methods = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
+
+
+    var trampolining = false;
+
+    function Sentinel(args) {
+      this.args = args;
+    }
+
+    function trampoline(f) {
+      return function () {
+        for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+          args[_key2] = arguments[_key2];
+        }
+
+        trampolining = true;
+        var ret = f.apply(invoke, args);
+        while (ret instanceof Sentinel) {
+          ret = f.apply(invoke, ret.args);
+        }trampolining = false;
+        return ret;
+      };
+    }
+
+    var invoke = trampoline(function () {
+      for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+        args[_key3] = arguments[_key3];
+      }
+
+      var dispatchVal = dispatch.apply(null, args);
+      for (var i = 0; i < methods.length; i++) {
+        var _methods$i = _slicedToArray(methods[i], 2);
+
+        var methodVal = _methods$i[0];
+        var methodFn = _methods$i[1];
+
+        if (test(dispatchVal, methodVal)) {
+          return methodFn.apply(invoke, args);
+        }
+      }
+      if (defaultMethod) {
+        return defaultMethod.apply(invoke, args);
+      } else {
+        throw new Error('No method for dispatch value ' + dispatchVal);
+      }
+    });
+
+    invoke.recur = function () {
+      for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+        args[_key4] = arguments[_key4];
+      }
+
+      if (!trampolining) throw new Error("recur can only be called inside a method");
+      return new Sentinel(args);
+    };
+
+    invoke.dispatch = function (newDispatch) {
+      dispatch = newDispatch;
+      return invoke;
+    };
+
+    invoke.test = function (newTest) {
+      test = newTest;
+      return invoke;
+    };
+
+    invoke.when = function (dispatchVal, methodFn) {
+      methods = methods.concat([[dispatchVal, methodFn]]);
+      return invoke;
+    };
+
+    invoke.whenAny = function (dispatchVals, methodFn) {
+      return dispatchVals.reduce(function (self, val) {
+        return invoke.when(val, methodFn);
+      }, invoke);
+    };
+
+    invoke.else = function () {
+      var newDefaultMethod = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+
+      defaultMethod = newDefaultMethod;
+      return invoke;
+    };
+
+    invoke.clone = function () {
+      return multimethod(dispatch, test, defaultMethod, methods.slice());
+    };
+
+    return invoke;
+  }
+
   //---------------------------------------------------------------------
   // Source file: ../srcjs/browser.js
 
@@ -428,6 +737,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       var self = this;
 
       this.pendingData[name] = value;
+
       if (!this.timerId && !this.reentrant) {
         this.timerId = setTimeout(function () {
           self.reentrant = true;
@@ -449,55 +759,78 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
 
   var InputNoResendDecorator = function InputNoResendDecorator(target, initialValues) {
     this.target = target;
-    this.lastSentValues = initialValues || {};
+    this.lastSentValues = this.reset(initialValues);
   };
   (function () {
     this.setInput = function (name, value) {
+      // Note that opts is not passed to setInput at this stage of the input
+      // decorator stack. If in the future this setInput keeps track of opts, it
+      // would be best not to store the `el`, because that could prevent it from
+      // being GC'd.
+      var _splitInputNameType = splitInputNameType(name);
+
+      var inputName = _splitInputNameType.name;
+      var inputType = _splitInputNameType.inputType;
+
       var jsonValue = JSON.stringify(value);
-      if (this.lastSentValues[name] === jsonValue) return;
-      this.lastSentValues[name] = jsonValue;
-      this.target.setInput(name, value);
-    };
-    this.reset = function (values) {
-      values = values || {};
-      var strValues = {};
-      $.each(values, function (key, value) {
-        strValues[key] = JSON.stringify(value);
-      });
-      this.lastSentValues = strValues;
-    };
-  }).call(InputNoResendDecorator.prototype);
 
-  var InputDeferDecorator = function InputDeferDecorator(target) {
-    this.target = target;
-    this.pendingInput = {};
-  };
-  (function () {
-    this.setInput = function (name, value) {
-      if (/^\./.test(name)) this.target.setInput(name, value);else this.pendingInput[name] = value;
+      if (this.lastSentValues[inputName] && this.lastSentValues[inputName].jsonValue === jsonValue && this.lastSentValues[inputName].inputType === inputType) {
+        return;
+      }
+      this.lastSentValues[inputName] = { jsonValue: jsonValue, inputType: inputType };
+      this.target.setInput(name, value);
     };
-    this.submit = function () {
-      for (var name in this.pendingInput) {
-        if (this.pendingInput.hasOwnProperty(name)) this.target.setInput(name, this.pendingInput[name]);
+    this.reset = function () {
+      var values = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+
+      // Given an object with flat name-value format:
+      //   { x: "abc", "y.shiny.number": 123 }
+      // Create an object in cache format and save it:
+      //   { x: { jsonValue: '"abc"', inputType: "" },
+      //     y: { jsonValue: "123", inputType: "shiny.number" } }
+      var cacheValues = {};
+
+      for (var inputName in values) {
+        if (values.hasOwnProperty(inputName)) {
+          var _splitInputNameType2 = splitInputNameType(inputName);
+
+          var name = _splitInputNameType2.name;
+          var inputType = _splitInputNameType2.inputType;
+
+          cacheValues[name] = {
+            jsonValue: JSON.stringify(values[inputName]),
+            inputType: inputType
+          };
+        }
       }
+
+      this.lastSentValues = cacheValues;
     };
-  }).call(InputDeferDecorator.prototype);
+  }).call(InputNoResendDecorator.prototype);
 
   var InputEventDecorator = function InputEventDecorator(target) {
     this.target = target;
   };
   (function () {
-    this.setInput = function (name, value, immediate) {
+    this.setInput = function (name, value, opts) {
       var evt = jQuery.Event("shiny:inputchanged");
-      var name2 = name.split(':');
-      evt.name = name2[0];
-      evt.inputType = name2.length > 1 ? name2[1] : '';
+
+      var input = splitInputNameType(name);
+      evt.name = input.name;
+      evt.inputType = input.inputType;
       evt.value = value;
+      evt.binding = opts.binding;
+      evt.el = opts.el;
+
       $(document).trigger(evt);
+
       if (!evt.isDefaultPrevented()) {
         name = evt.name;
         if (evt.inputType !== '') name += ':' + evt.inputType;
-        this.target.setInput(name, evt.value, immediate);
+
+        // opts aren't passed along to lower levels in the input decorator
+        // stack.
+        this.target.setInput(name, evt.value);
       }
     };
   }).call(InputEventDecorator.prototype);
@@ -507,9 +840,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     this.inputRatePolicies = {};
   };
   (function () {
-    this.setInput = function (name, value, immediate) {
+    this.setInput = function (name, value, opts) {
       this.$ensureInit(name);
-      if (immediate) this.inputRatePolicies[name].immediateCall(name, value, immediate);else this.inputRatePolicies[name].normalCall(name, value, immediate);
+
+      if (opts.immediate) this.inputRatePolicies[name].immediateCall(name, value, opts);else this.inputRatePolicies[name].normalCall(name, value, opts);
     };
     this.setRatePolicy = function (name, mode, millis) {
       if (mode === 'direct') {
@@ -523,11 +857,59 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     this.$ensureInit = function (name) {
       if (!(name in this.inputRatePolicies)) this.setRatePolicy(name, 'direct');
     };
-    this.$doSetInput = function (name, value) {
-      this.target.setInput(name, value);
+    this.$doSetInput = function (name, value, opts) {
+      this.target.setInput(name, value, opts);
     };
   }).call(InputRateDecorator.prototype);
 
+  var InputDeferDecorator = function InputDeferDecorator(target) {
+    this.target = target;
+    this.pendingInput = {};
+  };
+  (function () {
+    this.setInput = function (name, value, opts) {
+      if (/^\./.test(name)) this.target.setInput(name, value, opts);else this.pendingInput[name] = { value: value, opts: opts };
+    };
+    this.submit = function () {
+      for (var name in this.pendingInput) {
+        if (this.pendingInput.hasOwnProperty(name)) {
+          var input = this.pendingInput[name];
+          this.target.setInput(name, input.value, input.opts);
+        }
+      }
+    };
+  }).call(InputDeferDecorator.prototype);
+
+  var InputValidateDecorator = function InputValidateDecorator(target) {
+    this.target = target;
+  };
+  (function () {
+    this.setInput = function (name, value, opts) {
+      if (!name) throw "Can't set input with empty name.";
+
+      opts = addDefaultInputOpts(opts);
+
+      this.target.setInput(name, value, opts);
+    };
+  }).call(InputValidateDecorator.prototype);
+
+  // Merge opts with defaults, and return a new object.
+  function addDefaultInputOpts(opts) {
+    return $.extend({
+      immediate: false,
+      binding: null,
+      el: null
+    }, opts);
+  }
+
+  function splitInputNameType(name) {
+    var name2 = name.split(':');
+    return {
+      name: name2[0],
+      inputType: name2.length > 1 ? name2[1] : ''
+    };
+  }
+
   //---------------------------------------------------------------------
   // Source file: ../srcjs/shinyapp.js
 
@@ -537,6 +919,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     // Cached input values
     this.$inputValues = {};
 
+    // Input values at initialization (and reconnect)
+    this.$initialInput = {};
+
     // Output bindings
     this.$bindings = {};
 
@@ -559,11 +944,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     this.connect = function (initialInput) {
       if (this.$socket) throw "Connect was already called on this application object";
 
-      $.extend(initialInput, {
-        // IE8 and IE9 have some limitations with data URIs
-        ".clientdata_allowDataUriScheme": typeof WebSocket !== 'undefined'
-      });
-
       this.$socket = this.createSocket();
       this.$initialInput = initialInput;
       $.extend(this.$inputValues, initialInput);
@@ -893,6 +1273,33 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       }
     };
 
+    // Narrows a scopeComponent -- an input or output object -- to one constrained
+    // by nsPrefix. Returns a new object with keys removed and renamed as
+    // necessary.
+    function narrowScopeComponent(scopeComponent, nsPrefix) {
+      return Object.keys(scopeComponent).filter(function (k) {
+        return k.indexOf(nsPrefix) === 0;
+      }).map(function (k) {
+        return _defineProperty({}, k.substring(nsPrefix.length), scopeComponent[k]);
+      }).reduce(function (obj, pair) {
+        return $.extend(obj, pair);
+      }, {});
+    }
+
+    // Narrows a scope -- an object with input and output "subComponents" -- to
+    // one constrained by the nsPrefix string.
+    //
+    // If nsPrefix is null or empty, returns scope without modification.
+    //
+    // Otherwise, returns a new object with keys in subComponents removed and
+    // renamed as necessary.
+    function narrowScope(scope, nsPrefix) {
+      return nsPrefix ? {
+        input: narrowScopeComponent(scope.input, nsPrefix),
+        output: narrowScopeComponent(scope.output, nsPrefix)
+      } : scope;
+    }
+
     this.$updateConditionals = function () {
       $(document).trigger({
         type: 'shiny:conditional'
@@ -922,7 +1329,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
           el.data('data-display-if-func', condFunc);
         }
 
-        var show = condFunc(scope);
+        var nsPrefix = el.attr('data-ns-prefix');
+        var nsScope = narrowScope(scope, nsPrefix);
+        var show = condFunc(nsScope);
         var showing = el.css("display") !== "none";
         if (show !== showing) {
           if (show) {
@@ -1128,7 +1537,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     });
 
     addMessageHandler('config', function (message) {
-      this.config = message;
+      this.config = { workerId: message.workerId, sessionId: message.sessionId };
+      if (message.user) exports.user = message.user;
+      $(document).trigger('shiny:sessioninitialized');
     });
 
     addMessageHandler('busy', function (message) {
@@ -1182,8 +1593,323 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       });
     });
 
+    function getTabset(id) {
+      var $tabset = $("#" + $escape(id));
+      if ($tabset.length === 0) throw "There is no tabsetPanel (or navbarPage or navlistPanel) " + "with id equal to '" + id + "'";
+      return $tabset;
+    }
+
+    function getTabContent($tabset) {
+      var tabsetId = $tabset.attr("data-tabsetid");
+      var $tabContent = $("div.tab-content[data-tabsetid='" + $escape(tabsetId) + "']");
+      return $tabContent;
+    }
+
+    function getTargetTabs($tabset, $tabContent, target) {
+      var dataValue = "[data-value='" + $escape(target) + "']";
+      var $aTag = $tabset.find("a" + dataValue);
+      var $liTag = $aTag.parent();
+      if ($liTag.length === 0) {
+        throw "There is no tabPanel (or navbarMenu) with value" + " (or menuName) equal to '" + target + "'";
+      }
+      var $liTags = [];
+      var $divTags = [];
+
+      if ($aTag.attr("data-toggle") === "dropdown") {
+        // dropdown
+        var $dropdownTabset = $aTag.find("+ ul.dropdown-menu");
+        var dropdownId = $dropdownTabset.attr("data-tabsetid");
+
+        var $dropdownLiTags = $dropdownTabset.find("a[data-toggle='tab']").parent("li");
+        $dropdownLiTags.each(function (i, el) {
+          $liTags.push($(el));
+        });
+        var selector = "div.tab-pane[id^='tab-" + $escape(dropdownId) + "']";
+        var $dropdownDivs = $tabContent.find(selector);
+        $dropdownDivs.each(function (i, el) {
+          $divTags.push($(el));
+        });
+      } else {
+        // regular tab
+        $divTags.push($tabContent.find("div" + dataValue));
+      }
+      return { $liTag: $liTag, $liTags: $liTags, $divTags: $divTags };
+    }
+
+    addMessageHandler("shiny-insert-tab", function (message) {
+      var $parentTabset = getTabset(message.inputId);
+      var $tabset = $parentTabset;
+      var $tabContent = getTabContent($tabset);
+      var tabsetId = $parentTabset.attr("data-tabsetid");
+
+      var $divTag = $(message.divTag.html);
+      var $liTag = $(message.liTag.html);
+      var $aTag = $liTag.find("> a");
+
+      // Unless the item is being prepended/appended, the target tab
+      // must be provided
+      var target = null;
+      var $targetLiTag = null;
+      if (message.target !== null) {
+        target = getTargetTabs($tabset, $tabContent, message.target);
+        $targetLiTag = target.$liTag;
+      }
+
+      // If the item is to be placed inside a navbarMenu (dropdown),
+      // change the value of $tabset from the parent's ul tag to the
+      // dropdown's ul tag
+      var dropdown = getDropdown();
+      if (dropdown !== null) {
+        if ($aTag.attr("data-toggle") === "dropdown") throw "Cannot insert a navbarMenu inside another one";
+        $tabset = dropdown.$tabset;
+        tabsetId = dropdown.id;
+      }
+
+      // For regular tab items, fix the href (of the li > a tag)
+      // and the id (of the div tag). This does not apply to plain
+      // text items (which function as dividers and headers inside
+      // navbarMenus) and whole navbarMenus (since those get
+      // constructed from scratch on the R side and therefore
+      // there are no ids that need matching)
+      if ($aTag.attr("data-toggle") === "tab") {
+        var index = getTabIndex($tabset, tabsetId);
+        var tabId = "tab-" + tabsetId + "-" + index;
+        $liTag.find("> a").attr("href", "#" + tabId);
+        $divTag.attr("id", tabId);
+      }
+
+      // actually insert the item into the right place
+      if (message.position === "before") {
+        if ($targetLiTag) {
+          $targetLiTag.before($liTag);
+        } else {
+          $tabset.append($liTag);
+        }
+      } else if (message.position === "after") {
+        if ($targetLiTag) {
+          $targetLiTag.after($liTag);
+        } else {
+          $tabset.prepend($liTag);
+        }
+      }
+
+      exports.renderContent($liTag[0], { html: $liTag.html(), deps: message.liTag.deps });
+      // jcheng 2017-07-28: This next part might look a little insane versus the
+      // more obvious `$tabContent.append($divTag);`, but there's a method to the
+      // madness.
+      //
+      // 1) We need to load the dependencies, and this needs to happen before
+      //    any scripts in $divTag get a chance to run.
+      // 2) The scripts in $divTag need to run only once.
+      // 3) The contents of $divTag need to be sent through renderContent so that
+      //    singletons may be registered and/or obeyed, and so that inputs/outputs
+      //    may be bound.
+      //
+      // Add to these constraints these facts:
+      //
+      // A) The (non-jQuery) DOM manipulation functions don't cause scripts to
+      //    run, but the jQuery functions all do.
+      // B) renderContent must be called on an element that's attached to the
+      //    document.
+      // C) $divTag may be of length > 1 (e.g. navbarMenu). I also noticed text
+      //    elements consisting of just "\n" being included in the nodeset of
+      //    $divTag.
+      // D) renderContent has a bug where only position "replace" (the default)
+      //    uses the jQuery functions, so other positions like "beforeend" will
+      //    prevent child script tags from running.
+      //
+      // In theory the same problem exists for $liTag but since that content is
+      // much less likely to include arbitrary scripts, we're skipping it.
+      //
+      // This code could be nicer if we didn't use renderContent, but rather the
+      // lower-level functions that renderContent uses. Like if we pre-process
+      // the value of message.divTag.html for singletons, we could do that, then
+      // render dependencies, then do $tabContent.append($divTag).
+      exports.renderContent($tabContent[0], { html: "", deps: message.divTag.deps }, "beforeend");
+      $divTag.get().forEach(function (el) {
+        // Must not use jQuery for appending el to the doc, we don't want any
+        // scripts to run (since they will run when renderContent takes a crack).
+        $tabContent[0].appendChild(el);
+        // If `el` itself is a script tag, this approach won't work (the script
+        // won't be run), since we're only sending innerHTML through renderContent
+        // and not the whole tag. That's fine in this case because we control the
+        // R code that generates this HTML, and we know that the element is not
+        // a script tag.
+        exports.renderContent(el, el.innerHTML || el.textContent);
+      });
+
+      if (message.select) {
+        $liTag.find("a").tab("show");
+      }
+
+      /* Barbara -- August 2017
+      Note: until now, the number of tabs in a tabsetPanel (or navbarPage
+      or navlistPanel) was always fixed. So, an easy way to give an id to
+      a tab was simply incrementing a counter. (Just like it was easy to
+      give a random 4-digit number to identify the tabsetPanel). Now that
+      we're introducing dynamic tabs, we must retrieve these numbers and
+      fix the dummy id given to the tab in the R side -- there, we always
+      set the tab id (counter dummy) to "id" and the tabset id to "tsid")
+      */
+      function getTabIndex($tabset, tabsetId) {
+        // The 0 is to ensure this works for empty tabsetPanels as well
+        var existingTabIds = [0];
+        var leadingHref = "#tab-" + tabsetId + "-";
+        // loop through all existing tabs, find the one with highest id
+        // (since this is based on a numeric counter), and increment
+        $tabset.find("> li").each(function () {
+          var $tab = $(this).find("> a[data-toggle='tab']");
+          if ($tab.length > 0) {
+            var index = $tab.attr("href").replace(leadingHref, "");
+            existingTabIds.push(Number(index));
+          }
+        });
+        return Math.max.apply(null, existingTabIds) + 1;
+      }
+
+      // Finds out if the item will be placed inside a navbarMenu
+      // (dropdown). If so, returns the dropdown tabset (ul tag)
+      // and the dropdown tabsetid (to be used to fix the tab ID)
+      function getDropdown() {
+        if (message.menuName !== null) {
+          // menuName is only provided if the user wants to prepend
+          // or append an item inside a navbarMenu (dropdown)
+          var $dropdownATag = $("a.dropdown-toggle[data-value='" + $escape(message.menuName) + "']");
+          if ($dropdownATag.length === 0) {
+            throw "There is no navbarMenu with menuName equal to '" + message.menuName + "'";
+          }
+          var $dropdownTabset = $dropdownATag.find("+ ul.dropdown-menu");
+          var dropdownId = $dropdownTabset.attr("data-tabsetid");
+          return { $tabset: $dropdownTabset, id: dropdownId };
+        } else if (message.target !== null) {
+          // if our item is to be placed next to a tab that is inside
+          // a navbarMenu, our item will also be inside
+          var $uncleTabset = $targetLiTag.parent("ul");
+          if ($uncleTabset.hasClass("dropdown-menu")) {
+            var uncleId = $uncleTabset.attr("data-tabsetid");
+            return { $tabset: $uncleTabset, id: uncleId };
+          }
+        }
+        return null;
+      }
+    });
+
+    // If the given tabset has no active tabs, select the first one
+    function ensureTabsetHasVisibleTab($tabset) {
+      if ($tabset.find("li.active").not(".dropdown").length === 0) {
+        // Note: destTabValue may be null. We still want to proceed
+        // through the below logic and setValue so that the input
+        // value for the tabset gets updated (i.e. input$tabsetId
+        // should be null if there are no tabs).
+        var destTabValue = getFirstTab($tabset);
+        var inputBinding = $tabset.data('shiny-input-binding');
+        var evt = jQuery.Event('shiny:updateinput');
+        evt.binding = inputBinding;
+        $tabset.trigger(evt);
+        inputBinding.setValue($tabset[0], destTabValue);
+      }
+    }
+
+    // Given a tabset ul jquery object, return the value of the first tab
+    // (in document order) that's visible and able to be selected.
+    function getFirstTab($ul) {
+      return $ul.find("li:visible a[data-toggle='tab']").first().attr("data-value") || null;
+    }
+
+    function tabApplyFunction(target, func) {
+      var liTags = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
+
+      $.each(target, function (key, el) {
+        if (key === "$liTag") {
+          // $liTag is always just one jQuery element
+          func(el);
+        } else if (key === "$divTags") {
+          // $divTags is always an array (even if length = 1)
+          $.each(el, function (i, div) {
+            func(div);
+          });
+        } else if (liTags && key === "$liTags") {
+          // $liTags is always an array (even if length = 0)
+          $.each(el, function (i, div) {
+            func(div);
+          });
+        }
+      });
+    }
+
+    addMessageHandler("shiny-remove-tab", function (message) {
+      var $tabset = getTabset(message.inputId);
+      var $tabContent = getTabContent($tabset);
+      var target = getTargetTabs($tabset, $tabContent, message.target);
+
+      tabApplyFunction(target, removeEl);
+
+      ensureTabsetHasVisibleTab($tabset);
+
+      function removeEl($el) {
+        exports.unbindAll($el, true);
+        $el.remove();
+      }
+    });
+
+    addMessageHandler("shiny-change-tab-visibility", function (message) {
+      var $tabset = getTabset(message.inputId);
+      var $tabContent = getTabContent($tabset);
+      var target = getTargetTabs($tabset, $tabContent, message.target);
+
+      tabApplyFunction(target, changeVisibility, true);
+
+      ensureTabsetHasVisibleTab($tabset);
+
+      function changeVisibility($el) {
+        if (message.type === "show") $el.css("display", "");else if (message.type === "hide") {
+          $el.hide();
+          $el.removeClass("active");
+        }
+      }
+    });
+
     addMessageHandler('updateQueryString', function (message) {
-      window.history.replaceState(null, null, message.queryString);
+
+      // leave the bookmarking code intact
+      if (message.mode === "replace") {
+        window.history.replaceState(null, null, message.queryString);
+        return;
+      }
+
+      var what = null;
+      if (message.queryString.charAt(0) === "#") what = "hash";else if (message.queryString.charAt(0) === "?") what = "query";else throw "The 'query' string must start with either '?' " + "(to update the query string) or with '#' (to " + "update the hash).";
+
+      var path = window.location.pathname;
+      var oldQS = window.location.search;
+      var oldHash = window.location.hash;
+
+      /* Barbara -- December 2016
+      Note: we could check if the new QS and/or hash are different
+      from the old one(s) and, if not, we could choose not to push
+      a new state (whether or not we would replace it is moot/
+      inconsequential). However, I think that it is better to
+      interpret each call to `updateQueryString` as representing
+      new state (even if the message.queryString is the same), so
+      that check isn't even performed as of right now.
+      */
+
+      var relURL = path;
+      if (what === "query") relURL += message.queryString;else relURL += oldQS + message.queryString; // leave old QS if it exists
+      window.history.pushState(null, null, relURL);
+
+      // for the case when message.queryString has both a query string
+      // and a hash (`what = "hash"` allows us to trigger the
+      // hashchange event)
+      if (message.queryString.indexOf("#") !== -1) what = "hash";
+
+      // for the case when there was a hash before, but there isn't
+      // any hash now (e.g. for when only the query string is updated)
+      if (window.location.hash !== oldHash) what = "hash";
+
+      // This event needs to be triggered manually because pushState() never
+      // causes a hashchange event to be fired,
+      if (what === "hash") $(document).trigger("hashchange");
     });
 
     addMessageHandler("resetBrush", function (message) {
@@ -1197,8 +1923,13 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       binding: function binding(message) {
         var key = message.id;
         var binding = this.$bindings[key];
-        if (binding && binding.showProgress) {
-          binding.showProgress(true);
+        if (binding) {
+          $(binding.el).trigger({
+            type: 'shiny:outputinvalidated',
+            binding: binding,
+            name: key
+          });
+          if (binding.showProgress) binding.showProgress(true);
         }
       },
 
@@ -1260,13 +1991,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
           if (typeof message.detail !== 'undefined') {
             $progress.find('.progress-detail').text(message.detail);
           }
-          if (typeof message.value !== 'undefined') {
-            if (message.value !== null) {
-              $progress.find('.progress').show();
-              $progress.find('.progress-bar').width(message.value * 100 + '%');
-            } else {
-              $progress.find('.progress').hide();
-            }
+          if (typeof message.value !== 'undefined' && message.value !== null) {
+            $progress.find('.progress').show();
+            $progress.find('.progress-bar').width(message.value * 100 + '%');
           }
         } else if (message.style === "old") {
           // For old-style (Shiny <=0.13.2) progress indicators.
@@ -1278,13 +2005,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
           if (typeof message.detail !== 'undefined') {
             $progress.find('.progress-detail').text(message.detail);
           }
-          if (typeof message.value !== 'undefined') {
-            if (message.value !== null) {
-              $progress.find('.progress').show();
-              $progress.find('.bar').width(message.value * 100 + '%');
-            } else {
-              $progress.find('.progress').hide();
-            }
+          if (typeof message.value !== 'undefined' && message.value !== null) {
+            $progress.find('.progress').show();
+            $progress.find('.bar').width(message.value * 100 + '%');
           }
 
           $progress.fadeIn();
@@ -1316,10 +2039,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     // Returns a URL which can be queried to get values from inside the server
     // function. This is enabled with `options(shiny.testmode=TRUE)`.
     this.getTestSnapshotBaseUrl = function () {
-      var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+      var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
 
-      var _ref$fullUrl = _ref.fullUrl;
-      var fullUrl = _ref$fullUrl === undefined ? true : _ref$fullUrl;
+      var _ref2$fullUrl = _ref2.fullUrl;
+      var fullUrl = _ref2$fullUrl === undefined ? true : _ref2$fullUrl;
 
       var loc = window.location;
       var url = "";
@@ -1388,22 +2111,22 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     var fadeDuration = 250;
 
     function show() {
-      var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+      var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
 
-      var _ref2$html = _ref2.html;
-      var html = _ref2$html === undefined ? '' : _ref2$html;
-      var _ref2$action = _ref2.action;
-      var action = _ref2$action === undefined ? '' : _ref2$action;
-      var _ref2$deps = _ref2.deps;
-      var deps = _ref2$deps === undefined ? [] : _ref2$deps;
-      var _ref2$duration = _ref2.duration;
-      var duration = _ref2$duration === undefined ? 5000 : _ref2$duration;
-      var _ref2$id = _ref2.id;
-      var id = _ref2$id === undefined ? null : _ref2$id;
-      var _ref2$closeButton = _ref2.closeButton;
-      var closeButton = _ref2$closeButton === undefined ? true : _ref2$closeButton;
-      var _ref2$type = _ref2.type;
-      var type = _ref2$type === undefined ? null : _ref2$type;
+      var _ref3$html = _ref3.html;
+      var html = _ref3$html === undefined ? '' : _ref3$html;
+      var _ref3$action = _ref3.action;
+      var action = _ref3$action === undefined ? '' : _ref3$action;
+      var _ref3$deps = _ref3.deps;
+      var deps = _ref3$deps === undefined ? [] : _ref3$deps;
+      var _ref3$duration = _ref3.duration;
+      var duration = _ref3$duration === undefined ? 5000 : _ref3$duration;
+      var _ref3$id = _ref3.id;
+      var id = _ref3$id === undefined ? null : _ref3$id;
+      var _ref3$closeButton = _ref3.closeButton;
+      var closeButton = _ref3$closeButton === undefined ? true : _ref3$closeButton;
+      var _ref3$type = _ref3.type;
+      var type = _ref3$type === undefined ? null : _ref3$type;
 
       if (!id) id = randomId();
 
@@ -1547,12 +2270,12 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     // content is non-Bootstrap. Bootstrap modals require some special handling,
     // which is coded in here.
     show: function show() {
-      var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+      var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
 
-      var _ref3$html = _ref3.html;
-      var html = _ref3$html === undefined ? '' : _ref3$html;
-      var _ref3$deps = _ref3.deps;
-      var deps = _ref3$deps === undefined ? [] : _ref3$deps;
+      var _ref4$html = _ref4.html;
+      var html = _ref4$html === undefined ? '' : _ref4$html;
+      var _ref4$deps = _ref4.deps;
+      var deps = _ref4$deps === undefined ? [] : _ref4$deps;
 
 
       // If there was an existing Bootstrap modal, then there will be a modal-
@@ -2840,6 +3563,11 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       // For reversed scales, the min and max can be reversed, so use findBox
       // to ensure correct order.
       state.boundsData = coordmap.findBox(minData, maxData);
+      // Round to 14 significant digits to avoid spurious changes in FP values
+      // (#1634).
+      state.boundsData = mapValues(state.boundsData, function (val) {
+        return roundSignif(val, 14);
+      });
 
       // We also need to attach the data bounds and panel as data attributes, so
       // that if the image is re-sent, we can grab the data bounds to create a new
@@ -3126,8 +3854,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       exports.unbindAll(el);
     }
 
-    exports.unbindAll(el);
-
     var html;
     var dependencies = [];
     if (content === null) {
@@ -3323,6 +4049,14 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
   });
   outputBindings.register(downloadLinkOutputBinding, 'shiny.downloadLink');
 
+  // Trigger shiny:filedownload event whenever a downloadButton/Link is clicked
+  $(document).on('click.shinyDownloadLink', 'a.shiny-download-link', function (e) {
+    var evt = jQuery.Event('shiny:filedownload');
+    evt.name = this.id;
+    evt.href = this.href;
+    $(document).trigger(evt);
+  });
+
   //---------------------------------------------------------------------
   // Source file: ../srcjs/output_binding_datatable.js
 
@@ -3532,12 +4266,15 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
 
       if (data.hasOwnProperty('label')) $(el).parent().find('label[for="' + $escape(el.id) + '"]').text(data.label);
 
+      if (data.hasOwnProperty('placeholder')) el.placeholder = data.placeholder;
+
       $(el).trigger('change');
     },
     getState: function getState(el) {
       return {
         label: $(el).parent().find('label[for="' + $escape(el.id) + '"]').text(),
-        value: el.value
+        value: el.value,
+        placeholder: el.placeholder
       };
     },
     getRatePolicy: function getRatePolicy() {
@@ -4628,14 +5365,23 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     },
     setValue: function setValue(el, value) {
       var self = this;
-      var anchors = $(el).find('li:not(.dropdown)').children('a');
-      anchors.each(function () {
-        if (self._getTabName($(this)) === value) {
-          $(this).tab('show');
-          return false; // Break out of each()
-        }
-        return true;
-      });
+      var success = false;
+      if (value) {
+        var anchors = $(el).find('li:not(.dropdown)').children('a');
+        anchors.each(function () {
+          if (self._getTabName($(this)) === value) {
+            $(this).tab('show');
+            success = true;
+            return false; // Break out of each()
+          }
+          return true;
+        });
+      }
+      if (!success) {
+        // This is to handle the case where nothing is selected, e.g. the last tab
+        // was removed using removeTab.
+        $(el).trigger("change");
+      }
     },
     getState: function getState(el) {
       return { value: this.getValue(el) };
@@ -4644,7 +5390,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       if (data.hasOwnProperty('value')) this.setValue(el, data.value);
     },
     subscribe: function subscribe(el, callback) {
-      $(el).on('shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding', function (event) {
+      $(el).on('change shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding', function (event) {
         callback();
       });
     },
@@ -4681,6 +5427,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
         // invalidated reactives, but observers don't actually execute.
         self.shinyapp.makeRequest('uploadieFinish', [], function () {}, function () {});
         $(self.iframe).remove();
+        // Reset the file input's value to "". This allows the same file to be
+        // uploaded again. https://stackoverflow.com/a/22521275
+        $(self.fileEl).val("");
       };
       if (this.iframe.attachEvent) {
         this.iframe.attachEvent('onload', iframeDestroy);
@@ -4699,9 +5448,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     };
   }).call(IE8FileUploader.prototype);
 
-  var FileUploader = function FileUploader(shinyapp, id, files) {
+  var FileUploader = function FileUploader(shinyapp, id, files, el) {
     this.shinyapp = shinyapp;
     this.id = id;
+    this.el = el;
     FileProcessor.call(this, files);
   };
   $.extend(FileUploader.prototype, FileProcessor.prototype);
@@ -4759,6 +5509,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
           return xhrVal;
         },
         data: file,
+        contentType: 'application/octet-stream',
         processData: false,
         success: function success() {
           self.progressBytes += file.size;
@@ -4771,26 +5522,37 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     };
     this.onComplete = function () {
       var self = this;
-      this.makeRequest('uploadEnd', [this.jobId, this.id], function (response) {
-        self.$setActive(false);
-        self.onProgress(null, 1);
-        self.$bar().text('Upload complete');
-      }, function (error) {
-        self.onError(error);
-      });
-      this.$bar().text('Finishing upload');
 
-      // Trigger event when all files are finished uploading.
-      var evt = jQuery.Event("shiny:fileuploaded");
-      evt.name = this.id;
-      evt.files = $.map(this.files, function (file, i) {
+      var fileInfo = $.map(this.files, function (file, i) {
         return {
           name: file.name,
           size: file.size,
           type: file.type
         };
       });
+
+      // Trigger shiny:inputchanged. Unlike a normal shiny:inputchanged event,
+      // it's not possible to modify the information before the values get
+      // sent to the server.
+      var evt = jQuery.Event("shiny:inputchanged");
+      evt.name = this.id;
+      evt.value = fileInfo;
+      evt.binding = fileInputBinding;
+      evt.el = this.el;
+      evt.inputType = 'shiny.fileupload';
       $(document).trigger(evt);
+
+      this.makeRequest('uploadEnd', [this.jobId, this.id], function (response) {
+        self.$setActive(false);
+        self.onProgress(null, 1);
+        self.$bar().text('Upload complete');
+        // Reset the file input's value to "". This allows the same file to be
+        // uploaded again. https://stackoverflow.com/a/22521275
+        $(evt.el).val("");
+      }, function (error) {
+        self.onError(error);
+      });
+      this.$bar().text('Finishing upload');
     };
     this.onError = function (message) {
       this.$setError(message || '');
@@ -4813,7 +5575,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       this.$container().css('visibility', visible ? 'visible' : 'hidden');
     };
     this.$setError = function (error) {
-      this.$bar().toggleClass('bar-danger', error !== null);
+      this.$bar().toggleClass('progress-bar-danger', error !== null);
       if (error !== null) {
         this.onProgress(null, 1);
         this.$bar().text(error);
@@ -4824,11 +5586,43 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     };
   }).call(FileUploader.prototype);
 
-  function uploadFiles(evt) {
-    // If previously selected files are uploading, abort that.
-    var $el = $(evt.target);
+  // NOTE On Safari, at least version 10.1.2, *if the developer console is open*,
+  // setting the input's value will behave strangely because of a Safari bug. The
+  // uploaded file's name will appear over the placeholder value, instead of
+  // replacing it. The workaround is to restart Safari. When I (Alan Dipert) ran
+  // into this bug Winston Chang helped me diagnose the exact problem, and Winston
+  // then submitted a bug report to Apple.
+  function setFileText($el, files) {
+    var $fileText = $el.closest('div.input-group').find('input[type=text]');
+    if (files.length === 1) {
+      $fileText.val(files[0].name);
+    } else {
+      $fileText.val(files.length + " files");
+    }
+  }
+
+  // If previously selected files are uploading, abort that.
+  function abortCurrentUpload($el) {
     var uploader = $el.data('currentUploader');
     if (uploader) uploader.abort();
+    // Clear data-restore attribute if present.
+    $el.removeAttr('data-restore');
+  }
+
+  function uploadDroppedFilesIE10Plus(el, files) {
+    var $el = $(el);
+    abortCurrentUpload($el);
+
+    // Set the label in the text box
+    setFileText($el, files);
+
+    // Start the new upload and put the uploader in 'currentUploader'.
+    $el.data('currentUploader', new FileUploader(exports.shinyapp, fileInputBinding.getId(el), files, el));
+  }
+
+  function uploadFiles(evt) {
+    var $el = $(evt.target);
+    abortCurrentUpload($el);
 
     var files = evt.target.files;
     // IE8 here does not necessarily mean literally IE8; it indicates if the web
@@ -4838,18 +5632,13 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
 
     if (!IE8 && files.length === 0) return;
 
-    // Clear data-restore attribute if present.
-    $el.removeAttr('data-restore');
-
     // Set the label in the text box
     var $fileText = $el.closest('div.input-group').find('input[type=text]');
     if (IE8) {
       // If we're using IE8/9, just use this placeholder
       $fileText.val("[Uploaded file]");
-    } else if (files.length === 1) {
-      $fileText.val(files[0].name);
     } else {
-      $fileText.val(files.length + " files");
+      setFileText($el, files);
     }
 
     // Start the new upload and put the uploader in 'currentUploader'.
@@ -4857,10 +5646,16 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       /*jshint nonew:false */
       new IE8FileUploader(exports.shinyapp, id, evt.target);
     } else {
-      $el.data('currentUploader', new FileUploader(exports.shinyapp, id, files));
+      $el.data('currentUploader', new FileUploader(exports.shinyapp, id, files, evt.target));
     }
   }
 
+  // Here we maintain a list of all the current file inputs. This is necessary
+  // because we need to trigger events on them in order to respond to file drag
+  // events. For example, they should all light up when a file is dragged on to
+  // the page.
+  var $fileInputs = $();
+
   var fileInputBinding = new InputBinding();
   $.extend(fileInputBinding, {
     find: function find(scope) {
@@ -4905,11 +5700,206 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       // This will be used only when restoring a file from a saved state.
       return 'shiny.file';
     },
+    _getZone: function _getZone(el) {
+      return $(el).closest("div.input-group");
+    },
+    // This implements draghoverstart/draghoverend events that occur once per
+    // selector, instead of once for every child the way native
+    // dragenter/dragleave do. Inspired by https://gist.github.com/meleyal/3794126
+    _enableDraghover: function _enableDraghover($el) {
+      var ns = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
+
+      // Create an empty jQuery collection. This is a set-like data structure that
+      // jQuery normally uses to contain the results of a selection.
+      var collection = $();
+
+      // Attach a dragenter handler to $el and all of its children. When the first
+      // child is entered, trigger a draghoverstart event.
+      $el.on("dragenter.dragHover", function (e) {
+        if (collection.size() === 0) {
+          $el.trigger("draghoverstart" + ns, e.originalEvent);
+        }
+        // Every child that has fired dragenter is added to the collection.
+        // Addition is idempotent, which accounts for elements producing dragenter
+        // multiple times.
+        collection = collection.add(e.originalEvent.target);
+      });
+
+      // Attach dragleave and drop handlers to $el and its children. Whenever a
+      // child fires either of these events, remove it from the collection.
+      $el.on("dragleave.dragHover drop.dragHover", function (e) {
+        collection = collection.not(e.originalEvent.target);
+        // When the collection has no elements, all of the children have been
+        // removed, and produce draghoverend event.
+        if (collection.size() === 0) {
+          $el.trigger("draghoverend" + ns, e.originalEvent);
+        }
+      });
+    },
+    _disableDraghover: function _disableDraghover($el) {
+      $el.off(".dragHover");
+    },
+    _enableDocumentEvents: function _enableDocumentEvents() {
+      var $doc = $("html");
+
+      this._enableDraghover($doc);
+      $doc.on({
+        "draghoverstart.fileDrag": function draghoverstartFileDrag(e) {
+          $fileInputs.trigger("showZone.fileDrag");
+        },
+        "draghoverend.fileDrag": function draghoverendFileDrag(e) {
+          $fileInputs.trigger("hideZone.fileDrag");
+        },
+        "dragover.fileDrag drop.fileDrag": function dragoverFileDragDropFileDrag(e) {
+          e.preventDefault();
+        }
+      });
+    },
+    _disableDocumentEvents: function _disableDocumentEvents() {
+      var $doc = $("html");
+
+      $doc.off(".fileDrag");
+      this._disableDraghover($doc);
+    },
+    _zoneEvents: ["showZone.fileDrag", "hideZone.fileDrag", "draghoverstart.zone", "draghoverend.zone", "drop"].join(" "),
+    _canSetFiles: function _canSetFiles(fileList) {
+      var testEl = document.createElement("input");
+      testEl.type = "file";
+      try {
+        testEl.files = fileList;
+      } catch (e) {
+        return false;
+      }
+      return true;
+    },
+    _handleDrop: function _handleDrop(e, el) {
+      var files = e.originalEvent.dataTransfer.files,
+          $el = $(el);
+      if (files === undefined || files === null) {
+        // 1. The FileList object isn't supported by this browser, and
+        // there's nothing else we can try. (< IE 10)
+        console.log("Dropping files is not supported on this browser. (no FileList)");
+      } else if (!this._canSetFiles(files)) {
+        // 2. The browser doesn't support assigning a type=file input's .files
+        // property, but we do have a FileList to work with. (IE10+/Edge)
+        $el.val("");
+        uploadDroppedFilesIE10Plus(el, files);
+      } else {
+        // 3. The browser supports FileList and input.files assignment.
+        // (Chrome, Safari)
+        $el.val("");
+        el.files = e.originalEvent.dataTransfer.files;
+      }
+    },
+    _activeClass: "shiny-file-input-active",
+    _overClass: "shiny-file-input-over",
+    _isIE9: function _isIE9() {
+      try {
+        return window.navigator.userAgent.match(/MSIE 9\./) && true || false;
+      } catch (e) {
+        return false;
+      }
+    },
     subscribe: function subscribe(el, callback) {
-      $(el).on('change.fileInputBinding', uploadFiles);
+      var _this = this;
+
+      var $el = $(el);
+      // Here we try to set up the necessary events for Drag and Drop ("DnD") on
+      // every browser except IE9. We specifically exclude IE9 because it's one
+      // browser that supports just enough of the functionality we need to be
+      // confusing. In particular, it supports drag events, so drop zones will
+      // highlight when a file is dragged into the browser window. It doesn't
+      // support the FileList object though, so the user's expectation that DnD is
+      // supported based on this highlighting would be incorrect.
+      if (!this._isIE9()) {
+        (function () {
+          var $zone = _this._getZone(el),
+              getState = function getState() {
+            return $el.data("state");
+          },
+              setState = function setState(newState) {
+            return $el.data("state", newState);
+          },
+              transition = multimethod().dispatch(function (e) {
+            return [getState(), e.type];
+          }).when(["plain", "showZone"], function (e) {
+            $zone.removeClass(_this._overClass);
+            $zone.addClass(_this._activeClass);
+            setState("activated");
+          }).when(["activated", "hideZone"], function (e) {
+            $zone.removeClass(_this._overClass);
+            $zone.removeClass(_this._activeClass);
+            setState("plain");
+          }).when(["activated", "draghoverstart"], function (e) {
+            $zone.addClass(_this._overClass);
+            $zone.removeClass(_this._activeClass);
+            setState("over");
+          })
+          // A "drop" event always coincides with a "draghoverend" event. Since
+          // we handle all draghoverend events the same way, by clearing our
+          // over-style and reverting to "activated" state, we only need to
+          // worry about handling the file upload itself here.
+          .when(["over", "drop"], function (e) {
+            _this._handleDrop(e, el);
+            // State change taken care of by ["over", "draghoverend"] handler.
+          }).when(["over", "draghoverend"], function (e) {
+            $zone.removeClass(_this._overClass);
+            $zone.addClass(_this._activeClass);
+            setState("activated");
+          })
+          // This next case happens when the window (like Finder) that a file is
+          // being dragged from occludes the browser window, and the dragged
+          // item first enters the page over a drop zone instead of entering
+          // through a none-zone element.
+          //
+          // The dragenter event that caused this draghoverstart to occur will
+          // bubble to the document, where it will cause a showZone event to be
+          // fired, and drop zones will activate and their states will
+          // transition to "activated".
+          //
+          // We schedule a function to be run *after* that happens, using
+          // setTimeout. The function we schedule will set the current element's
+          // state to "over", preparing us to deal with a subsequent
+          // "draghoverend".
+          .when(["plain", "draghoverstart"], function (e) {
+            window.setTimeout(function () {
+              $zone.addClass(_this._overClass);
+              $zone.removeClass(_this._activeClass);
+              setState("over");
+            }, 0);
+          }).else(function (e) {
+            console.log("fileInput DnD unhandled transition", getState(), e.type, e);
+          });
+
+          if ($fileInputs.length === 0) _this._enableDocumentEvents();
+          setState("plain");
+          $zone.on(_this._zoneEvents, transition);
+          $fileInputs = $fileInputs.add(el);
+          _this._enableDraghover($zone, ".zone");
+        })();
+      }
+
+      $el.on("change.fileInputBinding", uploadFiles);
     },
+
     unsubscribe: function unsubscribe(el) {
-      $(el).off('.fileInputBinding');
+      var $el = $(el),
+          $zone = this._getZone(el);
+
+      $el.removeData("state");
+
+      $zone.removeClass(this._overClass);
+      $zone.removeClass(this._activeClass);
+
+      this._disableDraghover($zone);
+
+      // Clean up local event handlers.
+      $el.off(".fileInputBinding");
+      $zone.off(this._zoneEvents);
+
+      // Remove el from list of inputs and (maybe) clean up global event handlers.
+      $fileInputs = $fileInputs.not(el);
+      if ($fileInputs.length === 0) this._disableDocumentEvents();
     }
   });
   inputBindings.register(fileInputBinding, 'shiny.fileInputBinding');
@@ -5003,19 +5993,27 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     var inputsRate = new InputRateDecorator(inputsEvent);
     var inputsDefer = new InputDeferDecorator(inputsEvent);
 
-    // By default, use rate decorator
-    var inputs = inputsRate;
-    $('input[type="submit"], button[type="submit"]').each(function () {
+    var inputs;
+    if ($('input[type="submit"], button[type="submit"]').length > 0) {
       // If there is a submit button on the page, use defer decorator
       inputs = inputsDefer;
-      $(this).click(function (event) {
-        event.preventDefault();
-        inputsDefer.submit();
+
+      $('input[type="submit"], button[type="submit"]').each(function () {
+        $(this).click(function (event) {
+          event.preventDefault();
+          inputsDefer.submit();
+        });
       });
-    });
+    } else {
+      // By default, use rate decorator
+      inputs = inputsRate;
+    }
+
+    inputs = new InputValidateDecorator(inputs);
 
-    exports.onInputChange = function (name, value) {
-      inputs.setInput(name, value);
+    exports.onInputChange = function (name, value, opts) {
+      opts = addDefaultInputOpts(opts);
+      inputs.setInput(name, value, opts);
     };
 
     var boundInputs = {};
@@ -5026,7 +6024,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
         var value = binding.getValue(el);
         var type = binding.getType(el);
         if (type) id = id + ":" + type;
-        inputs.setInput(id, value, !allowDeferred);
+
+        var opts = { immediate: !allowDeferred, binding: binding, el: el };
+        inputs.setInput(id, value, opts);
       }
     }
 
@@ -5035,7 +6035,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
 
       var bindings = inputBindings.getBindings();
 
-      var currentValues = {};
+      var inputItems = {};
 
       for (var i = 0; i < bindings.length; i++) {
         var binding = bindings[i].binding;
@@ -5049,7 +6049,14 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
 
           var type = binding.getType(el);
           var effectiveId = type ? id + ":" + type : id;
-          currentValues[effectiveId] = binding.getValue(el);
+          inputItems[effectiveId] = {
+            value: binding.getValue(el),
+            opts: {
+              immediate: true,
+              binding: binding,
+              el: el
+            }
+          };
 
           /*jshint loopfunc:true*/
           var thisCallback = function () {
@@ -5078,14 +6085,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
             binding: binding,
             bindingType: 'input'
           });
-
-          if (shinyapp.isConnected()) {
-            valueChangeCallback(binding, el, false);
-          }
         }
       }
 
-      return currentValues;
+      return inputItems;
     }
 
     function unbindInputs() {
@@ -5125,12 +6128,11 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       unbindOutputs(scope, includeSelf);
     }
     exports.bindAll = function (scope) {
-      // _bindAll alone returns initial values, it doesn't send them to the
-      // server. export.bindAll needs to send the values to the server, so we
-      // wrap _bindAll in a closure that does that.
-      var currentValues = _bindAll(scope);
-      $.each(currentValues, function (name, value) {
-        inputs.setInput(name, value);
+      // _bindAll returns input values; it doesn't send them to the server.
+      // export.bindAll needs to send the values to the server.
+      var currentInputItems = _bindAll(scope);
+      $.each(currentInputItems, function (name, item) {
+        inputs.setInput(name, item.value, item.opts);
       });
 
       // Not sure if the iframe stuff is an intrinsic part of bindAll, but bindAll
@@ -5173,7 +6175,16 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     // Initialize all input objects in the document, before binding
     initializeInputs(document);
 
-    var initialValues = _bindAll(document);
+    // The input values returned by _bindAll() each have a structure like this:
+    //   { value: 123, opts: { ... } }
+    // We want to only keep the value. This is because when the initialValues is
+    // passed to ShinyApp.connect(), the ShinyApp object stores the
+    // initialValues object for the duration of the session, and the opts may
+    // have a reference to the DOM element, which would prevent it from being
+    // GC'd.
+    var initialValues = mapValues(_bindAll(document), function (x) {
+      return x.value;
+    });
 
     // The server needs to know the size of each image and plot output element,
     // in case it is auto-sizing
@@ -5327,12 +6338,28 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
     initialValues['.clientdata_url_hostname'] = window.location.hostname;
     initialValues['.clientdata_url_port'] = window.location.port;
     initialValues['.clientdata_url_pathname'] = window.location.pathname;
+
+    // Send initial URL search (query string) and update it if it changes
     initialValues['.clientdata_url_search'] = window.location.search;
+
+    $(window).on('pushstate', function (e) {
+      inputs.setInput('.clientdata_url_search', window.location.search);
+    });
+
+    $(window).on('popstate', function (e) {
+      inputs.setInput('.clientdata_url_search', window.location.search);
+    });
+
     // This is only the initial value of the hash. The hash can change, but
-    // a reactive version of this isn't sent because w atching for changes can
+    // a reactive version of this isn't sent because watching for changes can
     // require polling on some browsers. The JQuery hashchange plugin can be
     // used if this capability is important.
     initialValues['.clientdata_url_hash_initial'] = window.location.hash;
+    initialValues['.clientdata_url_hash'] = window.location.hash;
+
+    $(window).on('hashchange', function (e) {
+      inputs.setInput('.clientdata_url_hash', location.hash);
+    });
 
     // The server needs to know what singletons were rendered as part of
     // the page loading
@@ -5347,6 +6374,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
       }
     });
 
+    // IE8 and IE9 have some limitations with data URIs
+    initialValues['.clientdata_allowDataUriScheme'] = typeof WebSocket !== 'undefined';
+
     // We've collected all the initial values--start the server process!
     inputsNoResend.reset(initialValues);
     shinyapp.connect(initialValues);
diff --git a/inst/www/shared/shiny.js.map b/inst/www/shared/shiny.js.map
index 7cda50a..06a3f72 100644
--- a/inst/www/shared/shiny.js.map
+++ b/inst/www/shared/shiny.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../../srcjs/_start.js","../../srcjs/utils.js","../../srcjs/browser.js","../../srcjs/input_rate.js","../../srcjs/shinyapp.js","../../srcjs/notifications.js","../../srcjs/modal.js","../../srcjs/file_processor.js","../../srcjs/binding_registry.js","../../srcjs/output_binding.js","../../srcjs/output_binding_text.js","../../srcjs/output_binding_image.js","../../srcjs/output_binding_html.js","../../srcjs/output_binding_downloadlink.js","../../srcjs/output_binding_datat [...]
\ No newline at end of file
+{"version":3,"sources":["../../srcjs/_start.js","../../srcjs/utils.js","../../srcjs/browser.js","../../srcjs/input_rate.js","../../srcjs/shinyapp.js","../../srcjs/notifications.js","../../srcjs/modal.js","../../srcjs/file_processor.js","../../srcjs/binding_registry.js","../../srcjs/output_binding.js","../../srcjs/output_binding_text.js","../../srcjs/output_binding_image.js","../../srcjs/output_binding_html.js","../../srcjs/output_binding_downloadlink.js","../../srcjs/output_binding_datat [...]
\ No newline at end of file
diff --git a/inst/www/shared/shiny.min.js b/inst/www/shared/shiny.min.js
index a32a51f..0fa1130 100644
--- a/inst/www/shared/shiny.min.js
+++ b/inst/www/shared/shiny.min.js
@@ -1,6 +1,6 @@
-/*! shiny 1.0.0 | (c) 2012-2017 RStudio, Inc. | License: GPL-3 | file LICENSE */
+/*! shiny 1.0.5 | (c) 2012-2017 RStudio, Inc. | License: GPL-3 | file LICENSE */
 
-"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a};!function(){function escapeHTML(a){return a.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"&#x2F;")}function randomId(){return Math.floor(4294967296+64424509440*Math.random()).toString(16 [...]
-return mergeSort(this.bindings,function(a,b){return b.priority-a.priority})}}).call(BindingRegistry.prototype);var inputBindings=exports.inputBindings=new BindingRegistry,outputBindings=exports.outputBindings=new BindingRegistry,OutputBinding=exports.OutputBinding=function(){};(function(){this.find=function(a){throw"Not implemented"},this.getId=function(a){return a["data-input-id"]||a.id},this.onValueChange=function(a,b){this.clearError(a),this.renderValue(a,b)},this.onValueError=functio [...]
-b(!0)}),$(a).on("changeDate.dateRangeInputBinding change.dateRangeInputBinding",function(a){b(!1)})},unsubscribe:function(a){$(a).off(".dateRangeInputBinding")}}),inputBindings.register(dateRangeInputBinding,"shiny.dateRangeInput");var selectInputBinding=new InputBinding;$.extend(selectInputBinding,{find:function(a){return $(a).find("select")},getId:function(a){return InputBinding.prototype.getId.call(this,a)||a.name},getValue:function(a){return $(a).val()},setValue:function(a,b){var c=t [...]
+"use strict";function _defineProperty(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},_slicedToArray=function(){function a(a,b){var c=[],d=!0,e=!1,f=void 0;try{for(var g,h=a[Symbol.iterator]();!(d=(g=h.next()).done)&& [...]
+return{$tabset:c,id:d}}if(null!==a.target){var e=l.parent("ul");if(e.hasClass("dropdown-menu")){var f=e.attr("data-tabsetid");return{$tabset:e,id:f}}}return null}var d=getTabset(a.inputId),e=d,f=getTabContent(e),g=d.attr("data-tabsetid"),h=$(a.divTag.html),i=$(a.liTag.html),j=i.find("> a"),k=null,l=null;null!==a.target&&(k=getTargetTabs(e,f,a.target),l=k.$liTag);var m=c();if(null!==m){if("dropdown"===j.attr("data-toggle"))throw"Cannot insert a navbarMenu inside another one";e=m.$tabset,g [...]
+find:function(a){return $(a).find('input[type="number"]')},getValue:function(a){var b=$(a).val();return/^\s*$/.test(b)?null:isNaN(b)?b:+b},setValue:function(a,b){a.value=b},getType:function(a){return"shiny.number"},receiveMessage:function(a,b){b.hasOwnProperty("value")&&(a.value=b.value),b.hasOwnProperty("min")&&(a.min=b.min),b.hasOwnProperty("max")&&(a.max=b.max),b.hasOwnProperty("step")&&(a.step=b.step),b.hasOwnProperty("label")&&$(a).parent().find('label[for="'+$escape(a.id)+'"]').tex [...]
 //# sourceMappingURL=shiny.min.js.map
\ No newline at end of file
diff --git a/inst/www/shared/shiny.min.js.map b/inst/www/shared/shiny.min.js.map
index 816a860..24051de 100644
--- a/inst/www/shared/shiny.min.js.map
+++ b/inst/www/shared/shiny.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../../srcjs/_start.js","../../srcjs/utils.js","../../srcjs/input_rate.js","../../srcjs/output_binding_html.js","../../srcjs/input_binding_slider.js","../../srcjs/input_binding_fileinput.js","../../srcjs/init_shiny.js","../../srcjs/browser.js","../../srcjs/shinyapp.js","../../srcjs/notifications.js","../../srcjs/modal.js","../../srcjs/file_processor.js","../../srcjs/binding_registry.js","../../srcjs/output_binding.js","../../srcjs/output_binding_text.js","../../sr [...]
\ No newline at end of file
+{"version":3,"sources":["../../srcjs/_start.js","../../srcjs/utils.js","../../srcjs/input_rate.js","../../srcjs/output_binding_html.js","../../srcjs/input_binding_slider.js","../../srcjs/input_binding_fileinput.js","../../srcjs/init_shiny.js","../../srcjs/browser.js","../../srcjs/shinyapp.js","../../srcjs/notifications.js","../../srcjs/modal.js","../../srcjs/file_processor.js","../../srcjs/binding_registry.js","../../srcjs/output_binding.js","../../srcjs/output_binding_text.js","../../sr [...]
\ No newline at end of file
diff --git a/man/NS.Rd b/man/NS.Rd
index 076b321..678a25d 100644
--- a/man/NS.Rd
+++ b/man/NS.Rd
@@ -41,4 +41,3 @@ into a namespaced one, by combining them with \code{ns.sep} in between.
 \url{http://shiny.rstudio.com/articles/modules.html}
 }
 \keyword{datasets}
-
diff --git a/man/Progress.Rd b/man/Progress.Rd
index 2c71cc5..4ce0d11 100644
--- a/man/Progress.Rd
+++ b/man/Progress.Rd
@@ -24,8 +24,7 @@ detail message (if any). The detail message will be shown with a
 de-emphasized appearance relative to \code{message}.}
 
 \item{value}{A numeric value at which to set
-the progress bar, relative to \code{min} and \code{max}.
-\code{NULL} hides the progress bar, if it is currently visible.}
+the progress bar, relative to \code{min} and \code{max}.}
 
 \item{style}{Progress display style. If \code{"notification"} (the default),
 the progress indicator will show using Shiny's notification API. If
@@ -112,4 +111,3 @@ shinyApp(ui, server)
 \code{\link{withProgress}}
 }
 \keyword{datasets}
-
diff --git a/man/absolutePanel.Rd b/man/absolutePanel.Rd
index 3e7996a..e568313 100644
--- a/man/absolutePanel.Rd
+++ b/man/absolutePanel.Rd
@@ -80,4 +80,3 @@ specify \code{0} for \code{top}, \code{left}, \code{right}, and \code{bottom}
 rather than the more obvious \code{width = "100\%"} and \code{height =
 "100\%"}.
 }
-
diff --git a/man/actionButton.Rd b/man/actionButton.Rd
index 98ddd51..637c9b2 100644
--- a/man/actionButton.Rd
+++ b/man/actionButton.Rd
@@ -56,7 +56,7 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{observeEvent}} and \code{\link{eventReactive}}
 
-Other input.elements: \code{\link{checkboxGroupInput}},
+Other input elements: \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
   \code{\link{numericInput}}, \code{\link{passwordInput}},
@@ -64,4 +64,3 @@ Other input.elements: \code{\link{checkboxGroupInput}},
   \code{\link{sliderInput}}, \code{\link{submitButton}},
   \code{\link{textAreaInput}}, \code{\link{textInput}}
 }
-
diff --git a/man/addResourcePath.Rd b/man/addResourcePath.Rd
index 492ff7a..93a8a4b 100644
--- a/man/addResourcePath.Rd
+++ b/man/addResourcePath.Rd
@@ -32,4 +32,3 @@ addResourcePath('datasets', system.file('data', package='datasets'))
 \seealso{
 \code{\link{singleton}}
 }
-
diff --git a/man/applyInputHandlers.Rd b/man/applyInputHandlers.Rd
index 4f2e130..3329aed 100644
--- a/man/applyInputHandlers.Rd
+++ b/man/applyInputHandlers.Rd
@@ -27,4 +27,3 @@ output.
 registerInputHandler
 }
 \keyword{internal}
-
diff --git a/man/bookmarkButton.Rd b/man/bookmarkButton.Rd
index 183e3f5..a223d26 100644
--- a/man/bookmarkButton.Rd
+++ b/man/bookmarkButton.Rd
@@ -70,4 +70,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{enableBookmarking}} for more examples.
 }
-
diff --git a/man/bootstrapLib.Rd b/man/bootstrapLib.Rd
index 0d3752a..218daf4 100644
--- a/man/bootstrapLib.Rd
+++ b/man/bootstrapLib.Rd
@@ -21,4 +21,3 @@ It isn't necessary to call this function if you use
 \code{\link{pageWithSidebar}}, and \code{\link{navbarPage}}, because they
 already include the Bootstrap web dependencies.
 }
-
diff --git a/man/bootstrapPage.Rd b/man/bootstrapPage.Rd
index 6ec2b3e..b682338 100644
--- a/man/bootstrapPage.Rd
+++ b/man/bootstrapPage.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/bootstrap.R
 \name{bootstrapPage}
-\alias{basicPage}
 \alias{bootstrapPage}
+\alias{basicPage}
 \title{Create a Bootstrap page}
 \usage{
 bootstrapPage(..., title = NULL, responsive = NULL, theme = NULL)
@@ -41,4 +41,3 @@ The \code{basicPage} function is deprecated, you should use the
 \seealso{
 \code{\link{fluidPage}}, \code{\link{fixedPage}}
 }
-
diff --git a/man/brushOpts.Rd b/man/brushOpts.Rd
index 36ce4c1..3c80877 100644
--- a/man/brushOpts.Rd
+++ b/man/brushOpts.Rd
@@ -49,4 +49,3 @@ This generates an object representing brushing options, to be passed as the
 \code{brush} argument of \code{\link{imageOutput}} or
 \code{\link{plotOutput}}.
 }
-
diff --git a/man/brushedPoints.Rd b/man/brushedPoints.Rd
index f09ba79..1760b47 100644
--- a/man/brushedPoints.Rd
+++ b/man/brushedPoints.Rd
@@ -69,4 +69,3 @@ using just the x or y variable, whichever is appropriate.
 \seealso{
 \code{\link{plotOutput}} for example usage.
 }
-
diff --git a/man/callModule.Rd b/man/callModule.Rd
index f45fd73..a31bc8b 100644
--- a/man/callModule.Rd
+++ b/man/callModule.Rd
@@ -29,4 +29,3 @@ modules are easier to reuse and easier to reason about. See the article at
 \seealso{
 \url{http://shiny.rstudio.com/articles/modules.html}
 }
-
diff --git a/man/checkboxGroupInput.Rd b/man/checkboxGroupInput.Rd
index 1f407e0..8b0eef9 100644
--- a/man/checkboxGroupInput.Rd
+++ b/man/checkboxGroupInput.Rd
@@ -4,8 +4,8 @@
 \alias{checkboxGroupInput}
 \title{Checkbox Group Input Control}
 \usage{
-checkboxGroupInput(inputId, label, choices, selected = NULL, inline = FALSE,
-  width = NULL)
+checkboxGroupInput(inputId, label, choices = NULL, selected = NULL,
+  inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL)
 }
 \arguments{
 \item{inputId}{The \code{input} slot that will be used to access the value.}
@@ -13,7 +13,10 @@ checkboxGroupInput(inputId, label, choices, selected = NULL, inline = FALSE,
 \item{label}{Display label for the control, or \code{NULL} for no label.}
 
 \item{choices}{List of values to show checkboxes for. If elements of the list
-are named then that name rather than the value is displayed to the user.}
+are named then that name rather than the value is displayed to the user. If
+this argument is provided, then \code{choiceNames} and \code{choiceValues}
+must not be provided, and vice-versa. The values should be strings; other
+types (such as logicals and numbers) will be coerced to strings.}
 
 \item{selected}{The values that should be initially selected, if any.}
 
@@ -21,6 +24,16 @@ are named then that name rather than the value is displayed to the user.}
 
 \item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
 see \code{\link{validateCssUnit}}.}
+
+\item{choiceNames, choiceValues}{List of names and values, respectively,
+that are displayed to the user in the app and correspond to the each
+choice (for this reason, \code{choiceNames} and \code{choiceValues}
+must have the same length). If either of these arguments is
+provided, then the other \emph{must} be provided and \code{choices}
+\emph{must not} be provided. The advantage of using both of these over
+a named list for \code{choices} is that \code{choiceNames} allows any
+type of UI object to be passed through (tag objects, icons, HTML code,
+...), instead of just simple text. See Examples.}
 }
 \value{
 A list of HTML elements that can be added to a UI definition.
@@ -42,19 +55,39 @@ ui <- fluidPage(
   tableOutput("data")
 )
 
-server <- function(input, output) {
+server <- function(input, output, session) {
   output$data <- renderTable({
     mtcars[, c("mpg", input$variable), drop = FALSE]
   }, rownames = TRUE)
 }
 
 shinyApp(ui, server)
+
+ui <- fluidPage(
+  checkboxGroupInput("icons", "Choose icons:",
+    choiceNames =
+      list(icon("calendar"), icon("bed"),
+           icon("cog"), icon("bug")),
+    choiceValues =
+      list("calendar", "bed", "cog", "bug")
+  ),
+  textOutput("txt")
+)
+
+server <- function(input, output, session) {
+  output$txt <- renderText({
+    icons <- paste(input$icons, collapse = ", ")
+    paste("You chose", icons)
+  })
+}
+
+shinyApp(ui, server)
 }
 }
 \seealso{
 \code{\link{checkboxInput}}, \code{\link{updateCheckboxGroupInput}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
   \code{\link{numericInput}}, \code{\link{passwordInput}},
@@ -62,4 +95,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{sliderInput}}, \code{\link{submitButton}},
   \code{\link{textAreaInput}}, \code{\link{textInput}}
 }
-
diff --git a/man/checkboxInput.Rd b/man/checkboxInput.Rd
index 6668f4a..611f08f 100644
--- a/man/checkboxInput.Rd
+++ b/man/checkboxInput.Rd
@@ -39,7 +39,7 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{checkboxGroupInput}}, \code{\link{updateCheckboxInput}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{dateInput}}, \code{\link{dateRangeInput}},
   \code{\link{fileInput}}, \code{\link{numericInput}},
@@ -48,4 +48,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{submitButton}}, \code{\link{textAreaInput}},
   \code{\link{textInput}}
 }
-
diff --git a/man/clickOpts.Rd b/man/clickOpts.Rd
index 0f05e9c..b0540f2 100644
--- a/man/clickOpts.Rd
+++ b/man/clickOpts.Rd
@@ -19,4 +19,3 @@ This generates an object representing click options, to be passed as the
 \code{click} argument of \code{\link{imageOutput}} or
 \code{\link{plotOutput}}.
 }
-
diff --git a/man/column.Rd b/man/column.Rd
index a244f9f..47c4552 100644
--- a/man/column.Rd
+++ b/man/column.Rd
@@ -64,4 +64,3 @@ shinyApp(ui, server = function(input, output) { })
 \seealso{
 \code{\link{fluidRow}}, \code{\link{fixedRow}}.
 }
-
diff --git a/man/conditionalPanel.Rd b/man/conditionalPanel.Rd
index 7841a03..9749aec 100644
--- a/man/conditionalPanel.Rd
+++ b/man/conditionalPanel.Rd
@@ -4,13 +4,16 @@
 \alias{conditionalPanel}
 \title{Conditional Panel}
 \usage{
-conditionalPanel(condition, ...)
+conditionalPanel(condition, ..., ns = NS(NULL))
 }
 \arguments{
 \item{condition}{A JavaScript expression that will be evaluated repeatedly to
 determine whether the panel should be displayed.}
 
 \item{...}{Elements to include in the panel.}
+
+\item{ns}{The \code{\link[=NS]{namespace}} object of the current module, if
+any.}
 }
 \description{
 Creates a panel that is visible or not, depending on the value of a
@@ -32,28 +35,50 @@ You are not recommended to use special JavaScript characters such as a
   value.
 }
 \examples{
-sidebarPanel(
-   selectInput(
-      "plotType", "Plot Type",
-      c(Scatter = "scatter",
-        Histogram = "hist")),
-
-   # Only show this panel if the plot type is a histogram
-   conditionalPanel(
-      condition = "input.plotType == 'hist'",
-      selectInput(
-         "breaks", "Breaks",
-         c("Sturges",
-           "Scott",
-           "Freedman-Diaconis",
-           "[Custom]" = "custom")),
-
-      # Only show this panel if Custom is selected
+## Only run this example in interactive R sessions
+if (interactive()) {
+  ui <- fluidPage(
+    sidebarPanel(
+      selectInput("plotType", "Plot Type",
+        c(Scatter = "scatter", Histogram = "hist")
+      ),
+      # Only show this panel if the plot type is a histogram
       conditionalPanel(
-         condition = "input.breaks == 'custom'",
-         sliderInput("breakCount", "Break Count", min=1, max=1000, value=10)
+        condition = "input.plotType == 'hist'",
+        selectInput(
+          "breaks", "Breaks",
+          c("Sturges", "Scott", "Freedman-Diaconis", "[Custom]" = "custom")
+        ),
+        # Only show this panel if Custom is selected
+        conditionalPanel(
+          condition = "input.breaks == 'custom'",
+          sliderInput("breakCount", "Break Count", min = 1, max = 50, value = 10)
+        )
       )
-   )
-)
-}
+    ),
+    mainPanel(
+      plotOutput("plot")
+    )
+  )
+
+  server <- function(input, output) {
+    x <- rnorm(100)
+    y <- rnorm(100)
 
+    output$plot <- renderPlot({
+      if (input$plotType == "scatter") {
+        plot(x, y)
+      } else {
+        breaks <- input$breaks
+        if (breaks == "custom") {
+          breaks <- input$breakCount
+        }
+
+        hist(x, breaks = breaks)
+      }
+    })
+  }
+
+  shinyApp(ui, server)
+}
+}
diff --git a/man/createWebDependency.Rd b/man/createWebDependency.Rd
index baa302b..12c7fa5 100644
--- a/man/createWebDependency.Rd
+++ b/man/createWebDependency.Rd
@@ -4,12 +4,18 @@
 \alias{createWebDependency}
 \title{Create a web dependency}
 \usage{
-createWebDependency(dependency)
+createWebDependency(dependency, scrubFile = TRUE)
 }
 \arguments{
 \item{dependency}{A single HTML dependency object, created using
-\code{\link[htmltools]{htmlDependency}}. If the \code{src} value is named, then
-\code{href} and/or \code{file} names must be present.}
+\code{\link[htmltools]{htmlDependency}}. If the \code{src} value is named,
+then \code{href} and/or \code{file} names must be present.}
+
+\item{scrubFile}{If TRUE (the default), remove \code{src$file} for the
+dependency. This prevents the local file path from being sent to the client
+when dynamic web dependencies are used. If FALSE, don't remove
+\code{src$file}. Setting it to FALSE should be needed only in very unusual
+cases.}
 }
 \value{
 A single HTML dependency object that has an \code{href}-named element
@@ -21,4 +27,3 @@ served over Shiny's HTTP server. This function works by using
 \code{\link{addResourcePath}} to map the HTML dependency's directory to a
 URL.
 }
-
diff --git a/man/dateInput.Rd b/man/dateInput.Rd
index d3e403c..6a8a802 100644
--- a/man/dateInput.Rd
+++ b/man/dateInput.Rd
@@ -97,7 +97,7 @@ shinyApp(ui, server = function(input, output) { })
 \seealso{
 \code{\link{dateRangeInput}}, \code{\link{updateDateInput}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
@@ -106,4 +106,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{sliderInput}}, \code{\link{submitButton}},
   \code{\link{textAreaInput}}, \code{\link{textInput}}
 }
-
diff --git a/man/dateRangeInput.Rd b/man/dateRangeInput.Rd
index 7a6cb11..859858f 100644
--- a/man/dateRangeInput.Rd
+++ b/man/dateRangeInput.Rd
@@ -114,7 +114,7 @@ shinyApp(ui, server = function(input, output) { })
 \seealso{
 \code{\link{dateInput}}, \code{\link{updateDateRangeInput}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{fileInput}}, \code{\link{numericInput}},
@@ -123,4 +123,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{submitButton}}, \code{\link{textAreaInput}},
   \code{\link{textInput}}
 }
-
diff --git a/man/dblclickOpts.Rd b/man/dblclickOpts.Rd
index 4917067..5f9312e 100644
--- a/man/dblclickOpts.Rd
+++ b/man/dblclickOpts.Rd
@@ -23,4 +23,3 @@ This generates an object representing dobule-click options, to be passed as
 the \code{dblclick} argument of \code{\link{imageOutput}} or
 \code{\link{plotOutput}}.
 }
-
diff --git a/man/debounce.Rd b/man/debounce.Rd
index 37803da..b12d1f1 100644
--- a/man/debounce.Rd
+++ b/man/debounce.Rd
@@ -78,6 +78,7 @@ window.
   time each subsequent event is considered is already after the time window
   has expired.
 }
+
 \examples{
 ## Only run examples in interactive R sessions
 if (interactive()) {
@@ -119,4 +120,3 @@ shinyApp(ui, server)
 }
 
 }
-
diff --git a/man/domains.Rd b/man/domains.Rd
index ba45786..dc9453e 100644
--- a/man/domains.Rd
+++ b/man/domains.Rd
@@ -3,8 +3,9 @@
 \name{domains}
 \alias{domains}
 \alias{getDefaultReactiveDomain}
-\alias{onReactiveDomainEnded}
 \alias{withReactiveDomain}
+\alias{onReactiveDomainEnded}
+\alias{domains}
 \title{Reactive domains}
 \usage{
 getDefaultReactiveDomain()
@@ -51,4 +52,3 @@ as a convenience function for registering callbacks. If the reactive domain
 is \code{NULL} and \code{failIfNull} is \code{FALSE}, then the callback will
 never be invoked.
 }
-
diff --git a/man/downloadButton.Rd b/man/downloadButton.Rd
index 5a5018e..0bcb4ef 100644
--- a/man/downloadButton.Rd
+++ b/man/downloadButton.Rd
@@ -3,6 +3,7 @@
 \name{downloadButton}
 \alias{downloadButton}
 \alias{downloadLink}
+\alias{downloadLink}
 \title{Create a download button or link}
 \usage{
 downloadButton(outputId, label = "Download", class = NULL, ...)
@@ -43,6 +44,5 @@ downloadLink('downloadData', 'Download')
 
 }
 \seealso{
-downloadHandler
+\code{\link{downloadHandler}}
 }
-
diff --git a/man/downloadHandler.Rd b/man/downloadHandler.Rd
index 6decb7a..fc23b8b 100644
--- a/man/downloadHandler.Rd
+++ b/man/downloadHandler.Rd
@@ -61,4 +61,3 @@ server <- function(input, output) {
 shinyApp(ui, server)
 }
 }
-
diff --git a/man/enableBookmarking.Rd b/man/enableBookmarking.Rd
index 24e8105..1b804e1 100644
--- a/man/enableBookmarking.Rd
+++ b/man/enableBookmarking.Rd
@@ -228,4 +228,3 @@ shinyApp(ui, server)
 
   Also see \code{\link{updateQueryString}}.
 }
-
diff --git a/man/exportTestValues.Rd b/man/exportTestValues.Rd
index a11f3ca..c2d0025 100644
--- a/man/exportTestValues.Rd
+++ b/man/exportTestValues.Rd
@@ -68,4 +68,3 @@ shinyApp(
 )
 }
 }
-
diff --git a/man/exprToFunction.Rd b/man/exprToFunction.Rd
index 827261c..2e12f1e 100644
--- a/man/exprToFunction.Rd
+++ b/man/exprToFunction.Rd
@@ -58,4 +58,3 @@ tripleA <- renderTriple({
 isolate(tripleA())
 # "text, text, text"
 }
-
diff --git a/man/fileInput.Rd b/man/fileInput.Rd
index 9ac884b..656718d 100644
--- a/man/fileInput.Rd
+++ b/man/fileInput.Rd
@@ -4,7 +4,8 @@
 \alias{fileInput}
 \title{File Upload Control}
 \usage{
-fileInput(inputId, label, multiple = FALSE, accept = NULL, width = NULL)
+fileInput(inputId, label, multiple = FALSE, accept = NULL, width = NULL,
+  buttonLabel = "Browse...", placeholder = "No file selected")
 }
 \arguments{
 \item{inputId}{The \code{input} slot that will be used to access the value.}
@@ -20,6 +21,11 @@ what kind of files the server is expecting.}
 
 \item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
 see \code{\link{validateCssUnit}}.}
+
+\item{buttonLabel}{The label used on the button. Can be text or an HTML tag
+object.}
+
+\item{placeholder}{The text to show before a file has been uploaded.}
 }
 \description{
 Create a file upload control that can be used to upload one or more files.
@@ -84,7 +90,7 @@ shinyApp(ui, server)
 }
 }
 \seealso{
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{numericInput}},
@@ -93,4 +99,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{submitButton}}, \code{\link{textAreaInput}},
   \code{\link{textInput}}
 }
-
diff --git a/man/fillPage.Rd b/man/fillPage.Rd
index 6d5dcc8..7f7f2b9 100644
--- a/man/fillPage.Rd
+++ b/man/fillPage.Rd
@@ -81,4 +81,3 @@ fillPage(
   )
 )
 }
-
diff --git a/man/fillRow.Rd b/man/fillRow.Rd
index 9f03e5a..e3cfab1 100644
--- a/man/fillRow.Rd
+++ b/man/fillRow.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/bootstrap-layout.R
 \name{fillRow}
-\alias{fillCol}
 \alias{fillRow}
+\alias{fillCol}
 \title{Flex Box-based row/column layouts}
 \usage{
 fillRow(..., flex = 1, width = "100\%", height = "100\%")
@@ -75,4 +75,3 @@ shinyApp(ui, server)
 
 }
 }
-
diff --git a/man/fixedPage.Rd b/man/fixedPage.Rd
index 0e1b084..6f4446a 100644
--- a/man/fixedPage.Rd
+++ b/man/fixedPage.Rd
@@ -68,4 +68,3 @@ shinyApp(ui, server = function(input, output) { })
 \seealso{
 \code{\link{column}}
 }
-
diff --git a/man/flowLayout.Rd b/man/flowLayout.Rd
index f6ab508..4cd6929 100644
--- a/man/flowLayout.Rd
+++ b/man/flowLayout.Rd
@@ -34,4 +34,3 @@ shinyApp(ui, server = function(input, output) { })
 \seealso{
 \code{\link{verticalLayout}}
 }
-
diff --git a/man/fluidPage.Rd b/man/fluidPage.Rd
index 5f5b1ca..b179891 100644
--- a/man/fluidPage.Rd
+++ b/man/fluidPage.Rd
@@ -102,4 +102,3 @@ shinyApp(ui, server = function(input, output) { })
 \seealso{
 \code{\link{column}}, \code{\link{sidebarLayout}}
 }
-
diff --git a/man/freezeReactiveValue.Rd b/man/freezeReactiveValue.Rd
index 444965f..b31f656 100644
--- a/man/freezeReactiveValue.Rd
+++ b/man/freezeReactiveValue.Rd
@@ -1,18 +1,24 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/reactives.R
-\name{freezeReactiveValue}
+\name{freezeReactiveVal}
+\alias{freezeReactiveVal}
 \alias{freezeReactiveValue}
 \title{Freeze a reactive value}
 \usage{
+freezeReactiveVal(x)
+
 freezeReactiveValue(x, name)
 }
 \arguments{
-\item{x}{A \code{\link{reactiveValues}} object (like \code{input}).}
+\item{x}{For \code{freezeReactiveValue}, a \code{\link{reactiveValues}}
+object (like \code{input}); for \code{freezeReactiveVal}, a
+\code{\link{reactiveVal}} object.}
 
 \item{name}{The name of a value in the \code{\link{reactiveValues}} object.}
 }
 \description{
-This freezes a reactive value. If the value is accessed while frozen, a
+These functions freeze a \code{\link{reactiveVal}}, or an element of a
+\code{\link{reactiveValues}}. If the value is accessed while frozen, a
 "silent" exception is raised and the operation is stopped. This is the same
 thing that happens if \code{req(FALSE)} is called. The value is thawed
 (un-frozen; accessing it will no longer raise an exception) when the current
@@ -58,4 +64,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{req}}
 }
-
diff --git a/man/getQueryString.Rd b/man/getQueryString.Rd
new file mode 100644
index 0000000..2fba74e
--- /dev/null
+++ b/man/getQueryString.Rd
@@ -0,0 +1,93 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/history.R
+\name{getQueryString}
+\alias{getQueryString}
+\alias{getUrlHash}
+\title{Get the query string / hash component from the URL}
+\usage{
+getQueryString(session = getDefaultReactiveDomain())
+
+getUrlHash(session = getDefaultReactiveDomain())
+}
+\arguments{
+\item{session}{A Shiny session object.}
+}
+\value{
+For \code{getQueryString}, a named list. For example, the query
+  string \code{?param1=value1&param2=value2} becomes \code{list(param1 =
+  value1, param2 = value2)}. For \code{getUrlHash}, a character vector with
+  the hash (including the leading \code{#} symbol).
+}
+\description{
+Two user friendly wrappers for getting the query string and the hash
+component from the app's URL.
+}
+\details{
+These can be particularly useful if you want to display different content
+depending on the values in the query string / hash (e.g. instead of basing
+the conditional on an input or a calculated reactive, you can base it on the
+query string). However, note that, if you're changing the query string / hash
+programatically from within the server code, you must use
+\code{updateQueryString(_yourNewQueryString_, mode = "push")}. The default
+\code{mode} for \code{updateQueryString} is \code{"replace"}, which doesn't
+raise any events, so any observers or reactives that depend on it will
+\emph{not} get triggered. However, if you're changing the query string / hash
+directly by typing directly in the browser and hitting enter, you don't have
+to worry about this.
+}
+\examples{
+## Only run this example in interactive R sessions
+if (interactive()) {
+
+  ## App 1: getQueryString
+  ## Printing the value of the query string
+  ## (Use the back and forward buttons to see how the browser
+  ## keeps a record of each state)
+  shinyApp(
+    ui = fluidPage(
+      textInput("txt", "Enter new query string"),
+      helpText("Format: ?param1=val1&param2=val2"),
+      actionButton("go", "Update"),
+      hr(),
+      verbatimTextOutput("query")
+    ),
+    server = function(input, output, session) {
+      observeEvent(input$go, {
+        updateQueryString(input$txt, mode = "push")
+      })
+      output$query <- renderText({
+        query <- getQueryString()
+        queryText <- paste(names(query), query,
+                       sep = "=", collapse=", ")
+        paste("Your query string is:\\n", queryText)
+      })
+    }
+  )
+
+  ## App 2: getUrlHash
+  ## Printing the value of the URL hash
+  ## (Use the back and forward buttons to see how the browser
+  ## keeps a record of each state)
+  shinyApp(
+    ui = fluidPage(
+      textInput("txt", "Enter new hash"),
+      helpText("Format: #hash"),
+      actionButton("go", "Update"),
+      hr(),
+      verbatimTextOutput("hash")
+    ),
+    server = function(input, output, session) {
+      observeEvent(input$go, {
+        updateQueryString(input$txt, mode = "push")
+      })
+      output$hash <- renderText({
+        hash <- getUrlHash()
+        paste("Your hash is:\\n", hash)
+      })
+    }
+  )
+}
+}
+\seealso{
+\code{\link{updateQueryString}}
+}
diff --git a/man/headerPanel.Rd b/man/headerPanel.Rd
index 14e0377..17b0c91 100644
--- a/man/headerPanel.Rd
+++ b/man/headerPanel.Rd
@@ -21,4 +21,3 @@ Create a header panel containing an application title.
 \examples{
 headerPanel("Hello Shiny!")
 }
-
diff --git a/man/helpText.Rd b/man/helpText.Rd
index a64974c..0305515 100644
--- a/man/helpText.Rd
+++ b/man/helpText.Rd
@@ -21,4 +21,3 @@ helpText("Note: while the data view will show only",
          "the specified number of observations, the",
          "summary will be based on the full dataset.")
 }
-
diff --git a/man/hoverOpts.Rd b/man/hoverOpts.Rd
index 9caa76f..bbfcc71 100644
--- a/man/hoverOpts.Rd
+++ b/man/hoverOpts.Rd
@@ -33,4 +33,3 @@ This generates an object representing hovering options, to be passed as the
 \code{hover} argument of \code{\link{imageOutput}} or
 \code{\link{plotOutput}}.
 }
-
diff --git a/man/htmlOutput.Rd b/man/htmlOutput.Rd
index 30c1c14..f1fc5a0 100644
--- a/man/htmlOutput.Rd
+++ b/man/htmlOutput.Rd
@@ -42,4 +42,3 @@ tags$ul(
   htmlOutput("summary", container = tags$li, class = "custom-li-output")
 )
 }
-
diff --git a/man/icon.Rd b/man/icon.Rd
index 2b19bb8..b253ceb 100644
--- a/man/icon.Rd
+++ b/man/icon.Rd
@@ -47,4 +47,3 @@ For lists of available icons, see
   \href{http://fontawesome.io/icons/}{http://fontawesome.io/icons/} and
   \href{http://getbootstrap.com/components/#glyphicons}{http://getbootstrap.com/components/#glyphicons}.
 }
-
diff --git a/man/inputPanel.Rd b/man/inputPanel.Rd
index 29df7be..8a803d5 100644
--- a/man/inputPanel.Rd
+++ b/man/inputPanel.Rd
@@ -13,4 +13,3 @@ inputPanel(...)
 A \code{\link{flowLayout}} with a grey border and light grey background,
 suitable for wrapping inputs.
 }
-
diff --git a/man/insertTab.Rd b/man/insertTab.Rd
new file mode 100644
index 0000000..55b7d33
--- /dev/null
+++ b/man/insertTab.Rd
@@ -0,0 +1,148 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/insert-tab.R
+\name{insertTab}
+\alias{insertTab}
+\alias{prependTab}
+\alias{appendTab}
+\alias{removeTab}
+\title{Dynamically insert/remove a tabPanel}
+\usage{
+insertTab(inputId, tab, target, position = c("before", "after"),
+  select = FALSE, session = getDefaultReactiveDomain())
+
+prependTab(inputId, tab, select = FALSE, menuName = NULL,
+  session = getDefaultReactiveDomain())
+
+appendTab(inputId, tab, select = FALSE, menuName = NULL,
+  session = getDefaultReactiveDomain())
+
+removeTab(inputId, target, session = getDefaultReactiveDomain())
+}
+\arguments{
+\item{inputId}{The \code{id} of the \code{tabsetPanel} (or
+\code{navlistPanel} or \code{navbarPage}) into which \code{tab} will
+be inserted/removed.}
+
+\item{tab}{The item to be added (must be created with \code{tabPanel},
+or with \code{navbarMenu}).}
+
+\item{target}{If inserting: the \code{value} of an existing
+\code{tabPanel}, next to which \code{tab} will be added.
+If removing: the \code{value} of the \code{tabPanel} that
+you want to remove. See Details if you want to insert next to/remove
+an entire \code{navbarMenu} instead.}
+
+\item{position}{Should \code{tab} be added before or after the
+\code{target} tab?}
+
+\item{select}{Should \code{tab} be selected upon being inserted?}
+
+\item{session}{The shiny session within which to call this function.}
+
+\item{menuName}{This argument should only be used when you want to
+prepend (or append) \code{tab} to the beginning (or end) of an
+existing \code{\link{navbarMenu}} (which must itself be part of
+an existing \code{\link{navbarPage}}). In this case, this argument
+should be the \code{menuName} that you gave your \code{navbarMenu}
+when you first created it (by default, this is equal to the value
+of the \code{title} argument). Note that you still need to set the
+\code{inputId} argument to whatever the \code{id} of the parent
+\code{navbarPage} is. If \code{menuName} is left as \code{NULL},
+\code{tab} will be prepended (or appended) to whatever
+\code{inputId} is.}
+}
+\description{
+Dynamically insert or remove a \code{\link{tabPanel}} (or a
+\code{\link{navbarMenu}}) from an existing \code{\link{tabsetPanel}},
+\code{\link{navlistPanel}} or \code{\link{navbarPage}}.
+}
+\details{
+When you want to insert a new tab before or after an existing tab, you
+should use \code{insertTab}. When you want to prepend a tab (i.e. add a
+tab to the beginning of the \code{tabsetPanel}), use \code{prependTab}.
+When you want to append a tab (i.e. add a tab to the end of the
+\code{tabsetPanel}), use \code{appendTab}.
+
+For \code{navbarPage}, you can insert/remove conventional
+\code{tabPanel}s (whether at the top level or nested inside a
+\code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
+For the latter case, \code{target} should be the \code{menuName} that
+you gave your \code{navbarMenu} when you first created it (by default,
+this is equal to the value of the \code{title} argument).
+}
+\examples{
+## Only run this example in interactive R sessions
+if (interactive()) {
+
+# example app for inserting/removing a tab
+ui <- fluidPage(
+  sidebarLayout(
+    sidebarPanel(
+      actionButton("add", "Add 'Dynamic' tab"),
+      actionButton("remove", "Remove 'Foo' tab")
+    ),
+    mainPanel(
+      tabsetPanel(id = "tabs",
+        tabPanel("Hello", "This is the hello tab"),
+        tabPanel("Foo", "This is the foo tab"),
+        tabPanel("Bar", "This is the bar tab")
+      )
+    )
+  )
+)
+server <- function(input, output, session) {
+  observeEvent(input$add, {
+    insertTab(inputId = "tabs",
+      tabPanel("Dynamic", "This a dynamically-added tab"),
+      target = "Bar"
+    )
+  })
+  observeEvent(input$remove, {
+    removeTab(inputId = "tabs", target = "Foo")
+  })
+}
+
+shinyApp(ui, server)
+
+
+# example app for prepending/appending a navbarMenu
+ui <- navbarPage("Navbar page", id = "tabs",
+  tabPanel("Home",
+    actionButton("prepend", "Prepend a navbarMenu"),
+    actionButton("append", "Append a navbarMenu")
+  )
+)
+server <- function(input, output, session) {
+  observeEvent(input$prepend, {
+    id <- paste0("Dropdown", input$prepend, "p")
+    prependTab(inputId = "tabs",
+      navbarMenu(id,
+        tabPanel("Drop1", paste("Drop1 page from", id)),
+        tabPanel("Drop2", paste("Drop2 page from", id)),
+        "------",
+        "Header",
+        tabPanel("Drop3", paste("Drop3 page from", id))
+      )
+    )
+  })
+  observeEvent(input$append, {
+    id <- paste0("Dropdown", input$append, "a")
+    appendTab(inputId = "tabs",
+      navbarMenu(id,
+        tabPanel("Drop1", paste("Drop1 page from", id)),
+        tabPanel("Drop2", paste("Drop2 page from", id)),
+        "------",
+        "Header",
+        tabPanel("Drop3", paste("Drop3 page from", id))
+      )
+    )
+  })
+}
+
+shinyApp(ui, server)
+
+}
+}
+\seealso{
+\code{\link{showTab}}
+}
diff --git a/man/insertUI.Rd b/man/insertUI.Rd
index b6c70c9..97f284a 100644
--- a/man/insertUI.Rd
+++ b/man/insertUI.Rd
@@ -86,4 +86,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{removeUI}}
 }
-
diff --git a/man/installExprFunction.Rd b/man/installExprFunction.Rd
index e6c3638..7435d4a 100644
--- a/man/installExprFunction.Rd
+++ b/man/installExprFunction.Rd
@@ -40,4 +40,3 @@ function named \code{func} in the current environment.
 Wraps \code{\link{exprToFunction}}; see that method's documentation
   for more documentation and examples.
 }
-
diff --git a/man/invalidateLater.Rd b/man/invalidateLater.Rd
index 681cd6e..84f0d58 100644
--- a/man/invalidateLater.Rd
+++ b/man/invalidateLater.Rd
@@ -63,4 +63,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{reactiveTimer}} is a slightly less safe alternative.
 }
-
diff --git a/man/is.reactivevalues.Rd b/man/is.reactivevalues.Rd
index 35169ae..b68f28a 100644
--- a/man/is.reactivevalues.Rd
+++ b/man/is.reactivevalues.Rd
@@ -15,4 +15,3 @@ Checks whether its argument is a reactivevalues object.
 \seealso{
 \code{\link{reactiveValues}}.
 }
-
diff --git a/man/isRunning.Rd b/man/isRunning.Rd
new file mode 100644
index 0000000..a17e0b6
--- /dev/null
+++ b/man/isRunning.Rd
@@ -0,0 +1,15 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/server.R
+\name{isRunning}
+\alias{isRunning}
+\title{Check whether a Shiny application is running}
+\usage{
+isRunning()
+}
+\value{
+\code{TRUE} if a Shiny application is currently running. Otherwise,
+  \code{FALSE}.
+}
+\description{
+This function tests whether a Shiny application is currently running.
+}
diff --git a/man/isolate.Rd b/man/isolate.Rd
index 61a3a63..c7b0ec3 100644
--- a/man/isolate.Rd
+++ b/man/isolate.Rd
@@ -77,4 +77,3 @@ isolate(fun())
 # isolate also works if the reactive expression accesses values from the
 # input object, like input$x
 }
-
diff --git a/man/knitr_methods.Rd b/man/knitr_methods.Rd
index 5e7ceb1..c82c610 100644
--- a/man/knitr_methods.Rd
+++ b/man/knitr_methods.Rd
@@ -1,10 +1,10 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/app.R
 \name{knitr_methods}
-\alias{knit_print.reactive}
+\alias{knitr_methods}
 \alias{knit_print.shiny.appobj}
 \alias{knit_print.shiny.render.function}
-\alias{knitr_methods}
+\alias{knit_print.reactive}
 \title{Knitr S3 methods}
 \usage{
 knit_print.shiny.appobj(x, ...)
@@ -24,4 +24,3 @@ knit_print.reactive(x, ..., inline = FALSE)
 These S3 methods are necessary to help Shiny applications and UI chunks embed
 themselves in knitr/rmarkdown documents.
 }
-
diff --git a/man/mainPanel.Rd b/man/mainPanel.Rd
index 579f95c..0bf5009 100644
--- a/man/mainPanel.Rd
+++ b/man/mainPanel.Rd
@@ -27,4 +27,3 @@ mainPanel(
    plotOutput("mpgPlot")
 )
 }
-
diff --git a/man/makeReactiveBinding.Rd b/man/makeReactiveBinding.Rd
index 9a50c69..5530eaa 100644
--- a/man/makeReactiveBinding.Rd
+++ b/man/makeReactiveBinding.Rd
@@ -30,4 +30,3 @@ observe(print(b()))
 a <- 20
 }
 }
-
diff --git a/man/markOutputAttrs.Rd b/man/markOutputAttrs.Rd
new file mode 100644
index 0000000..f0e468e
--- /dev/null
+++ b/man/markOutputAttrs.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/shinywrappers.R
+\name{markOutputAttrs}
+\alias{markOutputAttrs}
+\title{Mark a render function with attributes that will be used by the output}
+\usage{
+markOutputAttrs(renderFunc, snapshotExclude = NULL,
+  snapshotPreprocess = NULL)
+}
+\arguments{
+\item{renderFunc}{A function that is suitable for assigning to a Shiny output
+slot.}
+
+\item{snapshotExclude}{If TRUE, exclude the output from test snapshots.}
+
+\item{snapshotPreprocess}{A function for preprocessing the value before
+taking a test snapshot.}
+}
+\description{
+Mark a render function with attributes that will be used by the output
+}
+\keyword{internal}
diff --git a/man/markRenderFunction.Rd b/man/markRenderFunction.Rd
index 9f30fa4..28a841c 100644
--- a/man/markRenderFunction.Rd
+++ b/man/markRenderFunction.Rd
@@ -30,4 +30,3 @@ Shiny regarding what UI function is most commonly used with this type of
 render function. This can be used in R Markdown documents to create complete
 output widgets out of just the render function.
 }
-
diff --git a/man/maskReactiveContext.Rd b/man/maskReactiveContext.Rd
index 7615a1f..4905bb9 100644
--- a/man/maskReactiveContext.Rd
+++ b/man/maskReactiveContext.Rd
@@ -21,4 +21,3 @@ default, an error).
 \seealso{
 \code{\link{isolate}}
 }
-
diff --git a/man/modalButton.Rd b/man/modalButton.Rd
index 4bc3f1b..63daa30 100644
--- a/man/modalButton.Rd
+++ b/man/modalButton.Rd
@@ -18,4 +18,3 @@ When clicked, a \code{modalButton} will dismiss the modal dialog.
 \seealso{
 \code{\link{modalDialog}} for examples.
 }
-
diff --git a/man/modalDialog.Rd b/man/modalDialog.Rd
index 5bbfc5d..2eba591 100644
--- a/man/modalDialog.Rd
+++ b/man/modalDialog.Rd
@@ -129,4 +129,3 @@ shinyApp(
 )
 }
 }
-
diff --git a/man/navbarPage.Rd b/man/navbarPage.Rd
index d293500..dcedabb 100644
--- a/man/navbarPage.Rd
+++ b/man/navbarPage.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/bootstrap.R
 \name{navbarPage}
-\alias{navbarMenu}
 \alias{navbarPage}
+\alias{navbarMenu}
 \title{Create a page with a top level navigation bar}
 \usage{
 navbarPage(title, ..., id = NULL, selected = NULL,
@@ -10,7 +10,7 @@ navbarPage(title, ..., id = NULL, selected = NULL,
   footer = NULL, inverse = FALSE, collapsible = FALSE, collapsable,
   fluid = TRUE, responsive = NULL, theme = NULL, windowTitle = title)
 
-navbarMenu(title, ..., icon = NULL)
+navbarMenu(title, ..., menuName = title, icon = NULL)
 }
 \arguments{
 \item{title}{The title to display in the navbar}
@@ -65,6 +65,10 @@ www directory). For example, to use the theme located at
 \item{windowTitle}{The title that should be displayed by the browser window.
 Useful if \code{title} is not a string.}
 
+\item{menuName}{A name that identifies this \code{navbarMenu}. This
+is needed if you want to insert/remove or show/hide an entire
+\code{navbarMenu}.}
+
 \item{icon}{Optional icon to appear on a \code{navbarMenu} tab.}
 }
 \value{
@@ -98,6 +102,6 @@ navbarPage("App Title",
 }
 \seealso{
 \code{\link{tabPanel}}, \code{\link{tabsetPanel}},
-  \code{\link{updateNavbarPage}}
+  \code{\link{updateNavbarPage}}, \code{\link{insertTab}},
+  \code{\link{showTab}}
 }
-
diff --git a/man/navlistPanel.Rd b/man/navlistPanel.Rd
index a00b4bc..f1f0c18 100644
--- a/man/navlistPanel.Rd
+++ b/man/navlistPanel.Rd
@@ -53,6 +53,6 @@ fluidPage(
 )
 }
 \seealso{
-\code{\link{tabPanel}}, \code{\link{updateNavlistPanel}}
+\code{\link{tabPanel}}, \code{\link{updateNavlistPanel}},
+   \code{\link{insertTab}}, \code{\link{showTab}}
 }
-
diff --git a/man/nearPoints.Rd b/man/nearPoints.Rd
index 7ce65ff..970377a 100644
--- a/man/nearPoints.Rd
+++ b/man/nearPoints.Rd
@@ -87,4 +87,3 @@ nearPoints(mtcars, input$plot_click, threshold = 10, maxpoints = 1)
 \seealso{
 \code{\link{plotOutput}} for more examples.
 }
-
diff --git a/man/numericInput.Rd b/man/numericInput.Rd
index 4c506db..52ec6c3 100644
--- a/man/numericInput.Rd
+++ b/man/numericInput.Rd
@@ -46,7 +46,7 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{updateNumericInput}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
@@ -55,4 +55,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{submitButton}}, \code{\link{textAreaInput}},
   \code{\link{textInput}}
 }
-
diff --git a/man/observe.Rd b/man/observe.Rd
index a659b99..12b0c2a 100644
--- a/man/observe.Rd
+++ b/man/observe.Rd
@@ -116,4 +116,3 @@ obsD <- observe(expr_q, quoted = TRUE)
 # are at the console, you can force a flush with flushReact()
 shiny:::flushReact()
 }
-
diff --git a/man/observeEvent.Rd b/man/observeEvent.Rd
index d95aa0d..0425ed2 100644
--- a/man/observeEvent.Rd
+++ b/man/observeEvent.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/reactives.R
 \name{observeEvent}
-\alias{eventReactive}
 \alias{observeEvent}
+\alias{eventReactive}
 \title{Event handler}
 \usage{
 observeEvent(eventExpr, handlerExpr, event.env = parent.frame(),
@@ -120,7 +120,7 @@ updates in response to an event. This is just like a normal
 invalidations that come from its reactive dependencies; it only invalidates
 in response to the given event.
 }
-\section{\code{ignoreNULL} and \code{ignoreInit}}{
+\section{ignoreNULL and ignoreInit}{
 
 
 Both \code{observeEvent} and \code{eventReactive} take an \code{ignoreNULL}
@@ -173,6 +173,7 @@ these:
   }
 }
 }
+
 \examples{
 ## Only run this example in interactive R sessions
 if (interactive()) {
@@ -238,4 +239,3 @@ if (interactive()) {
 \seealso{
 \code{\link{actionButton}}
 }
-
diff --git a/man/onBookmark.Rd b/man/onBookmark.Rd
index 3db81e8..ae2366c 100644
--- a/man/onBookmark.Rd
+++ b/man/onBookmark.Rd
@@ -76,6 +76,7 @@ then Shiny will by default display a modal dialog with the bookmark URL.
   callback functions registered for the module will only be able to see the
   module's inputs and values.
 }
+
 \examples{
 ## Only run these examples in interactive sessions
 if (interactive()) {
@@ -206,4 +207,3 @@ shinyApp(ui, server)
 \seealso{
 enableBookmarking for general information on bookmarking.
 }
-
diff --git a/man/onFlush.Rd b/man/onFlush.Rd
index 964149a..c037f3a 100644
--- a/man/onFlush.Rd
+++ b/man/onFlush.Rd
@@ -34,4 +34,7 @@ These functions should be called within the application's server function.
 All of these functions return a function which can be called with no
 arguments to cancel the registration.
 }
-
+\seealso{
+\code{\link{onStop}()} for registering callbacks that will be
+  invoked when the application exits, or when a session ends.
+}
diff --git a/man/onStop.Rd b/man/onStop.Rd
new file mode 100644
index 0000000..1de54c7
--- /dev/null
+++ b/man/onStop.Rd
@@ -0,0 +1,81 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/shiny.R
+\name{onStop}
+\alias{onStop}
+\title{Run code after an application or session ends}
+\usage{
+onStop(fun, session = getDefaultReactiveDomain())
+}
+\arguments{
+\item{fun}{A function that will be called after the app has finished running.}
+
+\item{session}{A scope for when the callback will run. If \code{onStop} is
+called from within the server function, this will default to the current
+session, and the callback will be invoked when the current session ends. If
+\code{onStop} is called outside a server function, then the callback will
+be invoked with the application exits.}
+}
+\value{
+A function which, if invoked, will cancel the callback.
+}
+\description{
+This function registers callback functions that are invoked when the
+application exits (when \code{\link{runApp}} exits), or after each user
+session ends (when a client disconnects).
+}
+\examples{
+## Only run this example in interactive R sessions
+if (interactive()) {
+  # Open this application in multiple browsers, then close the browsers.
+  shinyApp(
+    ui = basicPage("onStop demo"),
+
+    server = function(input, output, session) {
+      onStop(function() cat("Session stopped\\n"))
+    },
+
+    onStart = function() {
+      cat("Doing application setup\\n")
+
+      onStop(function() {
+        cat("Doing application cleanup\\n")
+      })
+    }
+  )
+}
+# In the example above, onStop() is called inside of onStart(). This is
+# the pattern that should be used when creating a shinyApp() object from
+# a function, or at the console. If instead you are writing an app.R which
+# will be invoked with runApp(), you can do it that way, or put the onStop()
+# before the shinyApp() call, as shown below.
+
+\dontrun{
+# ==== app.R ====
+cat("Doing application setup\\n")
+onStop(function() {
+  cat("Doing application cleanup\\n")
+})
+
+shinyApp(
+  ui = basicPage("onStop demo"),
+
+  server = function(input, output, session) {
+    onStop(function() cat("Session stopped\\n"))
+  }
+)
+# ==== end app.R ====
+
+
+# Similarly, if you have a global.R, you can call onStop() from there.
+# ==== global.R ====
+cat("Doing application setup\\n")
+onStop(function() {
+  cat("Doing application cleanup\\n")
+})
+# ==== end global.R ====
+}
+}
+\seealso{
+\code{\link{onSessionEnded}()} for the same functionality, but at
+  the session level only.
+}
diff --git a/man/outputOptions.Rd b/man/outputOptions.Rd
index 9d504e4..0cb2ee6 100644
--- a/man/outputOptions.Rd
+++ b/man/outputOptions.Rd
@@ -40,4 +40,3 @@ outputOptions(output, "myplot")
 }
 
 }
-
diff --git a/man/pageWithSidebar.Rd b/man/pageWithSidebar.Rd
index cba5a99..f311257 100644
--- a/man/pageWithSidebar.Rd
+++ b/man/pageWithSidebar.Rd
@@ -46,4 +46,3 @@ pageWithSidebar(
   )
 )
 }
-
diff --git a/man/parseQueryString.Rd b/man/parseQueryString.Rd
index 626188c..5b9a516 100644
--- a/man/parseQueryString.Rd
+++ b/man/parseQueryString.Rd
@@ -44,4 +44,3 @@ function(input, output, session) {
 }
 
 }
-
diff --git a/man/passwordInput.Rd b/man/passwordInput.Rd
index 1009ff1..2b6e2aa 100644
--- a/man/passwordInput.Rd
+++ b/man/passwordInput.Rd
@@ -48,7 +48,7 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{updateTextInput}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
@@ -57,4 +57,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{submitButton}}, \code{\link{textAreaInput}},
   \code{\link{textInput}}
 }
-
diff --git a/man/plotOutput.Rd b/man/plotOutput.Rd
index 5b4ff79..5c74600 100644
--- a/man/plotOutput.Rd
+++ b/man/plotOutput.Rd
@@ -1,6 +1,7 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/bootstrap.R
 \name{plotOutput}
+\alias{plotOutput}
 \alias{imageOutput}
 \alias{plotOutput}
 \title{Create an plot or image output element}
@@ -111,6 +112,7 @@ The arguments \code{clickId} and \code{hoverId} only work for R base
   \code{print(p)}, then the coordinates for interactive graphics will not be
   properly scaled to the data space.
 }
+
 \examples{
 # Only run these examples in interactive R sessions
 if (interactive()) {
@@ -274,4 +276,3 @@ shinyApp(
 For the corresponding server-side functions, see
   \code{\link{renderPlot}} and  \code{\link{renderImage}}.
 }
-
diff --git a/man/plotPNG.Rd b/man/plotPNG.Rd
index a300c3f..115c6f3 100644
--- a/man/plotPNG.Rd
+++ b/man/plotPNG.Rd
@@ -41,4 +41,3 @@ 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)}.
 }
-
diff --git a/man/radioButtons.Rd b/man/radioButtons.Rd
index bf07086..64e3f13 100644
--- a/man/radioButtons.Rd
+++ b/man/radioButtons.Rd
@@ -4,8 +4,8 @@
 \alias{radioButtons}
 \title{Create radio buttons}
 \usage{
-radioButtons(inputId, label, choices, selected = NULL, inline = FALSE,
-  width = NULL)
+radioButtons(inputId, label, choices = NULL, selected = NULL,
+  inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL)
 }
 \arguments{
 \item{inputId}{The \code{input} slot that will be used to access the value.}
@@ -13,15 +13,28 @@ radioButtons(inputId, label, choices, selected = NULL, inline = FALSE,
 \item{label}{Display label for the control, or \code{NULL} for no label.}
 
 \item{choices}{List of values to select from (if elements of the list are
-named then that name rather than the value is displayed to the user)}
+named then that name rather than the value is displayed to the user). If
+this argument is provided, then \code{choiceNames} and \code{choiceValues}
+must not be provided, and vice-versa. The values should be strings; other
+types (such as logicals and numbers) will be coerced to strings.}
 
-\item{selected}{The initially selected value (if not specified then
-defaults to the first value)}
+\item{selected}{The initially selected value (if not specified then defaults
+to the first value)}
 
 \item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)}
 
 \item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
 see \code{\link{validateCssUnit}}.}
+
+\item{choiceNames, choiceValues}{List of names and values, respectively, that
+are displayed to the user in the app and correspond to the each choice (for
+this reason, \code{choiceNames} and \code{choiceValues} must have the same
+length). If either of these arguments is provided, then the other
+\emph{must} be provided and \code{choices} \emph{must not} be provided. The
+advantage of using both of these over a named list for \code{choices} is
+that \code{choiceNames} allows any type of UI object to be passed through
+(tag objects, icons, HTML code, ...), instead of just simple text. See
+Examples.}
 }
 \value{
 A set of radio buttons that can be added to a UI definition.
@@ -31,11 +44,10 @@ Create a set of radio buttons used to select an item from a list.
 }
 \details{
 If you need to represent a "None selected" state, it's possible to default
-the radio buttons to have no options selected by using
-\code{selected = character(0)}. However, this is not recommended, as it gives
-the user no way to return to that state once they've made a selection.
-Instead, consider having the first of your choices be \code{c("None selected"
-= "")}.
+the radio buttons to have no options selected by using \code{selected =
+character(0)}. However, this is not recommended, as it gives the user no way
+to return to that state once they've made a selection. Instead, consider
+having the first of your choices be \code{c("None selected" = "")}.
 }
 \examples{
 ## Only run examples in interactive R sessions
@@ -64,12 +76,33 @@ server <- function(input, output) {
 }
 
 shinyApp(ui, server)
+
+ui <- fluidPage(
+  radioButtons("rb", "Choose one:",
+               choiceNames = list(
+                 icon("calendar"),
+                 HTML("<p style='color:red;'>Red Text</p>"),
+                 "Normal text"
+               ),
+               choiceValues = list(
+                 "icon", "html", "text"
+               )),
+  textOutput("txt")
+)
+
+server <- function(input, output) {
+  output$txt <- renderText({
+    paste("You chose", input$rb)
+  })
+}
+
+shinyApp(ui, server)
 }
 }
 \seealso{
 \code{\link{updateRadioButtons}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
@@ -78,4 +111,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{submitButton}}, \code{\link{textAreaInput}},
   \code{\link{textInput}}
 }
-
diff --git a/man/reactive.Rd b/man/reactive.Rd
index 889e6fa..62940ce 100644
--- a/man/reactive.Rd
+++ b/man/reactive.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/reactives.R
 \name{reactive}
-\alias{is.reactive}
 \alias{reactive}
+\alias{is.reactive}
 \title{Create a reactive expression}
 \usage{
 reactive(x, env = parent.frame(), quoted = FALSE, label = NULL,
@@ -67,4 +67,3 @@ isolate(reactiveB())
 isolate(reactiveC())
 isolate(reactiveD())
 }
-
diff --git a/man/reactiveFileReader.Rd b/man/reactiveFileReader.Rd
index 1ca102c..a5f46b4 100644
--- a/man/reactiveFileReader.Rd
+++ b/man/reactiveFileReader.Rd
@@ -58,7 +58,7 @@ function(input, output, session) {
 # 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)
+fileData <- reactiveFileReader(1000, NULL, 'data.csv', read.csv)
 function(input, output, session) {
   output$data <- renderTable({
     fileData()
@@ -69,4 +69,3 @@ function(input, output, session) {
 \seealso{
 \code{\link{reactivePoll}}
 }
-
diff --git a/man/reactivePlot.Rd b/man/reactivePlot.Rd
index 7279288..2fc41df 100644
--- a/man/reactivePlot.Rd
+++ b/man/reactivePlot.Rd
@@ -18,4 +18,3 @@ reactivePlot(func, width = "auto", height = "auto", ...)
 \description{
 See \code{\link{renderPlot}}.
 }
-
diff --git a/man/reactivePoll.Rd b/man/reactivePoll.Rd
index f13e3c4..daa2f33 100644
--- a/man/reactivePoll.Rd
+++ b/man/reactivePoll.Rd
@@ -57,9 +57,22 @@ will be executed in a reactive context; therefore, they may read reactive
 values and reactive expressions.
 }
 \examples{
-# Assume the existence of readTimestamp and readValue functions
 function(input, output, session) {
-  data <- reactivePoll(1000, session, readTimestamp, readValue)
+
+  data <- reactivePoll(1000, session,
+    # This function returns the time that log_file was last modified
+    checkFunc = function() {
+      if (file.exists(log_file))
+        file.info(log_file)$mtime[1]
+      else
+        ""
+    },
+    # This function returns the content of log_file
+    valueFunc = function() {
+      read.csv(log_file)
+    }
+  )
+
   output$dataTable <- renderTable({
     data()
   })
@@ -68,4 +81,3 @@ function(input, output, session) {
 \seealso{
 \code{\link{reactiveFileReader}}
 }
-
diff --git a/man/reactivePrint.Rd b/man/reactivePrint.Rd
index b2c5eca..afbdb87 100644
--- a/man/reactivePrint.Rd
+++ b/man/reactivePrint.Rd
@@ -12,4 +12,3 @@ reactivePrint(func)
 \description{
 See \code{\link{renderPrint}}.
 }
-
diff --git a/man/reactiveTable.Rd b/man/reactiveTable.Rd
index fa73f2c..fe87689 100644
--- a/man/reactiveTable.Rd
+++ b/man/reactiveTable.Rd
@@ -14,4 +14,3 @@ reactiveTable(func, ...)
 \description{
 See \code{\link{renderTable}}.
 }
-
diff --git a/man/reactiveText.Rd b/man/reactiveText.Rd
index 203bc37..a832a82 100644
--- a/man/reactiveText.Rd
+++ b/man/reactiveText.Rd
@@ -12,4 +12,3 @@ reactiveText(func)
 \description{
 See \code{\link{renderText}}.
 }
-
diff --git a/man/reactiveTimer.Rd b/man/reactiveTimer.Rd
index ec47226..27b6a01 100644
--- a/man/reactiveTimer.Rd
+++ b/man/reactiveTimer.Rd
@@ -73,4 +73,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{invalidateLater}}
 }
-
diff --git a/man/reactiveUI.Rd b/man/reactiveUI.Rd
index d3e8dc9..e481506 100644
--- a/man/reactiveUI.Rd
+++ b/man/reactiveUI.Rd
@@ -12,4 +12,3 @@ reactiveUI(func)
 \description{
 See \code{\link{renderUI}}.
 }
-
diff --git a/man/reactiveVal.Rd b/man/reactiveVal.Rd
new file mode 100644
index 0000000..cce5cde
--- /dev/null
+++ b/man/reactiveVal.Rd
@@ -0,0 +1,83 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/reactives.R
+\name{reactiveVal}
+\alias{reactiveVal}
+\title{Create a (single) reactive value}
+\usage{
+reactiveVal(value = NULL, label = NULL)
+}
+\arguments{
+\item{value}{An optional initial value.}
+
+\item{label}{An optional label, for debugging purposes (see
+\code{\link{showReactLog}}). If missing, a label will be automatically
+created.}
+}
+\value{
+A function. Call the function with no arguments to (reactively) read
+  the value; call the function with a single argument to set the value.
+}
+\description{
+The \code{reactiveVal} function is used to construct a "reactive value"
+object. This is an object used for reading and writing a value, like a
+variable, but with special capabilities for reactive programming. When you
+read the value out of a reactiveVal object, the calling reactive expression
+takes a dependency, and when you change the value, it notifies any reactives
+that previously depended on that value.
+}
+\details{
+\code{reactiveVal} is very similar to \code{\link{reactiveValues}}, except
+that the former is for a single reactive value (like a variable), whereas the
+latter lets you conveniently use multiple reactive values by name (like a
+named list of variables). For a one-off reactive value, it's more natural to
+use \code{reactiveVal}. See the Examples section for an illustration.
+}
+\examples{
+
+\dontrun{
+
+# Create the object by calling reactiveVal
+r <- reactiveVal()
+
+# Set the value by calling with an argument
+r(10)
+
+# Read the value by calling without arguments
+r()
+
+}
+
+## Only run examples in interactive R sessions
+if (interactive()) {
+
+ui <- fluidPage(
+  actionButton("minus", "-1"),
+  actionButton("plus", "+1"),
+  br(),
+  textOutput("value")
+)
+
+# The comments below show the equivalent logic using reactiveValues()
+server <- function(input, output, session) {
+  value <- reactiveVal(0)       # rv <- reactiveValues(value = 0)
+
+  observeEvent(input$minus, {
+    newValue <- value() - 1     # newValue <- rv$value - 1
+    value(newValue)             # rv$value <- newValue
+  })
+
+  observeEvent(input$plus, {
+    newValue <- value() + 1     # newValue <- rv$value + 1
+    value(newValue)             # rv$value <- newValue
+  })
+
+  output$value <- renderText({
+    value()                     # rv$value
+  })
+}
+
+shinyApp(ui, server)
+
+}
+
+}
diff --git a/man/reactiveValues.Rd b/man/reactiveValues.Rd
index badf3d7..dcae1aa 100644
--- a/man/reactiveValues.Rd
+++ b/man/reactiveValues.Rd
@@ -45,4 +45,3 @@ isolate(values$a)
 \seealso{
 \code{\link{isolate}} and \code{\link{is.reactivevalues}}.
 }
-
diff --git a/man/reactiveValuesToList.Rd b/man/reactiveValuesToList.Rd
index 5bef437..298a06a 100644
--- a/man/reactiveValuesToList.Rd
+++ b/man/reactiveValuesToList.Rd
@@ -29,4 +29,3 @@ reactiveValuesToList(values)
 # at the console)
 isolate(reactiveValuesToList(values))
 }
-
diff --git a/man/registerInputHandler.Rd b/man/registerInputHandler.Rd
index 732ea43..5ba1e1d 100644
--- a/man/registerInputHandler.Rd
+++ b/man/registerInputHandler.Rd
@@ -62,4 +62,3 @@ getType: function(el) {
 \seealso{
 \code{\link{removeInputHandler}}
 }
-
diff --git a/man/removeInputHandler.Rd b/man/removeInputHandler.Rd
index 8637296..add1a44 100644
--- a/man/removeInputHandler.Rd
+++ b/man/removeInputHandler.Rd
@@ -20,4 +20,3 @@ for data of this type, the default jsonlite serialization will be used.
 \seealso{
 \code{\link{registerInputHandler}}
 }
-
diff --git a/man/removeUI.Rd b/man/removeUI.Rd
index 6dd0246..288dfbe 100644
--- a/man/removeUI.Rd
+++ b/man/removeUI.Rd
@@ -64,4 +64,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{insertUI}}
 }
-
diff --git a/man/renderDataTable.Rd b/man/renderDataTable.Rd
index 355c68f..ac5b242 100644
--- a/man/renderDataTable.Rd
+++ b/man/renderDataTable.Rd
@@ -87,4 +87,3 @@ if (interactive()) {
 \references{
 \url{http://datatables.net}
 }
-
diff --git a/man/renderImage.Rd b/man/renderImage.Rd
index 170217f..4a542c8 100644
--- a/man/renderImage.Rd
+++ b/man/renderImage.Rd
@@ -115,4 +115,3 @@ shinyApp(ui, server)
 For more details on how the images are generated, and how to control
   the output, see \code{\link{plotPNG}}.
 }
-
diff --git a/man/renderPlot.Rd b/man/renderPlot.Rd
index 0c49444..593246c 100644
--- a/man/renderPlot.Rd
+++ b/man/renderPlot.Rd
@@ -60,9 +60,9 @@ the CSS class name \code{shiny-plot-output}.
 
   See \code{\link{plotOutput}} for more information about interactive plots.
 }
+
 \seealso{
 For the corresponding client-side output function, and example
   usage, see \code{\link{plotOutput}}. For more details on how the plots are
   generated, and how to control the output, see \code{\link{plotPNG}}.
 }
-
diff --git a/man/renderPrint.Rd b/man/renderPrint.Rd
index aa29727..36f15e7 100644
--- a/man/renderPrint.Rd
+++ b/man/renderPrint.Rd
@@ -108,4 +108,3 @@ vecFun()
 \code{\link{renderText}} for displaying the value returned from a
   function, instead of the printed output.
 }
-
diff --git a/man/renderTable.Rd b/man/renderTable.Rd
index 731502e..f5b066e 100644
--- a/man/renderTable.Rd
+++ b/man/renderTable.Rd
@@ -70,4 +70,3 @@ slot.
 The corresponding HTML output tag should be \code{div} and have the CSS
 class name \code{shiny-html-output}.
 }
-
diff --git a/man/renderText.Rd b/man/renderText.Rd
index b70e32b..b854a02 100644
--- a/man/renderText.Rd
+++ b/man/renderText.Rd
@@ -101,4 +101,3 @@ vecFun()
 \code{\link{renderPrint}} for capturing the print output of a
   function, rather than the returned text value.
 }
-
diff --git a/man/renderUI.Rd b/man/renderUI.Rd
index 14e670b..e3e887a 100644
--- a/man/renderUI.Rd
+++ b/man/renderUI.Rd
@@ -50,4 +50,3 @@ shinyApp(ui, server)
 \seealso{
 conditionalPanel
 }
-
diff --git a/man/repeatable.Rd b/man/repeatable.Rd
index 5e49cf9..5cd0246 100644
--- a/man/repeatable.Rd
+++ b/man/repeatable.Rd
@@ -32,4 +32,3 @@ rnormA(3)  # [1]  1.8285879 -0.7468041 -0.4639111
 rnormA(5)  # [1]  1.8285879 -0.7468041 -0.4639111 -1.6510126 -1.4686924
 rnormB(5)  # [1] -0.7946034  0.2568374 -0.6567597  1.2451387 -0.8375699
 }
-
diff --git a/man/req.Rd b/man/req.Rd
index 3fdd5d6..6215302 100644
--- a/man/req.Rd
+++ b/man/req.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/utils.R
 \name{req}
-\alias{isTruthy}
 \alias{req}
+\alias{isTruthy}
 \title{Check for required values}
 \usage{
 req(..., cancelOutput = FALSE)
@@ -140,4 +140,3 @@ if (interactive()) {
   shinyApp(ui, server)
 }
 }
-
diff --git a/man/restoreInput.Rd b/man/restoreInput.Rd
index 8ceb795..3ff1e0a 100644
--- a/man/restoreInput.Rd
+++ b/man/restoreInput.Rd
@@ -15,4 +15,3 @@ restoreInput(id, default)
 This restores an input value from the current restore context. It should be
 called early on inside of input functions (like \code{\link{textInput}}).
 }
-
diff --git a/man/runApp.Rd b/man/runApp.Rd
index d4d620d..a86d2fe 100644
--- a/man/runApp.Rd
+++ b/man/runApp.Rd
@@ -101,4 +101,3 @@ if (interactive()) {
   runApp(app)
 }
 }
-
diff --git a/man/runExample.Rd b/man/runExample.Rd
index e92968a..55ac15f 100644
--- a/man/runExample.Rd
+++ b/man/runExample.Rd
@@ -43,4 +43,3 @@ if (interactive()) {
   system.file("examples", package="shiny")
 }
 }
-
diff --git a/man/runGadget.Rd b/man/runGadget.Rd
index a3bfcf7..30aad3c 100644
--- a/man/runGadget.Rd
+++ b/man/runGadget.Rd
@@ -49,4 +49,3 @@ runGadget(ui, server)
 runGadget(shinyApp(ui, server))
 }
 }
-
diff --git a/man/runUrl.Rd b/man/runUrl.Rd
index 05d5860..3902bda 100644
--- a/man/runUrl.Rd
+++ b/man/runUrl.Rd
@@ -1,9 +1,9 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/run-url.R
 \name{runUrl}
+\alias{runUrl}
 \alias{runGist}
 \alias{runGitHub}
-\alias{runUrl}
 \title{Run a Shiny application from a URL}
 \usage{
 runUrl(url, filetype = NULL, subdir = NULL, destdir = NULL, ...)
@@ -80,4 +80,3 @@ if (interactive()) {
   runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
 }
 }
-
diff --git a/man/safeError.Rd b/man/safeError.Rd
index d55fc89..50a4b8b 100644
--- a/man/safeError.Rd
+++ b/man/safeError.Rd
@@ -79,4 +79,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{shiny-options}}
 }
-
diff --git a/man/selectInput.Rd b/man/selectInput.Rd
index eaafef8..f9a509b 100644
--- a/man/selectInput.Rd
+++ b/man/selectInput.Rd
@@ -56,7 +56,7 @@ from a list of values.
 \details{
 By default, \code{selectInput()} and \code{selectizeInput()} use the
 JavaScript library \pkg{selectize.js}
-(\url{https://github.com/brianreavis/selectize.js}) to instead of the basic
+(\url{https://github.com/selectize/selectize.js}) to instead of the basic
 select input element. To use the standard HTML select input element, use
 \code{selectInput()} with \code{selectize=FALSE}.
 
@@ -115,7 +115,7 @@ shinyApp(
 \seealso{
 \code{\link{updateSelectInput}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
@@ -124,4 +124,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{submitButton}}, \code{\link{textAreaInput}},
   \code{\link{textInput}}
 }
-
diff --git a/man/serverInfo.Rd b/man/serverInfo.Rd
index e786b55..190fdd7 100644
--- a/man/serverInfo.Rd
+++ b/man/serverInfo.Rd
@@ -19,4 +19,3 @@ just returns \code{list(shinyServer = FALSE)}.
 This function will only return meaningful data when using Shiny Server
 version 1.2.2 or later.
 }
-
diff --git a/man/session.Rd b/man/session.Rd
index ff5c508..fe90250 100644
--- a/man/session.Rd
+++ b/man/session.Rd
@@ -27,12 +27,13 @@
     }
     \item{\code{singletons} - for internal use}
     \item{\code{url_protocol}, \code{url_hostname}, \code{url_port},
-      \code{url_pathname}, \code{url_search}, and \code{url_hash_initial}
-      can be used to get the components of the URL that was requested by the
-      browser to load the Shiny app page. These values are from the
-      browser's perspective, so neither HTTP proxies nor Shiny Server will
-      affect these values. The \code{url_search} value may be used with
-      \code{\link{parseQueryString}} to access query string parameters.
+      \code{url_pathname}, \code{url_search}, \code{url_hash_initial}
+      and \code{url_hash} can be used to get the components of the URL
+      that was requested by the browser to load the Shiny app page.
+      These values are from the browser's perspective, so neither HTTP
+      proxies nor Shiny Server will affect these values. The
+      \code{url_search} value may be used with \code{\link{parseQueryString}}
+      to access query string parameters.
     }
   }
   \code{clientData} also contains information about each output.
@@ -181,4 +182,3 @@ relating to the session. The following list describes the items available
 in the environment; they can be accessed using the \code{$} operator (for
 example, \code{session$clientData$url_search}).
 }
-
diff --git a/man/setBookmarkExclude.Rd b/man/setBookmarkExclude.Rd
index b819882..e7aeed9 100644
--- a/man/setBookmarkExclude.Rd
+++ b/man/setBookmarkExclude.Rd
@@ -26,4 +26,3 @@ application.
 \seealso{
 \code{\link{enableBookmarking}} for examples.
 }
-
diff --git a/man/setSerializer.Rd b/man/setSerializer.Rd
new file mode 100644
index 0000000..ef10eba
--- /dev/null
+++ b/man/setSerializer.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/serializers.R
+\name{setSerializer}
+\alias{setSerializer}
+\title{Add a function for serializing an input before bookmarking application state}
+\usage{
+setSerializer(inputId, fun, session = getDefaultReactiveDomain())
+}
+\arguments{
+\item{inputId}{Name of the input value.}
+
+\item{fun}{A function that takes the input value and returns a modified
+value. The returned value will be used for the test snapshot.}
+
+\item{session}{A Shiny session object.}
+}
+\description{
+Add a function for serializing an input before bookmarking application state
+}
+\keyword{internal}
diff --git a/man/shiny-options.Rd b/man/shiny-options.Rd
index e0c93d5..54f2c06 100644
--- a/man/shiny-options.Rd
+++ b/man/shiny-options.Rd
@@ -86,4 +86,3 @@ The default polling interval is 500 milliseconds. You can change this
   }
 }
 }
-
diff --git a/man/shiny-package.Rd b/man/shiny-package.Rd
index 2a49a42..7befe80 100644
--- a/man/shiny-package.Rd
+++ b/man/shiny-package.Rd
@@ -2,13 +2,13 @@
 % Please edit documentation in R/shiny.R
 \docType{package}
 \name{shiny-package}
-\alias{shiny}
 \alias{shiny-package}
+\alias{shiny}
 \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
+prebuilt widgets make it possible to build beautiful, responsive, and
 powerful applications with minimal effort.
 }
 \details{
@@ -19,4 +19,3 @@ includes extensive annotated examples.
 \seealso{
 \link{shiny-options} for documentation about global options.
 }
-
diff --git a/man/shinyApp.Rd b/man/shinyApp.Rd
index 122d990..cdbd29d 100644
--- a/man/shinyApp.Rd
+++ b/man/shinyApp.Rd
@@ -1,16 +1,16 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/app.R
 \name{shinyApp}
+\alias{shinyApp}
+\alias{shinyAppDir}
+\alias{shinyAppFile}
 \alias{as.shiny.appobj}
-\alias{as.shiny.appobj.character}
-\alias{as.shiny.appobj.list}
 \alias{as.shiny.appobj.shiny.appobj}
-\alias{as.tags.shiny.appobj}
+\alias{as.shiny.appobj.list}
+\alias{as.shiny.appobj.character}
 \alias{is.shiny.appobj}
 \alias{print.shiny.appobj}
-\alias{shinyApp}
-\alias{shinyAppDir}
-\alias{shinyAppFile}
+\alias{as.tags.shiny.appobj}
 \title{Create a Shiny app object}
 \usage{
 shinyApp(ui = NULL, server = NULL, onStart = NULL, options = list(),
@@ -121,4 +121,3 @@ if (interactive()) {
   runApp(app)
 }
 }
-
diff --git a/man/shinyDeprecated.Rd b/man/shinyDeprecated.Rd
index 6c624a0..494b8e2 100644
--- a/man/shinyDeprecated.Rd
+++ b/man/shinyDeprecated.Rd
@@ -20,4 +20,3 @@ shinyDeprecated(new = NULL, msg = NULL,
 To disable these messages, use \code{options(shiny.deprecation.messages=FALSE)}.
 }
 \keyword{internal}
-
diff --git a/man/shinyOptions.Rd b/man/shinyOptions.Rd
index 4a9f5b3..5bcc234 100644
--- a/man/shinyOptions.Rd
+++ b/man/shinyOptions.Rd
@@ -36,4 +36,3 @@ shinyOptions(myOption = 10)
 getShinyOption("myOption")
 }
 }
-
diff --git a/man/shinyServer.Rd b/man/shinyServer.Rd
index 5bcf4df..fe3023b 100644
--- a/man/shinyServer.Rd
+++ b/man/shinyServer.Rd
@@ -54,4 +54,3 @@ function(input, output, session) {
 }
 }
 }
-
diff --git a/man/shinyUI.Rd b/man/shinyUI.Rd
index 05d3b5e..79f8249 100644
--- a/man/shinyUI.Rd
+++ b/man/shinyUI.Rd
@@ -19,4 +19,3 @@ ensure that the last expression to be returned from ui.R is a user interface.
 This function is kept for backwards compatibility with older applications. It
 returns the value that is passed to it.
 }
-
diff --git a/man/showBookmarkUrlModal.Rd b/man/showBookmarkUrlModal.Rd
index 530c74a..28e11e2 100644
--- a/man/showBookmarkUrlModal.Rd
+++ b/man/showBookmarkUrlModal.Rd
@@ -16,4 +16,3 @@ callback was set. It displays a modal dialog with the bookmark URL, along
 with a subtitle that is appropriate for the type of bookmarking used ("url"
 or "server").
 }
-
diff --git a/man/showModal.Rd b/man/showModal.Rd
index 0332036..ba80460 100644
--- a/man/showModal.Rd
+++ b/man/showModal.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/modal.R
 \name{showModal}
-\alias{removeModal}
 \alias{showModal}
+\alias{removeModal}
 \title{Show or remove a modal dialog}
 \usage{
 showModal(ui, session = getDefaultReactiveDomain())
@@ -22,4 +22,3 @@ typically used with \code{\link{modalDialog}}.
 \seealso{
 \code{\link{modalDialog}} for examples.
 }
-
diff --git a/man/showNotification.Rd b/man/showNotification.Rd
index ab1d3d2..af906b9 100644
--- a/man/showNotification.Rd
+++ b/man/showNotification.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/notifications.R
 \name{showNotification}
-\alias{removeNotification}
 \alias{showNotification}
+\alias{removeNotification}
 \title{Show or remove a notification}
 \usage{
 showNotification(ui, action = NULL, duration = 5, closeButton = TRUE,
@@ -88,4 +88,3 @@ shinyApp(
 )
 }
 }
-
diff --git a/man/showReactLog.Rd b/man/showReactLog.Rd
index 6f08794..a045e78 100644
--- a/man/showReactLog.Rd
+++ b/man/showReactLog.Rd
@@ -43,4 +43,3 @@ For security and performance reasons, do not enable
 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.
 }
-
diff --git a/man/showTab.Rd b/man/showTab.Rd
new file mode 100644
index 0000000..893810b
--- /dev/null
+++ b/man/showTab.Rd
@@ -0,0 +1,85 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/insert-tab.R
+\name{showTab}
+\alias{showTab}
+\alias{hideTab}
+\title{Dynamically hide/show a tabPanel}
+\usage{
+showTab(inputId, target, select = FALSE,
+  session = getDefaultReactiveDomain())
+
+hideTab(inputId, target, session = getDefaultReactiveDomain())
+}
+\arguments{
+\item{inputId}{The \code{id} of the \code{tabsetPanel} (or
+\code{navlistPanel} or \code{navbarPage}) in which to find
+\code{target}.}
+
+\item{target}{The \code{value} of the \code{tabPanel} to be
+hidden/shown. See Details if you want to hide/show an entire
+\code{navbarMenu} instead.}
+
+\item{select}{Should \code{target} be selected upon being shown?}
+
+\item{session}{The shiny session within which to call this function.}
+}
+\description{
+Dynamically hide or show a \code{\link{tabPanel}} (or a
+\code{\link{navbarMenu}})from an existing \code{\link{tabsetPanel}},
+\code{\link{navlistPanel}} or \code{\link{navbarPage}}.
+}
+\details{
+For \code{navbarPage}, you can hide/show conventional
+\code{tabPanel}s (whether at the top level or nested inside a
+\code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
+For the latter case, \code{target} should be the \code{menuName} that
+you gave your \code{navbarMenu} when you first created it (by default,
+this is equal to the value of the \code{title} argument).
+}
+\examples{
+## Only run this example in interactive R sessions
+if (interactive()) {
+
+ui <- navbarPage("Navbar page", id = "tabs",
+  tabPanel("Home",
+    actionButton("hideTab", "Hide 'Foo' tab"),
+    actionButton("showTab", "Show 'Foo' tab"),
+    actionButton("hideMenu", "Hide 'More' navbarMenu"),
+    actionButton("showMenu", "Show 'More' navbarMenu")
+  ),
+  tabPanel("Foo", "This is the foo tab"),
+  tabPanel("Bar", "This is the bar tab"),
+  navbarMenu("More",
+    tabPanel("Table", "Table page"),
+    tabPanel("About", "About page"),
+    "------",
+    "Even more!",
+    tabPanel("Email", "Email page")
+  )
+)
+
+server <- function(input, output, session) {
+  observeEvent(input$hideTab, {
+    hideTab(inputId = "tabs", target = "Foo")
+  })
+
+  observeEvent(input$showTab, {
+    showTab(inputId = "tabs", target = "Foo")
+  })
+
+  observeEvent(input$hideMenu, {
+    hideTab(inputId = "tabs", target = "More")
+  })
+
+  observeEvent(input$showMenu, {
+    showTab(inputId = "tabs", target = "More")
+  })
+}
+
+shinyApp(ui, server)
+}
+
+}
+\seealso{
+\code{\link{insertTab}}
+}
diff --git a/man/sidebarLayout.Rd b/man/sidebarLayout.Rd
index 4a5848a..da41658 100644
--- a/man/sidebarLayout.Rd
+++ b/man/sidebarLayout.Rd
@@ -63,4 +63,3 @@ server <- function(input, output) {
 shinyApp(ui, server)
 }
 }
-
diff --git a/man/sidebarPanel.Rd b/man/sidebarPanel.Rd
index 6bed408..96e360f 100644
--- a/man/sidebarPanel.Rd
+++ b/man/sidebarPanel.Rd
@@ -30,4 +30,3 @@ sidebarPanel(
   numericInput("obs", "Observations:", 10)
 )
 }
-
diff --git a/man/sliderInput.Rd b/man/sliderInput.Rd
index a4ff1be..13cdcee 100644
--- a/man/sliderInput.Rd
+++ b/man/sliderInput.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/input-slider.R
 \name{sliderInput}
-\alias{animationOptions}
 \alias{sliderInput}
+\alias{animationOptions}
 \title{Slider Input Widget}
 \usage{
 sliderInput(inputId, label, min, max, value, step = NULL, round = FALSE,
@@ -118,7 +118,7 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{updateSliderInput}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
@@ -127,4 +127,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{submitButton}}, \code{\link{textAreaInput}},
   \code{\link{textInput}}
 }
-
diff --git a/man/snapshotExclude.Rd b/man/snapshotExclude.Rd
new file mode 100644
index 0000000..8778381
--- /dev/null
+++ b/man/snapshotExclude.Rd
@@ -0,0 +1,14 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/snapshot.R
+\name{snapshotExclude}
+\alias{snapshotExclude}
+\title{Mark an output to be excluded from test snapshots}
+\usage{
+snapshotExclude(x)
+}
+\arguments{
+\item{x}{A reactive which will be assigned to an output.}
+}
+\description{
+Mark an output to be excluded from test snapshots
+}
diff --git a/man/snapshotPreprocessInput.Rd b/man/snapshotPreprocessInput.Rd
new file mode 100644
index 0000000..b5193bd
--- /dev/null
+++ b/man/snapshotPreprocessInput.Rd
@@ -0,0 +1,19 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/snapshot.R
+\name{snapshotPreprocessInput}
+\alias{snapshotPreprocessInput}
+\title{Add a function for preprocessing an input before taking a test snapshot}
+\usage{
+snapshotPreprocessInput(inputId, fun, session = getDefaultReactiveDomain())
+}
+\arguments{
+\item{inputId}{Name of the input value.}
+
+\item{fun}{A function that takes the input value and returns a modified
+value. The returned value will be used for the test snapshot.}
+
+\item{session}{A Shiny session object.}
+}
+\description{
+Add a function for preprocessing an input before taking a test snapshot
+}
diff --git a/man/snapshotPreprocessOutput.Rd b/man/snapshotPreprocessOutput.Rd
new file mode 100644
index 0000000..1c7923f
--- /dev/null
+++ b/man/snapshotPreprocessOutput.Rd
@@ -0,0 +1,17 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/snapshot.R
+\name{snapshotPreprocessOutput}
+\alias{snapshotPreprocessOutput}
+\title{Add a function for preprocessing an output before taking a test snapshot}
+\usage{
+snapshotPreprocessOutput(x, fun)
+}
+\arguments{
+\item{x}{A reactive which will be assigned to an output.}
+
+\item{fun}{A function that takes the output value as an input and returns a
+modified value. The returned value will be used for the test snapshot.}
+}
+\description{
+Add a function for preprocessing an output before taking a test snapshot
+}
diff --git a/man/splitLayout.Rd b/man/splitLayout.Rd
index 0920e49..da7b959 100644
--- a/man/splitLayout.Rd
+++ b/man/splitLayout.Rd
@@ -61,4 +61,3 @@ ui <- splitLayout(
 shinyApp(ui, server)
 }
 }
-
diff --git a/man/stacktrace.Rd b/man/stacktrace.Rd
index 1544ad7..626f4d6 100644
--- a/man/stacktrace.Rd
+++ b/man/stacktrace.Rd
@@ -1,17 +1,17 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/conditions.R
 \name{stacktrace}
-\alias{..stacktraceoff..}
-\alias{..stacktraceon..}
+\alias{stacktrace}
 \alias{captureStackTraces}
-\alias{conditionStackTrace}
-\alias{conditionStackTrace<-}
-\alias{extractStackTrace}
-\alias{formatStackTrace}
+\alias{withLogErrors}
 \alias{printError}
 \alias{printStackTrace}
-\alias{stacktrace}
-\alias{withLogErrors}
+\alias{extractStackTrace}
+\alias{formatStackTrace}
+\alias{conditionStackTrace}
+\alias{conditionStackTrace<-}
+\alias{..stacktraceon..}
+\alias{..stacktraceoff..}
 \title{Stack trace manipulation functions}
 \usage{
 captureStackTraces(expr)
@@ -168,4 +168,3 @@ try({
 
 }
 \keyword{internal}
-
diff --git a/man/stopApp.Rd b/man/stopApp.Rd
index 37c239c..03c278c 100644
--- a/man/stopApp.Rd
+++ b/man/stopApp.Rd
@@ -14,4 +14,3 @@ stopApp(returnValue = invisible())
 Stops the currently running Shiny app, returning control to the caller of
 \code{\link{runApp}}.
 }
-
diff --git a/man/submitButton.Rd b/man/submitButton.Rd
index 9aeb5fe..2ed5b14 100644
--- a/man/submitButton.Rd
+++ b/man/submitButton.Rd
@@ -65,7 +65,7 @@ shinyApp(
 }
 }
 \seealso{
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
@@ -74,4 +74,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{sliderInput}}, \code{\link{textAreaInput}},
   \code{\link{textInput}}
 }
-
diff --git a/man/tabPanel.Rd b/man/tabPanel.Rd
index ee86b46..4980f7b 100644
--- a/man/tabPanel.Rd
+++ b/man/tabPanel.Rd
@@ -38,4 +38,3 @@ mainPanel(
 \seealso{
 \code{\link{tabsetPanel}}
 }
-
diff --git a/man/tableOutput.Rd b/man/tableOutput.Rd
index 97e809c..afac26d 100644
--- a/man/tableOutput.Rd
+++ b/man/tableOutput.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/bootstrap.R
 \name{tableOutput}
-\alias{dataTableOutput}
 \alias{tableOutput}
+\alias{dataTableOutput}
 \title{Create a table output element}
 \usage{
 tableOutput(outputId)
@@ -57,4 +57,3 @@ if (interactive()) {
 \seealso{
 \code{\link{renderTable}}, \code{\link{renderDataTable}}.
 }
-
diff --git a/man/tabsetPanel.Rd b/man/tabsetPanel.Rd
index 00ae537..1bb706f 100644
--- a/man/tabsetPanel.Rd
+++ b/man/tabsetPanel.Rd
@@ -44,6 +44,6 @@ mainPanel(
 )
 }
 \seealso{
-\code{\link{tabPanel}}, \code{\link{updateTabsetPanel}}
+\code{\link{tabPanel}}, \code{\link{updateTabsetPanel}},
+  \code{\link{insertTab}}, \code{\link{showTab}}
 }
-
diff --git a/man/textAreaInput.Rd b/man/textAreaInput.Rd
index 7665827..5e3c15f 100644
--- a/man/textAreaInput.Rd
+++ b/man/textAreaInput.Rd
@@ -61,7 +61,7 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{updateTextAreaInput}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
@@ -70,4 +70,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{sliderInput}}, \code{\link{submitButton}},
   \code{\link{textInput}}
 }
-
diff --git a/man/textInput.Rd b/man/textInput.Rd
index 651d2dd..a9907fb 100644
--- a/man/textInput.Rd
+++ b/man/textInput.Rd
@@ -43,7 +43,7 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{updateTextInput}}
 
-Other input.elements: \code{\link{actionButton}},
+Other input elements: \code{\link{actionButton}},
   \code{\link{checkboxGroupInput}},
   \code{\link{checkboxInput}}, \code{\link{dateInput}},
   \code{\link{dateRangeInput}}, \code{\link{fileInput}},
@@ -52,4 +52,3 @@ Other input.elements: \code{\link{actionButton}},
   \code{\link{sliderInput}}, \code{\link{submitButton}},
   \code{\link{textAreaInput}}
 }
-
diff --git a/man/textOutput.Rd b/man/textOutput.Rd
index 2b18c28..9a82d7b 100644
--- a/man/textOutput.Rd
+++ b/man/textOutput.Rd
@@ -28,4 +28,3 @@ Text is HTML-escaped prior to rendering. This element is often used
 \examples{
 h3(textOutput("caption"))
 }
-
diff --git a/man/titlePanel.Rd b/man/titlePanel.Rd
index bcfe82c..e1e2ea0 100644
--- a/man/titlePanel.Rd
+++ b/man/titlePanel.Rd
@@ -29,4 +29,3 @@ ui <- fluidPage(
 shinyApp(ui, server = function(input, output) { })
 }
 }
-
diff --git a/man/updateActionButton.Rd b/man/updateActionButton.Rd
index cf3bcef..75f0f22 100644
--- a/man/updateActionButton.Rd
+++ b/man/updateActionButton.Rd
@@ -78,4 +78,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{actionButton}}
 }
-
diff --git a/man/updateCheckboxGroupInput.Rd b/man/updateCheckboxGroupInput.Rd
index b2595d1..f591a68 100644
--- a/man/updateCheckboxGroupInput.Rd
+++ b/man/updateCheckboxGroupInput.Rd
@@ -5,7 +5,8 @@
 \title{Change the value of a checkbox group input on the client}
 \usage{
 updateCheckboxGroupInput(session, inputId, label = NULL, choices = NULL,
-  selected = NULL, inline = FALSE)
+  selected = NULL, inline = FALSE, choiceNames = NULL,
+  choiceValues = NULL)
 }
 \arguments{
 \item{session}{The \code{session} object passed to function given to
@@ -16,11 +17,34 @@ updateCheckboxGroupInput(session, inputId, label = NULL, choices = NULL,
 \item{label}{The label to set for the input object.}
 
 \item{choices}{List of values to show checkboxes for. If elements of the list
-are named then that name rather than the value is displayed to the user.}
+are named then that name rather than the value is displayed to the user. If
+this argument is provided, then \code{choiceNames} and \code{choiceValues}
+must not be provided, and vice-versa. The values should be strings; other
+types (such as logicals and numbers) will be coerced to strings.}
 
 \item{selected}{The values that should be initially selected, if any.}
 
 \item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)}
+
+\item{choiceNames}{List of names and values, respectively,
+that are displayed to the user in the app and correspond to the each
+choice (for this reason, \code{choiceNames} and \code{choiceValues}
+must have the same length). If either of these arguments is
+provided, then the other \emph{must} be provided and \code{choices}
+\emph{must not} be provided. The advantage of using both of these over
+a named list for \code{choices} is that \code{choiceNames} allows any
+type of UI object to be passed through (tag objects, icons, HTML code,
+...), instead of just simple text. See Examples.}
+
+\item{choiceValues}{List of names and values, respectively,
+that are displayed to the user in the app and correspond to the each
+choice (for this reason, \code{choiceNames} and \code{choiceValues}
+must have the same length). If either of these arguments is
+provided, then the other \emph{must} be provided and \code{choices}
+\emph{must not} be provided. The advantage of using both of these over
+a named list for \code{choices} is that \code{choiceNames} allows any
+type of UI object to be passed through (tag objects, icons, HTML code,
+...), instead of just simple text. See Examples.}
 }
 \description{
 Change the value of a checkbox group input on the client
@@ -77,4 +101,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{checkboxGroupInput}}
 }
-
diff --git a/man/updateCheckboxInput.Rd b/man/updateCheckboxInput.Rd
index 6b5a222..15d55e7 100644
--- a/man/updateCheckboxInput.Rd
+++ b/man/updateCheckboxInput.Rd
@@ -60,4 +60,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{checkboxInput}}
 }
-
diff --git a/man/updateDateInput.Rd b/man/updateDateInput.Rd
index 19ac170..ef91819 100644
--- a/man/updateDateInput.Rd
+++ b/man/updateDateInput.Rd
@@ -71,4 +71,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{dateInput}}
 }
-
diff --git a/man/updateDateRangeInput.Rd b/man/updateDateRangeInput.Rd
index 6a922fc..b5f20c1 100644
--- a/man/updateDateRangeInput.Rd
+++ b/man/updateDateRangeInput.Rd
@@ -76,4 +76,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{dateRangeInput}}
 }
-
diff --git a/man/updateNumericInput.Rd b/man/updateNumericInput.Rd
index 7e12209..8a08164 100644
--- a/man/updateNumericInput.Rd
+++ b/man/updateNumericInput.Rd
@@ -74,4 +74,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{numericInput}}
 }
-
diff --git a/man/updateQueryString.Rd b/man/updateQueryString.Rd
index 418012f..d63c1d7 100644
--- a/man/updateQueryString.Rd
+++ b/man/updateQueryString.Rd
@@ -4,11 +4,18 @@
 \alias{updateQueryString}
 \title{Update URL in browser's location bar}
 \usage{
-updateQueryString(queryString, session = getDefaultReactiveDomain())
+updateQueryString(queryString, mode = c("replace", "push"),
+  session = getDefaultReactiveDomain())
 }
 \arguments{
 \item{queryString}{The new query string to show in the location bar.}
 
+\item{mode}{When the query string is updated, should the the current history
+entry be replaced (default), or should a new history entry be pushed onto
+the history stack? The former should only be used in a live bookmarking
+context. The latter is useful if you want to navigate between states using
+the browser's back and forward buttons. See Examples.}
+
 \item{session}{A Shiny session object.}
 }
 \description{
@@ -16,7 +23,79 @@ This function updates the client browser's query string in the location bar.
 It typically is called from an observer. Note that this will not work in
 Internet Explorer 9 and below.
 }
-\seealso{
-\code{\link{enableBookmarking}} for examples.
+\details{
+For \code{mode = "push"}, only three updates are currently allowed:
+\enumerate{
+  \item the query string (format: \code{?param1=val1&param2=val2})
+  \item the hash (format: \code{#hash})
+  \item both the query string and the hash
+    (format: \code{?param1=val1&param2=val2#hash})
+}
+
+In other words, if \code{mode = "push"}, the \code{queryString} must start
+with either \code{?} or with \code{#}.
+
+A technical curiosity: under the hood, this function is calling the HTML5
+history API (which is where the names for the \code{mode} argument come from).
+When \code{mode = "replace"}, the function called is
+\code{window.history.replaceState(null, null, queryString)}.
+When \code{mode = "push"}, the function called is
+\code{window.history.pushState(null, null, queryString)}.
 }
+\examples{
+## Only run these examples in interactive sessions
+if (interactive()) {
 
+  ## App 1: Doing "live" bookmarking
+  ## Update the browser's location bar every time an input changes.
+  ## This should not be used with enableBookmarking("server"),
+  ## because that would create a new saved state on disk every time
+  ## the user changes an input.
+  enableBookmarking("url")
+  shinyApp(
+    ui = function(req) {
+      fluidPage(
+        textInput("txt", "Text"),
+        checkboxInput("chk", "Checkbox")
+      )
+    },
+    server = function(input, output, session) {
+      observe({
+        # Trigger this observer every time an input changes
+        reactiveValuesToList(input)
+        session$doBookmark()
+      })
+      onBookmarked(function(url) {
+        updateQueryString(url)
+      })
+    }
+  )
+
+  ## App 2: Printing the value of the query string
+  ## (Use the back and forward buttons to see how the browser
+  ## keeps a record of each state)
+  shinyApp(
+    ui = fluidPage(
+      textInput("txt", "Enter new query string"),
+      helpText("Format: ?param1=val1&param2=val2"),
+      actionButton("go", "Update"),
+      hr(),
+      verbatimTextOutput("query")
+    ),
+    server = function(input, output, session) {
+      observeEvent(input$go, {
+        updateQueryString(input$txt, mode = "push")
+      })
+      output$query <- renderText({
+        query <- getQueryString()
+        queryText <- paste(names(query), query,
+                       sep = "=", collapse=", ")
+        paste("Your query string is:\\n", queryText)
+      })
+    }
+  )
+}
+}
+\seealso{
+\code{\link{enableBookmarking}}, \code{\link{getQueryString}}
+}
diff --git a/man/updateRadioButtons.Rd b/man/updateRadioButtons.Rd
index c297c7b..216366a 100644
--- a/man/updateRadioButtons.Rd
+++ b/man/updateRadioButtons.Rd
@@ -5,7 +5,8 @@
 \title{Change the value of a radio input on the client}
 \usage{
 updateRadioButtons(session, inputId, label = NULL, choices = NULL,
-  selected = NULL, inline = FALSE)
+  selected = NULL, inline = FALSE, choiceNames = NULL,
+  choiceValues = NULL)
 }
 \arguments{
 \item{session}{The \code{session} object passed to function given to
@@ -16,12 +17,35 @@ updateRadioButtons(session, inputId, label = NULL, choices = NULL,
 \item{label}{The label to set for the input object.}
 
 \item{choices}{List of values to select from (if elements of the list are
-named then that name rather than the value is displayed to the user)}
+named then that name rather than the value is displayed to the user). If
+this argument is provided, then \code{choiceNames} and \code{choiceValues}
+must not be provided, and vice-versa. The values should be strings; other
+types (such as logicals and numbers) will be coerced to strings.}
 
-\item{selected}{The initially selected value (if not specified then
-defaults to the first value)}
+\item{selected}{The initially selected value (if not specified then defaults
+to the first value)}
 
 \item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)}
+
+\item{choiceNames}{List of names and values, respectively, that
+are displayed to the user in the app and correspond to the each choice (for
+this reason, \code{choiceNames} and \code{choiceValues} must have the same
+length). If either of these arguments is provided, then the other
+\emph{must} be provided and \code{choices} \emph{must not} be provided. The
+advantage of using both of these over a named list for \code{choices} is
+that \code{choiceNames} allows any type of UI object to be passed through
+(tag objects, icons, HTML code, ...), instead of just simple text. See
+Examples.}
+
+\item{choiceValues}{List of names and values, respectively, that
+are displayed to the user in the app and correspond to the each choice (for
+this reason, \code{choiceNames} and \code{choiceValues} must have the same
+length). If either of these arguments is provided, then the other
+\emph{must} be provided and \code{choices} \emph{must not} be provided. The
+advantage of using both of these over a named list for \code{choices} is
+that \code{choiceNames} allows any type of UI object to be passed through
+(tag objects, icons, HTML code, ...), instead of just simple text. See
+Examples.}
 }
 \description{
 Change the value of a radio input on the client
@@ -74,4 +98,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{radioButtons}}
 }
-
diff --git a/man/updateSelectInput.Rd b/man/updateSelectInput.Rd
index 6c3f5a6..1f981f8 100644
--- a/man/updateSelectInput.Rd
+++ b/man/updateSelectInput.Rd
@@ -96,4 +96,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{selectInput}}
 }
-
diff --git a/man/updateSliderInput.Rd b/man/updateSliderInput.Rd
index 4ae24c2..9ce463e 100644
--- a/man/updateSliderInput.Rd
+++ b/man/updateSliderInput.Rd
@@ -74,4 +74,3 @@ if (interactive()) {
 \seealso{
 \code{\link{sliderInput}}
 }
-
diff --git a/man/updateTabsetPanel.Rd b/man/updateTabsetPanel.Rd
index 4304477..d23f7d4 100644
--- a/man/updateTabsetPanel.Rd
+++ b/man/updateTabsetPanel.Rd
@@ -1,9 +1,9 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/update-input.R
 \name{updateTabsetPanel}
+\alias{updateTabsetPanel}
 \alias{updateNavbarPage}
 \alias{updateNavlistPanel}
-\alias{updateTabsetPanel}
 \title{Change the selected tab on the client}
 \usage{
 updateTabsetPanel(session, inputId, selected = NULL)
@@ -56,4 +56,3 @@ shinyApp(ui, server)
 \code{\link{tabsetPanel}}, \code{\link{navlistPanel}},
 \code{\link{navbarPage}}
 }
-
diff --git a/man/updateTextAreaInput.Rd b/man/updateTextAreaInput.Rd
index a357412..063bb3f 100644
--- a/man/updateTextAreaInput.Rd
+++ b/man/updateTextAreaInput.Rd
@@ -4,7 +4,8 @@
 \alias{updateTextAreaInput}
 \title{Change the value of a textarea input on the client}
 \usage{
-updateTextAreaInput(session, inputId, label = NULL, value = NULL)
+updateTextAreaInput(session, inputId, label = NULL, value = NULL,
+  placeholder = NULL)
 }
 \arguments{
 \item{session}{The \code{session} object passed to function given to
@@ -15,6 +16,8 @@ updateTextAreaInput(session, inputId, label = NULL, value = NULL)
 \item{label}{The label to set for the input object.}
 
 \item{value}{The value to set for the input object.}
+
+\item{placeholder}{The placeholder to set for the input object.}
 }
 \description{
 Change the value of a textarea input on the client
@@ -68,4 +71,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{textAreaInput}}
 }
-
diff --git a/man/updateTextInput.Rd b/man/updateTextInput.Rd
index 0aef66a..0b6dde5 100644
--- a/man/updateTextInput.Rd
+++ b/man/updateTextInput.Rd
@@ -4,7 +4,8 @@
 \alias{updateTextInput}
 \title{Change the value of a text input on the client}
 \usage{
-updateTextInput(session, inputId, label = NULL, value = NULL)
+updateTextInput(session, inputId, label = NULL, value = NULL,
+  placeholder = NULL)
 }
 \arguments{
 \item{session}{The \code{session} object passed to function given to
@@ -15,6 +16,8 @@ updateTextInput(session, inputId, label = NULL, value = NULL)
 \item{label}{The label to set for the input object.}
 
 \item{value}{The value to set for the input object.}
+
+\item{placeholder}{The placeholder to set for the input object.}
 }
 \description{
 Change the value of a text input on the client
@@ -68,4 +71,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{textInput}}
 }
-
diff --git a/man/urlModal.Rd b/man/urlModal.Rd
index d6ab510..3075021 100644
--- a/man/urlModal.Rd
+++ b/man/urlModal.Rd
@@ -19,4 +19,3 @@ textarea input, and the URL text will be selected so that it can be easily
 copied. The result from \code{urlModal} should be passed to the
 \code{\link{showModal}} function to display it in the browser.
 }
-
diff --git a/man/validate.Rd b/man/validate.Rd
index 9f2ba52..1c9cc20 100644
--- a/man/validate.Rd
+++ b/man/validate.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/utils.R
 \name{validate}
-\alias{need}
 \alias{validate}
+\alias{need}
 \title{Validate input values and other conditions}
 \usage{
 validate(..., errorClass = character(0))
@@ -102,4 +102,3 @@ shinyApp(ui, server)
 
 }
 }
-
diff --git a/man/verbatimTextOutput.Rd b/man/verbatimTextOutput.Rd
index 05fd583..67c2c2f 100644
--- a/man/verbatimTextOutput.Rd
+++ b/man/verbatimTextOutput.Rd
@@ -41,4 +41,3 @@ if (interactive()) {
   )
 }
 }
-
diff --git a/man/verticalLayout.Rd b/man/verticalLayout.Rd
index a86a066..65fb55e 100644
--- a/man/verticalLayout.Rd
+++ b/man/verticalLayout.Rd
@@ -33,4 +33,3 @@ shinyApp(ui, server = function(input, output) { })
 \seealso{
 \code{\link{fluidPage}}, \code{\link{flowLayout}}
 }
-
diff --git a/man/viewer.Rd b/man/viewer.Rd
index a6e63d8..48fa998 100644
--- a/man/viewer.Rd
+++ b/man/viewer.Rd
@@ -1,10 +1,10 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/server.R
 \name{viewer}
-\alias{browserViewer}
-\alias{dialogViewer}
-\alias{paneViewer}
 \alias{viewer}
+\alias{paneViewer}
+\alias{dialogViewer}
+\alias{browserViewer}
 \title{Viewer options}
 \usage{
 paneViewer(minHeight = NULL)
@@ -36,4 +36,3 @@ viewer APIs are not available in the current R environment, then the gadget
 will be displayed in the system's default web browser (see
 \code{\link[utils]{browseURL}}).
 }
-
diff --git a/man/wellPanel.Rd b/man/wellPanel.Rd
index c2db5b3..cb80313 100644
--- a/man/wellPanel.Rd
+++ b/man/wellPanel.Rd
@@ -16,4 +16,3 @@ The newly created panel.
 Creates a panel with a slightly inset border and grey background. Equivalent
 to Bootstrap's \code{well} CSS class.
 }
-
diff --git a/man/withMathJax.Rd b/man/withMathJax.Rd
index 4ea5954..595b1ff 100644
--- a/man/withMathJax.Rd
+++ b/man/withMathJax.Rd
@@ -21,4 +21,3 @@ withMathJax(helpText("Some math here $$\\\\alpha+\\\\beta$$"))
 # now we can just write "static" content without withMathJax()
 div("more math here $$\\\\sqrt{2}$$")
 }
-
diff --git a/man/withProgress.Rd b/man/withProgress.Rd
index 53298ce..a8a13df 100644
--- a/man/withProgress.Rd
+++ b/man/withProgress.Rd
@@ -1,9 +1,9 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/progress.R
 \name{withProgress}
-\alias{incProgress}
-\alias{setProgress}
 \alias{withProgress}
+\alias{setProgress}
+\alias{incProgress}
 \title{Reporting progress (functional API)}
 \usage{
 withProgress(expr, min = 0, max = 1, value = min + (max - min) * 0.1,
@@ -28,8 +28,7 @@ Must be less tham \code{max}. Default is 0.}
 greater than \code{min}. Default is 1.}
 
 \item{value}{Single-element numeric vector; the value at which to set the
-progress bar, relative to \code{min} and \code{max}. \code{NULL} hides the
-progress bar, if it is currently visible.}
+progress bar, relative to \code{min} and \code{max}.}
 
 \item{message}{A single-element character vector; the message to be displayed
 to the user, or \code{NULL} to hide the current message (if any).}
@@ -115,4 +114,3 @@ shinyApp(ui, server)
 \seealso{
 \code{\link{Progress}}
 }
-
diff --git a/tests/testthat/test-bootstrap.r b/tests/testthat/test-bootstrap.r
index 29c9201..56f1e8f 100644
--- a/tests/testthat/test-bootstrap.r
+++ b/tests/testthat/test-bootstrap.r
@@ -28,22 +28,43 @@ test_that("Repeated names for selectInput and radioButtons choices", {
      format(x)
   ))
 
-
-  # Radio buttons
+  # Radio buttons using choices
   x <- radioButtons('id','label', choices = c(a='x1', a='x2', b='x3'))
   choices <- x$children
 
-  expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]], 'a')
+  expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]], HTML('a'))
   expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$value, 'x1')
   expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$checked, 'checked')
 
-  expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[2]]$children[[1]], 'a')
+  expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[2]]$children[[1]], HTML('a'))
   expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$value, 'x2')
   expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$checked, NULL)
 
-  expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]], 'b')
+  expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]], HTML('b'))
   expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$value, 'x3')
   expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$checked, NULL)
+
+  # Radio buttons using choiceNames and choiceValues
+  x <- radioButtons('id','label',
+    choiceNames = list(icon('calendar'), HTML('<p style="color:red;">Red</p>'), 'Normal'),
+    choiceValues = list('icon', 'html', 'text')
+  )
+  choices <- x$children
+
+  expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]],
+    HTML('<i class="fa fa-calendar"></i>'))
+  expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$value, 'icon')
+  expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$checked, 'checked')
+
+  expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[2]]$children[[1]],
+    HTML('<p style="color:red;">Red</p>'))
+  expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$value, 'html')
+  expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$checked, NULL)
+
+  expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]],
+    HTML('Normal'))
+  expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$value, 'text')
+  expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$checked, NULL)
 })
 
 
@@ -167,3 +188,80 @@ test_that("selectInput selects items by default", {
     selectInput('x', 'x', list("a", "b"), multiple = TRUE)
   ))
 })
+
+test_that("normalizeChoicesArgs does its job", {
+
+  # Unnamed vectors and lists
+  expected <- list(choiceNames = list("a", "b"), choiceValues = list("a", "b"))
+  expect_equal(normalizeChoicesArgs(c("a", "b"), NULL, NULL), expected)
+  expect_equal(normalizeChoicesArgs(list("a", "b"), NULL, NULL), expected)
+
+  # Named list
+  expected <- list(choiceNames = list("one", "two"), choiceValues = list("a", "b"))
+  x <- list(one = "a", two = "b")
+  expect_equal(normalizeChoicesArgs(x, NULL, NULL), expected)
+  expect_equal(normalizeChoicesArgs(NULL, names(x), unname(x)), expected)
+
+  # Using unnamed `choiceNames` and `choiceValues` vectors/lists directly
+  expect_equal(normalizeChoicesArgs(NULL, c("one", "two"),  c("a", "b")), expected)
+  expect_equal(normalizeChoicesArgs(NULL, list("one", "two"), list("a", "b")), expected)
+
+  # Numbers
+  expected <- list(choiceNames = list("a", "b"), choiceValues = list("1", "2"))
+  expect_equal(normalizeChoicesArgs(c("a" = 1, "b" = 2), NULL, NULL), expected)
+  expect_equal(normalizeChoicesArgs(list("a" = 1, "b" = 2), NULL, NULL), expected)
+  expect_equal(normalizeChoicesArgs(NULL, c("a", "b"), c(1, 2)), expected)
+  expect_equal(normalizeChoicesArgs(NULL, list("a", "b"), list("1", "2")), expected)
+
+  # Using choiceNames with HTML and choiceValues
+  nms <- list(icon("calendar"), HTML("<p style='color:red;'>Red Text</p>"))
+  vals <- list("a", "b")
+  expected <- list(choiceNames = nms, choiceValues = vals)
+  expect_equal(normalizeChoicesArgs(NULL, nms, vals), expected)
+
+  # Attempt to use choices, AND choiceNames + choiceValues
+  x <- list("a", "b")
+  expect_warning(res <- normalizeChoicesArgs(x, nms, vals),
+    "Using `choices` argument; ignoring `choiceNames` and `choiceValues`.")
+  expect_equal(res, list(choiceNames = list("a", "b"), choiceValues = list("a", "b")))
+
+  # Set possibilities to character(0)
+  expected <- list(choiceNames = list(), choiceValues = list())
+  expect_equal(normalizeChoicesArgs(character(0), NULL, NULL), expected)
+  expect_equal(normalizeChoicesArgs(NULL, character(0), character(0)), expected)
+  expect_warning(res <- normalizeChoicesArgs(character(0), character(0), character(0)),
+    "Using `choices` argument; ignoring `choiceNames` and `choiceValues`.")
+  expect_equal(res, expected)
+
+  # Set possibilities to NULL in an inconsistent way
+  expected <- paste("One of `choiceNames` or `choiceValues` was set to NULL,",
+                    "but either both or none should be NULL.")
+  expect_error(normalizeChoicesArgs(NULL, character(0), NULL, FALSE), expected, fixed = TRUE)
+  expect_error(normalizeChoicesArgs(NULL, NULL, character(0), FALSE), expected, fixed = TRUE)
+  expected <- paste("Please specify a non-empty vector for `choices` (or,",
+                    "alternatively, for both `choiceNames` AND `choiceValues`).")
+  expect_error(normalizeChoicesArgs(NULL, character(0), NULL), expected, fixed = TRUE)
+  expect_error(normalizeChoicesArgs(NULL, NULL, character(0)), expected, fixed = TRUE)
+
+  # Set all possibilities to NULL (and mustExist = FALSE)
+  expected <- list(choiceNames = NULL, choiceValues = NULL)
+  expect_equal(normalizeChoicesArgs(NULL, NULL, NULL, FALSE), expected)
+})
+
+test_that("Choices need not be provided, can be NULL or c()", {
+
+  expected <- "<div id=\"cb\" class=\"form-group shiny-input-checkboxgroup shiny-input-container\">\n  <label class=\"control-label\" for=\"cb\">Choose:</label>\n  <div class=\"shiny-options-group\"></div>\n</div>"
+  noChoices <- checkboxGroupInput("cb", "Choose:")
+  choicesNull <- checkboxGroupInput("cb", "Choose:", choices = NULL)
+  choicesCharacter <- checkboxGroupInput("cb", "Choose:", choices = c())
+  choicesCharacter0 <- checkboxGroupInput("cb", "Choose:", choices = character(0))
+  allChoicesNull <- checkboxGroupInput("cb", "Choose:", choices = NULL,
+    choiceNames = NULL, choiceValues = NULL)
+
+  expect_identical(noChoices, choicesNull)
+  expect_identical(noChoices, choicesCharacter)
+  expect_identical(noChoices, choicesCharacter0)
+  expect_identical(noChoices, allChoicesNull)
+
+  expect_true(grepl(fixed = TRUE, expected, format(noChoices)))
+})
diff --git a/tests/testthat/test-get-extension.R b/tests/testthat/test-get-extension.R
new file mode 100644
index 0000000..be17965
--- /dev/null
+++ b/tests/testthat/test-get-extension.R
@@ -0,0 +1,11 @@
+context("get-extension")
+
+test_that("Valid extensions are preserved", {
+  expect_equal(maybeGetExtension("report.csv"), ".csv")
+  expect_equal(maybeGetExtension("data.tar.bz2"), ".bz2")
+})
+
+test_that("Invalid extensions are discarded", {
+  expect_equal(maybeGetExtension("report. ℒ  裸 邏 ℓo "), "")
+  expect_equal(maybeGetExtension("data"), "")
+})
diff --git a/tests/testthat/test-modules.R b/tests/testthat/test-modules.R
index 6a687d6..e0dfa04 100644
--- a/tests/testthat/test-modules.R
+++ b/tests/testthat/test-modules.R
@@ -1,11 +1,30 @@
 context("modules")
 
 test_that("Namespace qualifying", {
+  expect_equivalent(NS(NULL, "one"), "one")
+  expect_equivalent(NS(NULL)("one"), "one")
+
+  expect_equivalent(NS("one", NULL), "one")
+  expect_equivalent(NS("one")(NULL), "one")
+
   expect_equivalent(NS("one", "two"), "one-two")
+  expect_equivalent(NS("one")("two"), "one-two")
+
   expect_equivalent(NS(c("one", "two"))(NULL), "one-two")
-  expect_equivalent(NS(NULL)(c("one", "two")), "one-two")
-  expect_equivalent(NS(c("one", "two"), c("three", "four")), "one-two-three-four")
-  expect_equivalent(NS(c("one", "two"))(c("three", "four")), "one-two-three-four")
+  expect_equivalent(NS(c("one", "two"), NULL), "one-two")
+
+  expect_equivalent(NS(NULL)(c("one", "two")), c("one", "two"))
+  expect_equivalent(NS(NULL, c("one", "two")), c("one", "two"))
+
+  expect_equivalent(NS("one", c("two", "three")), c("one-two", "one-three"))
+  expect_equivalent(NS("one")(c("two", "three")), c("one-two", "one-three"))
+
+  expect_equivalent(NS(c("one", "two"), "three"), "one-two-three")
+  expect_equivalent(NS(c("one", "two"))("three"), "one-two-three")
+
+  expect_equivalent(NS(c("one", "two"), c("three", "four")), c("one-two-three", "one-two-four"))
+  expect_equivalent(NS(c("one", "two"))(c("three", "four")), c("one-two-three", "one-two-four"))
+
   expect_equivalent(NS(c("one", "two"))("three four"), "one-two-three four")
   expect_equivalent(NS(c("one", "two"))("three-four"), "one-two-three-four")
 })
diff --git a/tests/testthat/test-plot-coordmap.R b/tests/testthat/test-plot-coordmap.R
index 212bcbf..8741686 100644
--- a/tests/testthat/test-plot-coordmap.R
+++ b/tests/testthat/test-plot-coordmap.R
@@ -60,17 +60,17 @@ test_that("ggplot coordmap", {
   dev.off()
 
   # Check mapping vars
-  expect_equal(m[[1]]$mapping, list(x = "xvar", y = "yvar"))
+  expect_equal(sortList(m[[1]]$mapping), list(x = "xvar", y = "yvar"))
 
 
-  # Plot with computed variable (histogram)
-  p <- ggplot(dat, aes(xvar)) + geom_histogram(binwidth=1)
+  # Plot with an expression in aes, and a computed variable (histogram)
+  p <- ggplot(dat, aes(xvar/2)) + geom_histogram(binwidth=1)
   png(tmpfile)
   m <- getGgplotCoordmap(print(p), 1, 72)
   dev.off()
 
   # Check mapping vars - no value for y
-  expect_equal(m[[1]]$mapping, list(x = "xvar", y = NULL))
+  expect_equal(sortList(m[[1]]$mapping), list(x = "xvar/2", y = NULL))
 })
 
 
diff --git a/tests/testthat/test-reactivity.r b/tests/testthat/test-reactivity.r
index 9f5edcb..1933706 100644
--- a/tests/testthat/test-reactivity.r
+++ b/tests/testthat/test-reactivity.r
@@ -1,6 +1,77 @@
 context("reactivity")
 
 
+test_that("ReactiveVal", {
+  val <- reactiveVal()
+
+  isolate({
+    expect_true(is.null(val()))
+
+    # Set to a simple value
+    val(1)
+    expect_equal(val(), 1)
+
+    # Set to a complex value
+    val(cars)
+    expect_equal(val(), cars)
+
+    # Check that passing in an initial value works
+    expect_equal(reactiveVal(10)(), 10)
+  })
+
+  o <- observe({
+    val()
+  })
+  flushReact()
+  expect_equal(execCount(o), 1)
+  # Just making sure o is stable
+  flushReact()
+  expect_equal(execCount(o), 1)
+
+  # Changing value causes o to invalidate
+  val(10)
+  flushReact()
+  expect_equal(execCount(o), 2)
+
+  # Setting new value that's same as current value is a no-op
+  val(10)
+  flushReact()
+  expect_equal(execCount(o), 2)  #
+
+  o$destroy()
+})
+
+test_that("ReactiveVals have independent dependencies", {
+  # Issue 1710
+  x <- reactiveVal(0)
+  y <- reactiveVal(0)
+
+  o <- observe({
+    y()
+  })
+
+  # The observer always fires the first time
+  x(1)
+  flushReact()
+  expect_equal(execCount(o), 1)
+
+  # Changing x again shouldn't invalidate the observer
+  x(2)
+  flushReact()
+  expect_equal(execCount(o), 1)
+
+  o$destroy()
+})
+
+
+test_that("ReactiveVal labels", {
+  val <- reactiveVal()
+  expect_equal(attr(val, "label", exact = TRUE), "val")
+
+  name.with.dots = reactiveVal()
+  expect_equal(attr(name.with.dots, "label", exact = TRUE), "name.with.dots")
+})
+
 # Test for correct behavior of ReactiveValues
 test_that("ReactiveValues", {
   # Creation and indexing into ReactiveValues -------------------------------
diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R
index efaa846..96faf7a 100644
--- a/tests/testthat/test-utils.R
+++ b/tests/testthat/test-utils.R
@@ -52,6 +52,27 @@ test_that("Setting the private seed explicitly results in identical values", {
   expect_identical(id7, id8)
 })
 
+test_that("Private and 'public' random streams are independent and work the same", {
+  set.seed(0)
+  public <- c(runif(1), runif(1), runif(1))
+  withPrivateSeed(set.seed(0))
+  private <- c(withPrivateSeed(runif(1)), withPrivateSeed(runif(1)), withPrivateSeed(runif(1)))
+  expect_identical(public, private)
+
+  # Interleaved calls to runif() with private and public streams
+  set.seed(0)
+  withPrivateSeed(set.seed(0))
+  public  <- numeric()
+  private <- numeric()
+  public[1]  <- runif(1)
+  private[1] <- withPrivateSeed(runif(1))
+  private[2] <- withPrivateSeed(runif(1))
+  public[2]  <- runif(1)
+  public[3]  <- runif(1)
+  private[3] <- withPrivateSeed(runif(1))
+  expect_identical(public, private)
+})
+
 test_that("need() works as expected", {
 
   # These are all falsy

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-shiny.git



More information about the debian-med-commit mailing list