Compare commits

..

7 Commits

Author SHA1 Message Date
Barret Schloerke
890957495b Remove debug branch name 2021-08-02 22:33:01 -04:00
Barret Schloerke
877ad18bb9 remove debug step 2021-08-02 22:31:53 -04:00
Barret Schloerke
a1cf9b40f0 fix master spacing 2021-08-02 22:31:42 -04:00
Barret Schloerke
5361c61faa use same branch 2021-08-02 22:30:00 -04:00
Barret Schloerke
b386e2b550 use same branch 2021-08-02 22:29:48 -04:00
Barret Schloerke
52499e4bc0 add space 2021-08-02 22:29:25 -04:00
Barret Schloerke
6fae374b11 debug 2021-08-02 22:28:10 -04:00
166 changed files with 10055 additions and 2350 deletions

View File

@@ -1,12 +0,0 @@
#!/bin/bash -e
. ./tools/documentation/checkDocsCurrent.sh
echo "Updating package.json version to match DESCRIPTION Version"
Rscript ./tools/updatePackageJsonVersion.R
if [ -n "$(git status --porcelain package.json)" ]
then
yarn build
git add ./inst package.json && git commit -m 'Sync package version (GitHub Actions)' || echo "No package version to commit"
else
echo "No package version difference detected; package.json is current."
fi

View File

@@ -1,23 +1,150 @@
# Workflow derived from https://github.com/rstudio/shiny-workflows
#
# NOTE: This Shiny team GHA workflow is overkill for most R packages.
# For most R packages it is better to use https://github.com/r-lib/actions
name: R-CMD-check
on:
push:
branches: [main, rc-**]
branches:
- master
pull_request:
branches: [main]
schedule:
- cron: '0 5 * * 1' # every monday
branches:
- master
name: Package checks
jobs:
website:
uses: rstudio/shiny-workflows/.github/workflows/website.yaml@v1
routine:
uses: rstudio/shiny-workflows/.github/workflows/routine.yaml@v1
with:
node-version: "14.x"
R-CMD-check:
uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
strategy:
fail-fast: false
matrix:
config:
- {os: macOS-latest, r: 'devel'}
- {os: macOS-latest, r: '4.0'}
- {os: windows-latest, r: '4.0'}
- {os: ubuntu-16.04, r: '4.0', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
- {os: ubuntu-16.04, r: '3.6', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
- {os: ubuntu-16.04, r: '3.5', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
- {os: ubuntu-16.04, r: '3.4', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
- {os: ubuntu-16.04, r: '3.3', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
env:
_R_CHECK_FORCE_SUGGESTS_: false
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
RSPM: ${{ matrix.config.rspm }}
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
steps:
# https://github.com/actions/checkout/issues/135
- name: Set git to use LF
if: runner.os == 'Windows'
run: |
git config --system core.autocrlf false
git config --system core.eol lf
- uses: actions/checkout@v2
- uses: r-lib/actions/setup-r@master
id: install-r
with:
r-version: ${{ matrix.config.r }}
- uses: r-lib/actions/setup-pandoc@master
- name: Install pak and query dependencies
shell: Rscript {0}
run: |
install.packages("pak", repos = "https://r-lib.github.io/p/pak/dev/")
saveRDS(pak::pkg_deps_tree("local::.", dependencies = TRUE), ".github/r-depends.rds")
- name: Cache R packages
uses: actions/cache@v2
with:
path: ${{ env.R_LIBS_USER }}
key: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-${{ hashFiles('.github/r-depends.rds') }}
restore-keys: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-
- name: Install system dependencies
if: runner.os == 'Linux'
shell: Rscript {0}
run: |
pak::local_system_requirements(execute = TRUE)
# https://stackoverflow.com/a/66568545/591574
#> fatal error: 'X11/Intrinsic.h' file not found
- name: Install Cairo macOS R devel dependency
if: runner.os == 'macOS' && matrix.config.r == 'devel'
run: |
brew install libxt
# xquartz and cairo are needed for Cairo package.
# harfbuzz and fribidi are needed for textshaping package.
- name: Mac systemdeps
if: runner.os == 'macOS'
run: |
brew install --cask xquartz
brew install cairo
brew install harfbuzz fribidi
# Use a shorter temp directory for pak installations, due to filename
# length issues on Windows. https://github.com/r-lib/pak/issues/252
- name: Windows temp dir
if: runner.os == 'Windows'
run: |
New-Item -Path "C:\" -Name "tmp" -ItemType Directory
echo "TMPDIR=c:\tmp" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Install dependencies
run: |
pak::local_install_dev_deps(upgrade = TRUE)
pak::pkg_install("rcmdcheck")
shell: Rscript {0}
- name: Find PhantomJS path
id: phantomjs
run: |
echo "::set-output name=path::$(Rscript -e 'cat(shinytest:::phantom_paths()[[1]])')"
- name: Cache PhantomJS
uses: actions/cache@v2
with:
path: ${{ steps.phantomjs.outputs.path }}
key: ${{ matrix.config.os }}-phantomjs
restore-keys: ${{ matrix.config.os }}-phantomjs
- name: Install PhantomJS
run: >
Rscript
-e "if (!shinytest::dependenciesInstalled()) shinytest::installDependencies()"
- name: Session info
run: |
options(width = 100)
pkgs <- installed.packages()[, "Package"]
sessioninfo::session_info(pkgs, include_base = TRUE)
shell: Rscript {0}
- name: Check
env:
_R_CHECK_CRAN_INCOMING_: false
_R_CHECK_FORCE_SUGGESTS_: ${{ matrix.config.r != 'devel' }}
run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check")
shell: Rscript {0}
- name: Show testthat output
if: always()
run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true
shell: bash
- name: Upload check results
if: failure()
uses: actions/upload-artifact@v2
with:
name: ${{ matrix.config.os }}-r${{ matrix.config.r }}-results
path: check
- name: Fix path for Windows caching
if: runner.os == 'Windows'
# This is needed because if you use the default tar at this stage,
# C:/Rtools/bin/tar.exe, it will say that it can't find gzip.exe. So
# we'll just set the path so that the original tar that would be
# found, will be found.
run: echo "C:/Program Files/Git/usr/bin" >> $GITHUB_PATH

172
.github/workflows/rituals.yaml vendored Normal file
View File

@@ -0,0 +1,172 @@
on:
push:
branches:
- master
- ghactions
pull_request:
branches:
- master
name: Rituals
jobs:
rituals:
name: Rituals
# if: false
runs-on: ${{ matrix.config.os }}
strategy:
fail-fast: false
matrix:
config:
- { os: ubuntu-16.04, r: '4.0', node: "14.x", rspm: "https://packagemanager.rstudio.com/all/__linux__/xenial/latest"}
env:
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
RSPM: ${{ matrix.config.rspm }}
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v1
- uses: r-lib/actions/pr-fetch@master
name: Git Pull (PR)
if: github.event_name == 'pull_request'
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- uses: r-lib/actions/setup-r@master
id: install-r
with:
r-version: ${{ matrix.config.r }}
- uses: r-lib/actions/setup-pandoc@master
- name: Git Config
run: |
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
- name: Install pak and query dependencies
shell: Rscript {0}
run: |
install.packages("pak", repos = "https://r-lib.github.io/p/pak/dev/")
saveRDS(pak::pkg_deps_tree("local::.", dependencies = TRUE), ".github/r-depends.rds")
- name: Cache R packages
uses: actions/cache@v2
with:
path: ${{ env.R_LIBS_USER }}
key: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-${{ hashFiles('.github/r-depends.rds') }}
restore-keys: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-
- name: Install system dependencies
# if: runner.os == 'Linux'
shell: Rscript {0}
run: |
pak::local_system_requirements(execute = TRUE)
- name: Install dependencies
shell: Rscript {0}
run: |
pak::local_install_dev_deps(upgrade = TRUE)
- name: Session info
shell: Rscript {0}
run: |
options(width = 100)
pak::pkg_install("sessioninfo")
pkgs <- installed.packages()[, "Package"]
sessioninfo::session_info(pkgs, include_base = TRUE)
- name: Url redirects
# only perform if in an RC branch (`rc-vX.Y.Z`)
if: ${{ github.event_name == 'push' && contains(github.ref, '/rc-v') }}
run: |
Rscript -e 'pak::pkg_install("r-lib/urlchecker"); urlchecker::url_update()'
# throw an error if man files were updated
if [ -n "$(git status --porcelain man)" ]
then
git status --porcelain
>&2 echo "Updated links found in files above"
>&2 echo 'Run `urlchecker::url_update()` to fix links locally'
exit 1
fi
# Add locally changed urls
git add .
git commit -m 'Update links (GitHub Actions)' || echo "No link changes to commit"
- name: Document
run: |
Rscript -e 'pak::pkg_install("devtools")'
Rscript -e 'devtools::document()'
git add man/\* NAMESPACE
git commit -m 'Document (GitHub Actions)' || echo "No documentation changes to commit"
- name: Check documentation
run: |
./tools/documentation/checkDocsCurrent.sh
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.config.node }}
# https://github.com/actions/cache/blame/ccf96194800dbb7b7094edcd5a7cf3ec3c270f10/examples.md#L185-L200
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: yarn cache
uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ matrix.config.os }}-${{ matrix.config.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ matrix.config.os }}-${{ matrix.config.node }}-yarn-
- name: Sync DESCRIPTION and package.json versions
run: |
Rscript -e 'pak::pkg_install("jsonlite")'
Rscript -e 'pkg <- jsonlite::read_json("package.json", simplifyVector = TRUE)' \
-e 'version <- as.list(read.dcf("DESCRIPTION")[1,])$Version' \
-e 'pkg$version <- gsub("^(\\d+).(\\d+).(\\d+).(.+)$", "\\1.\\2.\\3-alpha.\\4", version)' \
-e 'pkg$files <- as.list(pkg$files)' \
-e 'jsonlite::write_json(pkg, path = "package.json", pretty = TRUE, auto_unbox = TRUE)'
git add package.json && git commit -m 'sync package version (GitHub Actions)' || echo "No version changes to commit"
- name: Build JS
run: |
tree srcts
rm -r srcts/types
yarn install --immutable && yarn build
git add ./srcts/src && git commit -m 'yarn lint (GitHub Actions)' || echo "No yarn lint changes to commit"
git add ./srcts/types && git commit -m 'yarn tsc (GitHub Actions)' || echo "No type definition changes to commit"
git add ./inst && git commit -m 'yarn build (GitHub Actions)' || echo "No yarn build changes to commit"
if [ -n "$(git status --porcelain)" ]
then
git status --porcelain
>&2 echo "The above files changed when we built the JavaScript assets."
exit 1
else
echo "No difference detected; TypeScript build is current."
fi
- name: Git Push (PR)
uses: r-lib/actions/pr-push@master
if: github.event_name == 'pull_request'
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Verify no un-pushed commits (MASTER)
if: github.event_name == 'push'
run: |
# Can't push to a protected branch
if [ -n "`git cherry origin/master`" ]; then
echo "Un-pushed commits:"
git cherry -v origin/master
echo "\nCan not push to a protected branch. Exiting"
exit 1
fi
# Execute after pushing, as no updated files will be produced
- name: Test TypeScript code
run: |
yarn test

2
.gitignore vendored
View File

@@ -12,7 +12,7 @@ README.html
tools/yarn-error.log
# TypeScript / yarn
/node_modules/
node_modules/
.cache
.yarn/*
!.yarn/releases

3
.prettierrc.yml Normal file
View File

@@ -0,0 +1,3 @@
trailingComma: "es5"
arrowParens: always
endOfLine: lf

16
.vscode/settings.json vendored
View File

@@ -1,18 +1,10 @@
{
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"prettier.prettierPath": "./node_modules/prettier",
"typescript.enablePromptUseWorkspaceTsdk": true,
"[r]": {
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
},
"eslint.nodePath": ".yarn/sdks",
"prettier.prettierPath": ".yarn/sdks/prettier/index.js",
"typescript.enablePromptUseWorkspaceTsdk": true
}

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 1.7.1.9003
Version: 1.6.0.9022
Authors@R: c(
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com", comment = c(ORCID = "0000-0002-1576-2126")),
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
@@ -79,7 +79,7 @@ Imports:
jsonlite (>= 0.9.16),
xtable,
fontawesome (>= 0.2.1),
htmltools (>= 0.5.2),
htmltools (>= 0.5.1.9003),
R6 (>= 2.0),
sourcetools,
later (>= 1.0.0),
@@ -91,7 +91,7 @@ Imports:
withr,
commonmark (>= 1.7),
glue (>= 1.3.2),
bslib (>= 0.3.0),
bslib (>= 0.2.5.9002),
cachem,
ellipsis,
lifecycle (>= 0.2.0)
@@ -112,6 +112,10 @@ Suggests:
ragg,
showtext,
sass
Remotes:
r-lib/rlang,
rstudio/bslib,
rstudio/htmltools
URL: https://shiny.rstudio.com/
BugReports: https://github.com/rstudio/shiny/issues
Collate:
@@ -189,7 +193,6 @@ Collate:
'shinywrappers.R'
'showcase.R'
'snapshot.R'
'staticimports.R'
'tar.R'
'test-export.R'
'test-server.R'
@@ -202,7 +205,7 @@ Collate:
'version_selectize.R'
'version_strftime.R'
'viewer.R'
RoxygenNote: 7.1.2
RoxygenNote: 7.1.1
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RdMacros: lifecycle

51
NEWS.md
View File

@@ -1,47 +1,10 @@
shiny development
shiny 1.6.0.9000
================
## Full changelog
### Breaking changes
### Minor new features and improvements
* Shiny's internal HTML dependencies are now mounted dynamically instead of statically. (#3537)
* HTML dependencies that are sent to dynamic UI now have better type checking, and no longer require a `dep.src.href` field. (#3537)
* Default for `ref` input in `runGithub()` changed from `"master"` to `"HEAD"`. (#3346)
* When taking a test snapshot, the sort order of the json keys of the `input`, `output`, and `export` fields is currently sorted using the locale of the machine. This can lead to inconsistent test snapshot results. To opt-in to a consistent ordering of snapshot fields with `{shinytest}`, please set the global option `options(shiny.snapshotsortc = TRUE)`. `{shinytest2}` users do not need to set this value. (#3515)
### Bug fixes
* Closed tidyverse/dplyr#5552: Compatibility of dplyr 1.0 (and rlang chained errors in general) with `req()`, `validate()`, and friends.
* Closed #2955: Input and output bindings previously attempted to use `el['data-input-id']`, but that never worked. They now use `el.getAttribute('data-input-id')` instead. (#3538)
* Closed tidyverse/dplyr#6154: Values from an `actionButton()` had S3 classes in the incorrect order.
* Fixed a bug where updating an input value without a corresponding Input binding element did not trigger a JavaScript `shiny:inputchanged` event. Now, if no Input binding element is found, the `shiny:inputchanged` event is triggered on `window.document`. (#3584)
shiny 1.7.1
===========
## Bug Fixes
* Closed #3516: Fix regression in repeated calls to `appendTab()` when `navbarMenu()` is already present within a `tabsetPanel()`/`navbarPage()`. (#3518)
* Re-arranged conditions for testthat 1.0.0 compatibility. (#3512)
shiny 1.7.0
===========
## Full changelog
### Breaking changes
* The `format` and `locale` arguments to `sliderInput()` have been removed. They have been deprecated since 0.10.2.2 (released on 2014-12-08).
* Closed #3403: `insertTab()`'s `position` parameter now defaults to `"after"` instead of `"before"`. This has the benefit of allowing us to fix a bug in positioning when `target = NULL`, but has the drawback of changing the default behavior when `target` is not `NULL`. (#3404)
@@ -55,7 +18,7 @@ shiny 1.7.0
* All uses of `list(...)` have been replaced with `rlang::list2(...)`. This means that you can use trailing `,` without error and use rlang's `!!!` operator to "splice" a list of argument values into `...`. We think this'll be particularly useful for passing a list of `tabPanel()` to their consumers (i.e., `tabsetPanel()`, `navbarPage()`, etc). For example, `tabs <- list(tabPanel("A", "a"), tabPanel("B", "b")); navbarPage(!!!tabs)`. (#3315 and #3328)
* `installExprFunction()` and `exprToFunction()` are now able to handle quosures when `quoted = TRUE`. So `render`-functions which call these functions (such as with `htmlwidgets`) can now understand quosures. Users can also use `rlang::inject()` to unquote a quosure for evaluation. This also means that `render` function no longer need `env` and `quoted` parameters; that information can be embedded into a quosure which is then passed to the `render` function. Better documentation was added for how to create `render` functions. (#3472)
* `installExprFunction()` and `exprToFunction()` are now able to handle quosures, so `render`-functions which call these functions can now understand quosures, when they are called using `rlang::inject()`. This also means that `render` function no longer need `env` and `quoted` parameters; that information can be embedded into a quosure which is then passed to the `render` function. Additionally, the `getQuosure()` function was added, which makes it easier for developers to create `render` functions which understand quosures. Better documentation was added for how to create `render` functions. (#3462, #3466)
* `icon(lib="fontawesome")` is now powered by the `{fontawesome}` package, which will make it easier to use the latest FA icons in the future (by updating the `{fontawesome}` package). (#3302)
@@ -67,7 +30,7 @@ shiny 1.7.0
### Other improvements
* Shiny's core JavaScript code was converted to TypeScript. For the latest development information, please see the [README.md in `./srcts`](https://github.com/rstudio/shiny/tree/v1.7.0/srcts). (#3296)
* Shiny's core JavaScript code was converted to TypeScript. For the latest development information, please see the [README.md in `./srcts`](https://github.com/rstudio/shiny/tree/master/srcts). (#3296)
* Switched from `digest::digest()` to `rlang::hash()` for hashing. (#3264)
@@ -81,12 +44,10 @@ shiny 1.7.0
* Fixed [#2666](https://github.com/rstudio/shiny/issues/2666) and [#2670](https://github.com/rstudio/shiny/issues/2670): `nearPoints()` and `brushedPoints()` weren't properly account for missing values (#2666 was introduced in v1.4.0). ([#2668](https://github.com/rstudio/shiny/pull/2668))
* Closed #3374: `quoToFunction()` now works correctly with nested quosures; and as a result, quasi-quotation with rendering function (e.g., `renderPrint()`, `renderPlot()`, etc) now works as expected with nested quosures. (#3373)
* Closed #3374: `quoToFunction()` now works correctly with nested quosures; and as a result, quasi-quotation with rendering function (e.g., `renderPrint()`, `renderPlot()`, etc) now works as expected with nested quosures. (#3373)
* Exported `register_devmode_option()`. This method was described in the documentation for `devmode()` but was never exported. See `?devmode()` for more details on how to register Shiny Developer options using `register_devmode_option()`. (#3364)
* Closed #3484: In the RStudio IDE on Mac 11.5, selected checkboxes and radio buttons were not visible. (#3485)
### Library updates
* Closed #3286: Updated to Font-Awesome 5.15.2. (#3288)
@@ -100,7 +61,7 @@ This release focuses on improvements in three main areas:
1. Better theming (and Bootstrap 4) support:
* The `theme` argument of `fluidPage()`, `navbarPage()`, and `bootstrapPage()` all now understand `bslib::bs_theme()` objects, which can be used to opt-into Bootstrap 4, use any Bootswatch theme, and/or implement custom themes without writing any CSS.
* The `session` object now includes `$setCurrentTheme()` and `$getCurrentTheme()` methods to dynamically update (or obtain) the page's `theme` after initial load, which is useful for things such as [adding a dark mode switch to an app](https://rstudio.github.io/bslib/articles/bslib.html#dynamic) or some other "real-time" theming tool like `bslib::bs_themer()`.
* The `session` object now includes `$setCurrentTheme()` and `$getCurrentTheme()` methods to dynamically update (or obtain) the page's `theme` after initial load, which is useful for things such as [adding a dark mode switch to an app](https://rstudio.github.io/bslib/articles/theming.html#dynamic-shiny) or some other "real-time" theming tool like `bslib::bs_themer()`.
* For more details, see [`{bslib}`'s website](https://rstudio.github.io/bslib/)
2. Caching of `reactive()` and `render*()` (e.g. `renderText()`, `renderTable()`, etc) expressions.
@@ -222,7 +183,7 @@ shiny 1.5.0
* The new `moduleServer` function provides a simpler interface for creating and using modules. (#2773)
* Resolved #2732: `markdown()` is a new function for writing Markdown with Github extensions directly in Shiny UIs. Markdown rendering is performed by the [commonmark](https://github.com/r-lib/commonmark) package. (#2737)
* Resolved #2732: `markdown()` is a new function for writing Markdown with Github extensions directly in Shiny UIs. Markdown rendering is performed by the [commonmark](https://github.com/jeroen/commonmark) package. (#2737)
* The `getCurrentOutputInfo()` function can now return the background color (`bg`), foreground color (`fg`), `accent` (i.e., hyperlink) color, and `font` information of the output's HTML container. This information is reported by `plotOutput()`, `imageOutput()`, and any other output bindings containing a class of `.shiny-report-theme`. This feature allows developers to style an output's contents based on the container's CSS styling. (#2740)

View File

@@ -125,14 +125,14 @@ shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
}
if ("shinytest" %in% examples) {
if (!is_installed("shinytest", "1.4.0"))
if (!is_available("shinytest", "1.4.0"))
{
message(
"The tests/shinytest directory needs shinytest 1.4.0 or later to work properly."
)
if (is_installed("shinytest")) {
if (is_available("shinytest")) {
message("You currently have shinytest ",
get_package_version("shinytest"), " installed.")
utils::packageVersion("shinytest"), " installed.")
}
}
@@ -152,7 +152,7 @@ shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
# Helper to resolve paths relative to our template
template_path <- function(...) {
system_file("app_template", ..., package = "shiny")
system.file("app_template", ..., package = "shiny")
}
# Resolve path relative to destination

View File

@@ -255,7 +255,7 @@ utils::globalVariables(".GenericCallEnv", add = TRUE)
#' the cache.
#'
#' You may need to provide a `cacheHint` to [createRenderFunction()] (or
#' `htmlwidgets::shinyRenderWidget()`, if you've authored an htmlwidget) in
#' [htmlwidgets::shinyRenderWidget()], if you've authored an htmlwidget) in
#' order for `bindCache()` to correctly compute a cache key.
#'
#' The potential problem is a cache collision. Consider the following:

View File

@@ -138,7 +138,8 @@ bs_theme_deps <- function(theme) {
}
is_bs_theme <- function(x) {
bslib::is_bs_theme(x)
is_available("bslib", "0.2.0.9000") &&
bslib::is_bs_theme(x)
}
#' Obtain Shiny's Bootstrap Sass theme
@@ -214,10 +215,11 @@ registerThemeDependency <- function(func) {
bootstrapDependency <- function(theme) {
htmlDependency(
"bootstrap",
bootstrapVersion,
src = "www/shared/bootstrap",
package = "shiny",
"bootstrap", bootstrapVersion,
c(
href = "shared/bootstrap",
file = system.file("www/shared/bootstrap", package = "shiny")
),
script = c(
"js/bootstrap.min.js",
# Safely adding accessibility plugin for screen readers and keyboard users; no break for sighted aspects (see https://github.com/paypal/bootstrap-accessibility-plugin)
@@ -1107,17 +1109,11 @@ tableOutput <- function(outputId) {
dataTableDependency <- list(
htmlDependency(
"datatables",
"1.10.5",
src = "www/shared/datatables",
package = "shiny",
"datatables", "1.10.5", c(href = "shared/datatables"),
script = "js/jquery.dataTables.min.js"
),
htmlDependency(
"datatables-bootstrap",
"1.10.5",
src = "www/shared/datatables",
package = "shiny",
"datatables-bootstrap", "1.10.5", c(href = "shared/datatables"),
stylesheet = c("css/dataTables.bootstrap.css", "css/dataTables.extra.css"),
script = "js/dataTables.bootstrap.js"
)
@@ -1157,7 +1153,7 @@ dataTableOutput <- function(outputId) {
htmlOutput <- function(outputId, inline = FALSE,
container = if (inline) span else div, ...)
{
if (any_unnamed(list(...))) {
if (anyUnnamed(list(...))) {
warning("Unnamed elements in ... will be replaced with dynamic UI.")
}
container(id = outputId, class="shiny-html-output", ...)

View File

@@ -228,9 +228,7 @@ withLogErrors <- function(expr,
if (promises::is.promise(result)) {
result <- promises::catch(result, function(cond) {
# Don't print shiny.silent.error (i.e. validation errors)
if (cnd_inherits(cond, "shiny.silent.error")) {
return()
}
if (inherits(cond, "shiny.silent.error")) return()
if (isTRUE(getOption("show.error.messages"))) {
printError(cond, full = full, offset = offset)
}
@@ -241,7 +239,7 @@ withLogErrors <- function(expr,
},
error = function(cond) {
# Don't print shiny.silent.error (i.e. validation errors)
if (cnd_inherits(cond, "shiny.silent.error")) return()
if (inherits(cond, "shiny.silent.error")) return()
if (isTRUE(getOption("show.error.messages"))) {
printError(cond, full = full, offset = offset)
}

View File

@@ -1,6 +1,55 @@
# A scope where we can put mutable global state
.globals <- new.env(parent = emptyenv())
register_s3_method <- function(pkg, generic, class, fun = NULL) {
stopifnot(is.character(pkg), length(pkg) == 1)
stopifnot(is.character(generic), length(generic) == 1)
stopifnot(is.character(class), length(class) == 1)
if (is.null(fun)) {
fun <- get(paste0(generic, ".", class), envir = parent.frame())
} else {
stopifnot(is.function(fun))
}
if (pkg %in% loadedNamespaces()) {
registerS3method(generic, class, fun, envir = asNamespace(pkg))
}
# Always register hook in case pkg is loaded at some
# point the future (or, potentially, but less commonly,
# unloaded & reloaded)
setHook(
packageEvent(pkg, "onLoad"),
function(...) {
registerS3method(generic, class, fun, envir = asNamespace(pkg))
}
)
}
register_upgrade_message <- function(pkg, version) {
msg <- sprintf(
"This version of Shiny is designed to work with '%s' >= %s.
Please upgrade via install.packages('%s').",
pkg, version, pkg
)
if (pkg %in% loadedNamespaces() && !is_available(pkg, version)) {
packageStartupMessage(msg)
}
# Always register hook in case pkg is loaded at some
# point the future (or, potentially, but less commonly,
# unloaded & reloaded)
setHook(
packageEvent(pkg, "onLoad"),
function(...) {
if (!is_available(pkg, version)) packageStartupMessage(msg)
}
)
}
.onLoad <- function(libname, pkgname) {
# R's lazy-loading package scheme causes the private seed to be cached in the
# package itself, making our PRNG completely deterministic. This line resets
@@ -13,9 +62,9 @@
# Make sure these methods are available to knitr if shiny is loaded but not
# attached.
s3_register("knitr::knit_print", "reactive")
s3_register("knitr::knit_print", "shiny.appobj")
s3_register("knitr::knit_print", "shiny.render.function")
register_s3_method("knitr", "knit_print", "reactive")
register_s3_method("knitr", "knit_print", "shiny.appobj")
register_s3_method("knitr", "knit_print", "shiny.render.function")
# Shiny 1.4.0 bumps jQuery 1.x to 3.x, which caused a problem
# with static-rendering of htmlwidgets, and htmlwidgets 1.5

View File

@@ -4,7 +4,7 @@
# @param version The version of the package
check_suggested <- function(package, version = NULL) {
if (is_installed(package, version)) {
if (is_available(package, version)) {
return()
}
@@ -115,28 +115,22 @@ check_reactlog <- function() {
}
# read reactlog version from description file
# prevents version mismatch in code and description file
reactlog_version <- local({
version <- NULL
function() {
if (!is.null(version)) return(version)
reactlog_version <- function() {
desc <- read.dcf(system.file("DESCRIPTION", package = "shiny", mustWork = TRUE))
suggests <- desc[1,"Suggests"][[1]]
suggests_pkgs <- strsplit(suggests, "\n")[[1]]
desc <- read.dcf(system_file("DESCRIPTION", package = "shiny"))
suggests <- desc[1,"Suggests"][[1]]
suggests_pkgs <- strsplit(suggests, "\n")[[1]]
reactlog_info <- suggests_pkgs[grepl("reactlog", suggests_pkgs)]
if (length(reactlog_info) == 0) {
stop("reactlog can not be found in shiny DESCRIPTION file")
}
reactlog_info <- sub("^[^\\(]*\\(", "", reactlog_info)
reactlog_info <- sub("\\)[^\\)]*$", "", reactlog_info)
reactlog_info <- sub("^[>= ]*", "", reactlog_info)
version <<- package_version(reactlog_info)
version
reactlog_info <- suggests_pkgs[grepl("reactlog", suggests_pkgs)]
if (length(reactlog_info) == 0) {
stop("reactlog can not be found in shiny DESCRIPTION file")
}
})
reactlog_info <- sub("^[^\\(]*\\(", "", reactlog_info)
reactlog_info <- sub("\\)[^\\)]*$", "", reactlog_info)
reactlog_info <- sub("^[>= ]*", "", reactlog_info)
package_version(reactlog_info)
}
RLog <- R6Class(

View File

@@ -4,12 +4,12 @@ startPNG <- function(filename, width, height, res, ...) {
# to use ragg (say, instead of showtext, for custom font rendering).
# In the next shiny release, this option will likely be superseded in
# favor of a fully customizable graphics device option
if ((getOption('shiny.useragg') %||% FALSE) && is_installed("ragg")) {
if ((getOption('shiny.useragg') %||% FALSE) && is_available("ragg")) {
pngfun <- ragg::agg_png
} else if (capabilities("aqua")) {
# i.e., png(type = 'quartz')
pngfun <- grDevices::png
} else if ((getOption('shiny.usecairo') %||% TRUE) && is_installed("Cairo")) {
} else if ((getOption('shiny.usecairo') %||% TRUE) && is_available("Cairo")) {
pngfun <- Cairo::CairoPNG
} else {
# i.e., png(type = 'cairo')

View File

@@ -138,8 +138,7 @@ datePickerDependency <- function(theme) {
htmlDependency(
name = "bootstrap-datepicker-js",
version = version_bs_date_picker,
src = "www/shared/datepicker",
package = "shiny",
src = c(href = "shared/datepicker"),
script = if (getOption("shiny.minified", TRUE)) "js/bootstrap-datepicker.min.js"
else "js/bootstrap-datepicker.js",
# Need to enable noConflict mode. See #1346.
@@ -158,19 +157,18 @@ datePickerCSS <- function(theme) {
return(htmlDependency(
name = "bootstrap-datepicker-css",
version = version_bs_date_picker,
src = "www/shared/datepicker",
package = "shiny",
src = c(href = "shared/datepicker"),
stylesheet = "css/bootstrap-datepicker3.min.css"
))
}
scss_file <- system_file(package = "shiny", "www/shared/datepicker/scss/build3.scss")
scss_file <- system.file(package = "shiny", "www/shared/datepicker/scss/build3.scss")
bslib::bs_dependency(
input = sass::sass_file(scss_file),
theme = theme,
name = "bootstrap-datepicker",
version = version_bs_date_picker,
cache_key_extra = get_package_version("shiny")
cache_key_extra = shinyPackageVersion()
)
}

View File

@@ -213,7 +213,14 @@ selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
deps <- list(selectizeDependency())
if ('drag_drop' %in% options$plugins) {
deps[[length(deps) + 1]] <- jqueryuiDependency()
deps <- c(
deps,
list(htmlDependency(
'jqueryui', '1.12.1',
c(href = 'shared/jqueryui'),
script = 'jquery-ui.min.js'
))
)
}
# Insert script on same level as <select> tag
@@ -240,7 +247,7 @@ selectizeDependencyFunc <- function(theme) {
return(selectizeStaticDependency(version_selectize))
}
selectizeDir <- system_file(package = "shiny", "www/shared/selectize/")
selectizeDir <- system.file(package = "shiny", "www/shared/selectize/")
bs_version <- bslib::theme_version(theme)
stylesheet <- file.path(
selectizeDir, "scss", paste0("selectize.bootstrap", bs_version, ".scss")
@@ -260,17 +267,15 @@ selectizeDependencyFunc <- function(theme) {
theme = theme,
name = "selectize",
version = version_selectize,
cache_key_extra = get_package_version("shiny"),
cache_key_extra = shinyPackageVersion(),
.dep_args = list(script = script)
)
}
selectizeStaticDependency <- function(version) {
htmlDependency(
"selectize",
version,
src = "www/shared/selectize",
package = "shiny",
"selectize", version,
src = c(href = "shared/selectize"),
stylesheet = "css/selectize.bootstrap3.css",
script = c(
"js/selectize.min.js",

View File

@@ -205,17 +205,13 @@ ionRangeSliderDependency <- function() {
list(
# ion.rangeSlider also needs normalize.css, which is already included in Bootstrap.
htmlDependency(
"ionrangeslider-javascript",
version_ion_range_slider,
src = "www/shared/ionrangeslider",
package = "shiny",
"ionrangeslider-javascript", version_ion_range_slider,
src = c(href = "shared/ionrangeslider"),
script = "js/ion.rangeSlider.min.js"
),
htmlDependency(
"strftime",
version_strftime,
src = "www/shared/strftime",
package = "shiny",
"strftime", version_strftime,
src = c(href = "shared/strftime"),
script = "strftime-min.js"
),
bslib::bs_dependency_defer(ionRangeSliderDependencyCSS)
@@ -227,8 +223,7 @@ ionRangeSliderDependencyCSS <- function(theme) {
return(htmlDependency(
"ionrangeslider-css",
version_ion_range_slider,
src = "www/shared/ionrangeslider",
package = "shiny",
src = c(href = "shared/ionrangeslider"),
stylesheet = "css/ion.rangeSlider.css"
))
}
@@ -237,13 +232,13 @@ ionRangeSliderDependencyCSS <- function(theme) {
input = list(
list(accent = "$component-active-bg"),
sass::sass_file(
system_file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
system.file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
)
),
theme = theme,
name = "ionRangeSlider",
version = version_ion_range_slider,
cache_key_extra = get_package_version("shiny")
cache_key_extra = shinyPackageVersion()
)
}

View File

@@ -41,7 +41,7 @@ normalizeChoicesArgs <- function(choices, choiceNames, choiceValues,
if (length(choiceNames) != length(choiceValues)) {
stop("`choiceNames` and `choiceValues` must have the same length.")
}
if (any_named(choiceNames) || any_named(choiceValues)) {
if (anyNamed(choiceNames) || anyNamed(choiceValues)) {
stop("`choiceNames` and `choiceValues` must not be named.")
}
} else {

View File

@@ -79,8 +79,8 @@ absolutePanel <- function(...,
if (isTRUE(draggable)) {
divTag <- tagAppendAttributes(divTag, class='draggable')
return(tagList(
singleton(tags$head(tags$script(src='shared/jqueryui/jquery-ui.min.js'))),
divTag,
jqueryuiDependency(),
tags$script('$(".draggable").draggable();')
))
} else {
@@ -99,14 +99,3 @@ fixedPanel <- function(...,
width=width, height=height, draggable=draggable, cursor=match.arg(cursor),
fixed=TRUE)
}
jqueryuiDependency <- function() {
htmlDependency(
'jqueryui',
'1.12.1',
src = 'www/shared/jqueryui',
package = 'shiny',
script = 'jquery-ui.min.js'
)
}

View File

@@ -63,7 +63,7 @@ knit_print.shiny.appobj <- function(x, ...) {
#' @param inline Whether the object is printed inline.
knit_print.shiny.render.function <- function(x, ..., inline = FALSE) {
x <- htmltools::as.tags(x, inline = inline)
output <- knitr::knit_print(tagList(x), ..., inline = inline)
output <- knitr::knit_print(tagList(x))
attr(output, "knit_cacheable") <- FALSE
attr(output, "knit_meta") <- append(attr(output, "knit_meta"),
shiny_rmd_warning())
@@ -77,5 +77,5 @@ knit_print.reactive <- function(x, ..., inline = FALSE) {
renderFunc <- if (inline) renderText else renderPrint
knitr::knit_print(renderFunc({
x()
}), ..., inline = inline)
}), inline = inline)
}

View File

@@ -348,7 +348,7 @@ HandlerManager <- R6Class("HandlerManager",
httpResponse(status = 500L,
content_type = "text/html; charset=UTF-8",
content = as.character(htmltools::htmlTemplate(
system_file("template", "error.html", package = "shiny"),
system.file("template", "error.html", package = "shiny"),
message = conditionMessage(err)
))
)

View File

@@ -1,5 +1,5 @@
# Promise helpers taken from:
# https://github.com/rstudio/promises/blob/main/tests/testthat/common.R
# https://github.com/rstudio/promises/blob/master/tests/testthat/common.R
# Block until all pending later tasks have executed
wait_for_it <- function() {
while (!later::loop_empty()) {

View File

@@ -1203,7 +1203,7 @@ Observer <- R6Class(
# validation = function(e) NULL,
# shiny.output.cancel = function(e) NULL
if (cnd_inherits(e, "shiny.silent.error")) {
if (inherits(e, "shiny.silent.error")) {
return()
}

View File

@@ -1,6 +1,6 @@
####
# Generated by `./tools/documentation/updateReexports.R`: do not edit by hand
# Please call `source('tools/documentation/updateReexports.R')` from the root folder to update`
# Generated by `./tools/updateReexports.R`: do not edit by hand
# Please call `source('tools/updateReexports.R') from the root folder to update`
####
@@ -90,20 +90,17 @@ htmltools::em
#' @export
htmltools::hr
# htmltools tag.Rd -------------------------------------------------------------
#' @importFrom htmltools tag
#' @export
htmltools::tag
# htmltools tagList.Rd ---------------------------------------------------------
#' @importFrom htmltools tagList
#' @export
htmltools::tagList
# htmltools tagAppendAttributes.Rd ---------------------------------------------
#' @importFrom htmltools tagAppendAttributes
#' @export
htmltools::tagAppendAttributes
@@ -116,9 +113,6 @@ htmltools::tagHasAttribute
#' @export
htmltools::tagGetAttribute
# htmltools tagAppendChild.Rd --------------------------------------------------
#' @importFrom htmltools tagAppendChild
#' @export
htmltools::tagAppendChild

View File

@@ -181,7 +181,7 @@
#' # At the top of app.R, this set the application-scoped cache to be a disk
#' # cache that can be shared among multiple concurrent R processes, and is
#' # deleted when the system reboots.
#' shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache")))
#' shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache"))
#'
#' # At the top of app.R, this set the application-scoped cache to be a disk
#' # cache that can be shared among multiple concurrent R processes, and

View File

@@ -612,7 +612,7 @@ getGgplotCoordmap <- function(p, width, height, res) {
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 <- get_package_version("ggplot2")
ggplot_ver <- utils::packageVersion("ggplot2")
if (ggplot_ver > "2.2.1") {
find_panel_info_api(b)

View File

@@ -23,10 +23,10 @@
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' runUrl('https://github.com/rstudio/shiny_example/archive/main.tar.gz')
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
#'
#' # Can run an app from a subdirectory in the archive
#' runUrl("https://github.com/rstudio/shiny_example/archive/main.zip",
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
#' subdir = "inst/shinyapp/")
#' }
runUrl <- function(url, filetype = NULL, subdir = NULL, destdir = NULL, ...) {
@@ -121,8 +121,7 @@ runGist <- function(gist, destdir = NULL, ...) {
#' @param username GitHub username. If `repo` is of the form
#' `"username/repo"`, `username` will be taken from `repo`.
#' @param ref Desired git reference. Could be a commit, tag, or branch name.
#' Defaults to `"HEAD"`, which means the default branch on GitHub, typically
#' `"main"` or `"master"`.
#' Defaults to `"master"`.
#' @export
#' @examples
#' ## Only run this example in interactive R sessions
@@ -134,7 +133,7 @@ runGist <- function(gist, destdir = NULL, ...) {
#' runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
#' }
runGitHub <- function(repo, username = getOption("github.user"),
ref = "HEAD", subdir = NULL, destdir = NULL, ...) {
ref = "master", subdir = NULL, destdir = NULL, ...) {
if (grepl('/', repo)) {
res <- strsplit(repo, '/')[[1]]

View File

@@ -28,7 +28,7 @@
#' ports will be tried.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only. The value of this parameter can also be a
#' interactive sessions only. This value of this parameter can also be a
#' function to call with the application's URL.
#' @param host The IPv4 address that the application should listen on. Defaults
#' to the `shiny.host` option, if set, or `"127.0.0.1"` if not. See
@@ -467,7 +467,7 @@ runExample <- function(example=NA,
launch.browser = getOption('shiny.launch.browser', interactive()),
host=getOption('shiny.host', '127.0.0.1'),
display.mode=c("auto", "normal", "showcase")) {
examplesDir <- system_file('examples', package='shiny')
examplesDir <- system.file('examples', package='shiny')
dir <- resolve(examplesDir, example)
if (is.null(dir)) {
if (is.na(example)) {

View File

@@ -41,9 +41,9 @@ inputHandlers <- Map$new()
#' })
#'
#' ## On the Javascript side, the associated input binding must have a corresponding getType method:
#' # getType: function(el) {
#' # return "mypackage.validint";
#' # }
#' getType: function(el) {
#' return "mypackage.validint";
#' }
#'
#' }
#' @seealso [removeInputHandler()] [applyInputHandlers()]
@@ -181,7 +181,7 @@ registerInputHandler("shiny.datetime", function(val, ...){
registerInputHandler("shiny.action", function(val, shinysession, name) {
# mark up the action button value with a special class so we can recognize it later
class(val) <- c("shinyActionButtonValue", class(val))
class(val) <- c(class(val), "shinyActionButtonValue")
val
})

View File

@@ -128,7 +128,7 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
appvars <- new.env()
appvars$server <- NULL
sys.www.root <- system_file('www', package='shiny')
sys.www.root <- system.file('www', package='shiny')
# This value, if non-NULL, must be present on all HTTP and WebSocket
# requests as the Shiny-Shared-Secret header or else access will be
@@ -385,7 +385,7 @@ startApp <- function(appObj, port, host, quiet) {
list(
# Always handle /session URLs dynamically, even if / is a static path.
"session" = excludeStaticPath(),
"shared" = system_file(package = "shiny", "www", "shared")
"shared" = system.file(package = "shiny", "www", "shared")
),
.globals$resourcePaths
)

View File

@@ -125,9 +125,6 @@ getShinyOption <- function(name, default = NULL) {
#' console.}
#' \item{shiny.testmode (defaults to `FALSE`)}{If `TRUE`, then various features for testing Shiny
#' applications are enabled.}
#' \item{shiny.snapshotsortc (defaults to `FALSE`)}{If `TRUE`, test snapshot keys
#' for \pkg{shinytest} will be sorted consistently using the C locale. Snapshots
#' retrieved by \pkg{shinytest2} will always sort using the C locale.}
#' \item{shiny.trace (defaults to `FALSE`)}{Print messages sent between the R server and the web
#' browser client to the R console. This is useful for debugging. Possible
#' values are `"send"` (only print messages sent to the client),

View File

@@ -403,7 +403,7 @@ ShinySession <- R6Class(
sendMessage = function(...) {
# This function is a wrapper for $write
msg <- list(...)
if (any_unnamed(msg)) {
if (anyUnnamed(msg)) {
stop("All arguments to sendMessage must be named.")
}
private$write(toJSON(msg))
@@ -478,35 +478,6 @@ ShinySession <- R6Class(
# "json" unless requested otherwise. The only other valid value is
# "rds".
format <- params$format %||% "json"
# Machines can test their snapshot under different locales.
# R CMD check runs under the `C` locale.
# However, before this parameter, existing snapshots were most likely not
# under the `C` locale is would cause failures. This parameter allows
# users to opt-in to the `C` locale.
# From ?sort:
# However, there are some caveats with the radix sort:
# If x is a character vector, all elements must share the
# same encoding. Only UTF-8 (including ASCII) and Latin-1
# encodings are supported. Collation always follows the "C"
# locale.
# {shinytest2} will always set `sortC=1`
# {shinytest} does not have `sortC` functionality.
# Users should set `options(shiny.snapshotsortc = TRUE)` within their app.
# The sortingMethod should always be `radix` going forward.
sortMethod <-
if (!is.null(params$sortC)) {
if (params$sortC != "1") {
stop("The `sortC` parameter can only be `1` or not supplied")
}
"radix"
} else {
# Allow users to set an option for {shinytest2}.
if (isTRUE(getShinyOption("snapshotsortc", default = FALSE))) {
"radix"
} else {
"auto"
}
}
values <- list()
@@ -549,7 +520,7 @@ ShinySession <- R6Class(
}
)
values$input <- sortByName(values$input, method = sortMethod)
values$input <- sortByName(values$input)
}
if (!is.null(params$output)) {
@@ -577,7 +548,7 @@ ShinySession <- R6Class(
}
)
values$output <- sortByName(values$output, method = sortMethod)
values$output <- sortByName(values$output)
}
if (!is.null(params$export)) {
@@ -598,7 +569,7 @@ ShinySession <- R6Class(
)
}
values$export <- sortByName(values$export, method = sortMethod)
values$export <- sortByName(values$export)
}
# Make sure input, output, and export are all named lists (at this
@@ -854,7 +825,7 @@ ShinySession <- R6Class(
dots <- eval(substitute(alist(...)))
}
if (any_unnamed(dots))
if (anyUnnamed(dots))
stop("exportTestValues: all arguments must be named.")
names(dots) <- ns(names(dots))
@@ -942,7 +913,7 @@ ShinySession <- R6Class(
# Copy `values` from scopeState to state, adding namespace
if (length(scopeState$values) != 0) {
if (any_unnamed(scopeState$values)) {
if (anyUnnamed(scopeState$values)) {
stop("All scope values in must be named.")
}
@@ -1143,12 +1114,7 @@ ShinySession <- R6Class(
structure(list(), class = "try-error", condition = cond)
} else if (inherits(cond, "shiny.output.cancel")) {
structure(list(), class = "cancel-output")
} else if (cnd_inherits(cond, "shiny.silent.error")) {
# The error condition might have been chained by
# foreign code, e.g. dplyr. Find the original error.
while (!inherits(cond, "shiny.silent.error")) {
cond <- cond$parent
}
} else if (inherits(cond, "shiny.silent.error")) {
# Don't let shiny.silent.error go through the normal stop
# path of try, because we don't want it to print. But we
# do want to try to return the same looking result so that
@@ -1735,7 +1701,7 @@ ShinySession <- R6Class(
dots <- eval(substitute(alist(...)))
}
if (any_unnamed(dots))
if (anyUnnamed(dots))
stop("exportTestValues: all arguments must be named.")
# Create a named list where each item is a list with an expression and
@@ -1748,7 +1714,7 @@ ShinySession <- R6Class(
},
getTestSnapshotUrl = function(input = TRUE, output = TRUE, export = TRUE,
format = "json", sortC = FALSE) {
format = "json") {
reqString <- function(group, value) {
if (isTRUE(value))
paste0(group, "=1")
@@ -1762,7 +1728,6 @@ ShinySession <- R6Class(
reqString("input", input),
reqString("output", output),
reqString("export", export),
reqString("sortC", sortC),
paste0("format=", format),
sep = "&"
)

View File

@@ -193,7 +193,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
staticPaths <- list()
}
fallbackWWWDir <- system_file("www-dir", package = "shiny")
fallbackWWWDir <- system.file("www-dir", package = "shiny")
serverSource <- cachedFuncWithFile(appDir, "server.R", case.sensitive = FALSE,
function(serverR) {
@@ -455,7 +455,7 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
staticPaths <- list()
}
fallbackWWWDir <- system_file("www-dir", package = "shiny")
fallbackWWWDir <- system.file("www-dir", package = "shiny")
oldwd <- NULL
monitorHandle <- NULL

View File

@@ -39,7 +39,7 @@ renderPage <- function(ui, showcase=0, testMode=FALSE) {
# Put the body into the default template
ui <- htmlTemplate(
system_file("template", "default.html", package = "shiny"),
system.file("template", "default.html", package = "shiny"),
lang = lang,
body = ui,
# this template is a complete HTML document
@@ -55,14 +55,8 @@ renderPage <- function(ui, showcase=0, testMode=FALSE) {
if (testMode) {
# Add code injection listener if in test mode
shiny_deps[[length(shiny_deps) + 1]] <-
htmlDependency(
"shiny-testmode",
get_package_version("shiny"),
src = "www/shared",
package = "shiny",
script = "shiny-testmode.js",
all_files = FALSE
)
htmlDependency("shiny-testmode", shinyPackageVersion(),
c(href="shared"), script = "shiny-testmode.js")
}
html <- renderDocument(ui, shiny_deps, processDep = createWebDependency)
@@ -74,19 +68,23 @@ jqueryDependency <- function() {
if (version == 3) {
return(htmlDependency(
"jquery", version_jquery,
src = "www/shared",
src = c(
href = "shared",
file = "www/shared"
),
package = "shiny",
script = "jquery.min.js",
all_files = FALSE
script = "jquery.min.js"
))
}
if (version == 1) {
return(htmlDependency(
"jquery", "1.12.4",
src = "www/shared/legacy",
src = c(
href = "shared/legacy",
file = "www/shared/legacy"
),
package = "shiny",
script = "jquery.min.js",
all_files = FALSE
script = "jquery.min.js"
))
}
stop("Unsupported version of jQuery: ", version)
@@ -97,9 +95,8 @@ shinyDependencies <- function() {
bslib::bs_dependency_defer(shinyDependencyCSS),
htmlDependency(
name = "shiny-javascript",
version = get_package_version("shiny"),
src = "www/shared",
package = "shiny",
version = shinyPackageVersion(),
src = c(href = "shared"),
script =
if (isTRUE(
get_devmode_option(
@@ -109,27 +106,24 @@ shinyDependencies <- function() {
))
"shiny.min.js"
else
"shiny.js",
all_files = FALSE
"shiny.js"
)
)
}
shinyDependencyCSS <- function(theme) {
version <- get_package_version("shiny")
version <- shinyPackageVersion()
if (!is_bs_theme(theme)) {
return(htmlDependency(
name = "shiny-css",
version = version,
src = "www/shared",
package = "shiny",
stylesheet = "shiny.min.css",
all_files = FALSE
src = c(href = "shared"),
stylesheet = "shiny.min.css"
))
}
scss_home <- system_file("www/shared/shiny_scss", package = "shiny")
scss_home <- system.file("www/shared/shiny_scss", package = "shiny")
scss_files <- file.path(scss_home, c("bootstrap.scss", "shiny.scss"))
scss_files <- lapply(scss_files, sass::sass_file)

View File

@@ -822,9 +822,7 @@ downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()
#' Table output with the JavaScript DataTables library
#'
#' @description
#' `r lifecycle::badge("superseded")` Please use
#' \href{https://rstudio.github.io/DT/shiny.html}{\code{DT::renderDataTable()}}.
#' (Shiny 0.11.1)
#' `r lifecycle::badge("superseded")` Please use [`DT::renderDataTable()`]. (Shiny 0.11.1)
#'
#' Makes a reactive version of the given function that returns a data frame (or
#' matrix), which will be rendered with the [DataTables](https://datatables.net)
@@ -953,7 +951,7 @@ renderDataTable <- function(expr, options = NULL, searchDelay = 500,
DT10Names <- function() {
rbind(
utils::read.table(
system_file('www/shared/datatables/upgrade1.10.txt', package = 'shiny'),
system.file('www/shared/datatables/upgrade1.10.txt', package = 'shiny'),
stringsAsFactors = FALSE
),
c('aoColumns', 'Removed') # looks like an omission on the upgrade guide

View File

@@ -32,40 +32,26 @@ licenseLink <- function(licenseName) {
showcaseHead <- function() {
deps <- list(
jqueryuiDependency(),
htmlDependency(
"showdown",
"0.3.1",
src = "www/shared/showdown/compressed",
package="shiny",
script = "showdown.js"
),
htmlDependency(
"highlight.js",
"6.2",
src = "www/shared/highlight",
package="shiny",
script = "highlight.pack.js",
stylesheet = "rstudio.css"
),
htmlDependency(
"showcase",
"0.1.0",
src = "www/shared",
package = "shiny",
script = "shiny-showcase.js",
stylesheet = "shiny-showcase.css",
all_files = FALSE
)
htmlDependency("jqueryui", "1.12.1", c(href="shared/jqueryui"),
script = "jquery-ui.min.js"),
htmlDependency("showdown", "0.3.1", c(href="shared/showdown/compressed"),
script = "showdown.js"),
htmlDependency("highlight.js", "6.2", c(href="shared/highlight"),
script = "highlight.pack.js")
)
mdfile <- file.path.ci(getwd(), 'Readme.md')
html <- tagList(
html <- with(tags, tagList(
script(src="shared/shiny-showcase.js"),
link(rel="stylesheet", type="text/css",
href="shared/highlight/rstudio.css"),
link(rel="stylesheet", type="text/css",
href="shared/shiny-showcase.css"),
if (file.exists(mdfile))
tags$script(type="text/markdown", id="showcase-markdown-content",
script(type="text/markdown", id="showcase-markdown-content",
paste(readUTF8(mdfile), collapse="\n"))
else ""
)
))
return(attachDependencies(html, deps))
}

View File

@@ -1,216 +0,0 @@
# Generated by staticimports; do not edit by hand.
# ======================================================================
# Imported from pkg:staticimports
# ======================================================================
# Given a vector, return TRUE if any elements are named, FALSE otherwise.
# For zero-length vectors, always return FALSE.
any_named <- function(x) {
if (length(x) == 0) return(FALSE)
nms <- names(x)
!is.null(nms) && any(nzchar(nms))
}
# Given a vector, return TRUE if any elements are unnamed, FALSE otherwise.
# For zero-length vectors, always return FALSE.
any_unnamed <- function(x) {
if (length(x) == 0) return(FALSE)
nms <- names(x)
is.null(nms) || !all(nzchar(nms))
}
# Borrowed from pkgload:::dev_meta, with some modifications.
# Returns TRUE if `pkg` was loaded with `devtools::load_all()`.
devtools_loaded <- function(pkg) {
ns <- .getNamespace(pkg)
if (is.null(ns) || is.null(ns$.__DEVTOOLS__)) {
return(FALSE)
}
TRUE
}
get_package_version <- function(pkg) {
# `utils::packageVersion()` can be slow, so first try the fast path of
# checking if the package is already loaded.
ns <- .getNamespace(pkg)
if (is.null(ns)) {
utils::packageVersion(pkg)
} else {
as.package_version(ns$.__NAMESPACE__.$spec[["version"]])
}
}
is_installed <- function(pkg, version = NULL) {
installed <- isNamespaceLoaded(pkg) || nzchar(system_file_cached(package = pkg))
if (is.null(version)) {
return(installed)
}
installed && isTRUE(get_package_version(pkg) >= version)
}
register_upgrade_message <- function(pkg, version, error = FALSE) {
msg <- sprintf(
"This version of '%s' is designed to work with '%s' >= %s.
Please upgrade via install.packages('%s').",
environmentName(environment(register_upgrade_message)),
pkg, version, pkg
)
cond <- if (error) stop else packageStartupMessage
if (pkg %in% loadedNamespaces() && !is_installed(pkg, version)) {
cond(msg)
}
# Always register hook in case pkg is loaded at some
# point the future (or, potentially, but less commonly,
# unloaded & reloaded)
setHook(
packageEvent(pkg, "onLoad"),
function(...) {
if (!is_installed(pkg, version)) cond(msg)
}
)
}
# Simplified version rlang:::s3_register() that just uses
# warning() instead of rlang::warn() when registration fails
# https://github.com/r-lib/rlang/blob/main/R/compat-s3-register.R
s3_register <- function(generic, class, method = NULL) {
stopifnot(is.character(generic), length(generic) == 1)
stopifnot(is.character(class), length(class) == 1)
pieces <- strsplit(generic, "::")[[1]]
stopifnot(length(pieces) == 2)
package <- pieces[[1]]
generic <- pieces[[2]]
caller <- parent.frame()
get_method_env <- function() {
top <- topenv(caller)
if (isNamespace(top)) {
asNamespace(environmentName(top))
} else {
caller
}
}
get_method <- function(method, env) {
if (is.null(method)) {
get(paste0(generic, ".", class), envir = get_method_env())
} else {
method
}
}
register <- function(...) {
envir <- asNamespace(package)
# Refresh the method each time, it might have been updated by
# `devtools::load_all()`
method_fn <- get_method(method)
stopifnot(is.function(method_fn))
# Only register if generic can be accessed
if (exists(generic, envir)) {
registerS3method(generic, class, method_fn, envir = envir)
} else {
warning(
"Can't find generic `", generic, "` in package ", package,
" register S3 method. Do you need to update ", package,
" to the latest version?", call. = FALSE
)
}
}
# Always register hook in case package is later unloaded & reloaded
setHook(packageEvent(package, "onLoad"), function(...) {
register()
})
# Avoid registration failures during loading (pkgload or regular).
# Check that environment is locked because the registering package
# might be a dependency of the package that exports the generic. In
# that case, the exports (and the generic) might not be populated
# yet (#1225).
if (isNamespaceLoaded(package) && environmentIsLocked(asNamespace(package))) {
register()
}
invisible()
}
# Borrowed from pkgload::shim_system.file, with some modifications. This behaves
# like `system.file()`, except that (1) for packages loaded with
# `devtools::load_all()`, it will return the path to files in the package's
# inst/ directory, and (2) for other packages, the directory lookup is cached.
# Also, to keep the implementation simple, it doesn't support specification of
# lib.loc or mustWork.
system_file <- function(..., package = "base") {
if (!devtools_loaded(package)) {
return(system_file_cached(..., package = package))
}
if (!is.null(names(list(...)))) {
stop("All arguments other than `package` must be unnamed.")
}
# If package was loaded with devtools (the package loaded with load_all),
# also search for files under inst/, and don't cache the results (it seems
# more likely that the package path will change during the development
# process)
pkg_path <- find.package(package)
# First look in inst/
files_inst <- file.path(pkg_path, "inst", ...)
present_inst <- file.exists(files_inst)
# For any files that weren't present in inst/, look in the base path
files_top <- file.path(pkg_path, ...)
present_top <- file.exists(files_top)
# Merge them together. Here are the different possible conditions, and the
# desired result. NULL means to drop that element from the result.
#
# files_inst: /inst/A /inst/B /inst/C /inst/D
# present_inst: T T F F
# files_top: /A /B /C /D
# present_top: T F T F
# result: /inst/A /inst/B /C NULL
#
files <- files_top
files[present_inst] <- files_inst[present_inst]
# Drop cases where not present in either location
files <- files[present_inst | present_top]
if (length(files) == 0) {
return("")
}
# Make sure backslashes are replaced with slashes on Windows
normalizePath(files, winslash = "/")
}
# A wrapper for `system.file()`, which caches the results, because
# `system.file()` can be slow. Note that because of caching, if
# `system_file_cached()` is called on a package that isn't installed, then the
# package is installed, and then `system_file_cached()` is called again, it will
# still return "".
system_file_cached <- local({
pkg_dir_cache <- character()
function(..., package = "base") {
if (!is.null(names(list(...)))) {
stop("All arguments other than `package` must be unnamed.")
}
not_cached <- is.na(match(package, names(pkg_dir_cache)))
if (not_cached) {
pkg_dir <- system.file(package = package)
pkg_dir_cache[[package]] <<- pkg_dir
} else {
pkg_dir <- pkg_dir_cache[[package]]
}
file.path(pkg_dir, ...)
}
})

View File

@@ -2,11 +2,6 @@
#' @include map.R
NULL
# @staticimports pkg:staticimports
# is_installed get_package_version system_file
# s3_register register_upgrade_message
# any_named any_unnamed
#' Make a random number generator repeatable
#'
#' Given a function that generates random data, returns a wrapped version of
@@ -131,6 +126,34 @@ dropNullsOrEmpty <- function(x) {
x[!vapply(x, nullOrEmpty, FUN.VALUE=logical(1))]
}
# Given a vector/list, return TRUE if any elements are named, FALSE otherwise.
anyNamed <- function(x) {
# Zero-length vector
if (length(x) == 0) return(FALSE)
nms <- names(x)
# List with no name attribute
if (is.null(nms)) return(FALSE)
# List with name attribute; check for any ""
any(nzchar(nms))
}
# Given a vector/list, return TRUE if any elements are unnamed, FALSE otherwise.
anyUnnamed <- function(x) {
# Zero-length vector
if (length(x) == 0) return(FALSE)
nms <- names(x)
# List with no name attribute
if (is.null(nms)) return(TRUE)
# List with name attribute; check for any ""
any(!nzchar(nms))
}
# Given a vector/list, returns a named vector/list (the labels will be blank).
asNamed <- function(x) {
@@ -150,7 +173,7 @@ empty_named_list <- function() {
# name as elements in a, the element in a is dropped. Also, if there are any
# duplicated names in a or b, only the last one with that name is kept.
mergeVectors <- function(a, b) {
if (any_unnamed(a) || any_unnamed(b)) {
if (anyUnnamed(a) || anyUnnamed(b)) {
stop("Vectors must be either NULL or have names for all elements")
}
@@ -162,27 +185,15 @@ mergeVectors <- function(a, b) {
# Sort a vector by the names of items. If there are multiple items with the
# same name, preserve the original order of those items. For empty
# vectors/lists/NULL, return the original value.
sortByName <- function(x, method = "auto") {
if (any_unnamed(x))
sortByName <- function(x) {
if (anyUnnamed(x))
stop("All items must be named")
# Special case for empty vectors/lists, and NULL
if (length(x) == 0)
return(x)
# Must provide consistent sort order
# https://github.com/rstudio/shinytest/issues/409
# Using a flag in the snapshot url to determine the method
# `method="radix"` uses `C` locale, which is consistent across platforms
# Even if two platforms share `en_us.UTF-8`, they may not sort consistently
# https://blog.zhimingwang.org/macos-lc_collate-hunt
# (macOS) $ LC_ALL=en_US.UTF-8 sort <<<$'python-dev\npython3-dev'
# python-dev
# python3-dev
# (Linux) $ LC_ALL=en_US.UTF-8 sort <<<$'python-dev\npython3-dev'
# python3-dev
# python-dev
x[order(names(x), method = method)]
x[order(names(x))]
}
# Sort a vector. If a character vector, sort using C locale, which is consistent
@@ -484,7 +495,7 @@ shinyCallingHandlers <- function(expr) {
withCallingHandlers(captureStackTraces(expr),
error = function(e) {
# Don't intercept shiny.silent.error (i.e. validation errors)
if (cnd_inherits(e, "shiny.silent.error"))
if (inherits(e, "shiny.silent.error"))
return()
handle <- getOption('shiny.error')
@@ -1705,20 +1716,24 @@ findEnclosingApp <- function(path = ".") {
}
}
# Until `rlang::cnd_inherits()` is on CRAN
cnd_inherits <- function(cnd, class) {
cnd_some(cnd, ~ inherits(.x, class))
}
cnd_some <- function(.cnd, .p, ...) {
.p <- rlang::as_function(.p)
while (rlang::is_condition(.cnd)) {
if (.p(.cnd, ...)) {
return(TRUE)
}
.cnd <- .cnd$parent
# Check if a package is installed, and if version is specified,
# that we have at least that version
is_available <- function(package, version = NULL) {
installed <- nzchar(system.file(package = package))
if (is.null(version)) {
return(installed)
}
FALSE
installed && isTRUE(utils::packageVersion(package) >= version)
}
# cached version of utils::packageVersion("shiny")
shinyPackageVersion <- local({
version <- NULL
function() {
if (is.null(version)) {
version <<- utils::packageVersion("shiny")
}
version
}
})

View File

@@ -2,7 +2,7 @@
<!-- badges: start -->
[![CRAN](https://www.r-pkg.org/badges/version/shiny)](https://CRAN.R-project.org/package=shiny)
[![R build status](https://github.com/rstudio/shiny/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/rstudio/shiny/actions)
[![R build status](https://github.com/rstudio/shiny/workflows/R-CMD-check/badge.svg)](https://github.com/rstudio/shiny/actions)
[![RStudio community](https://img.shields.io/badge/community-shiny-blue?style=social&logo=rstudio&logoColor=75AADB)](https://community.rstudio.com/new-topic?category=shiny&tags=shiny)
<!-- badges: end -->
@@ -47,13 +47,13 @@ For help with learning fundamental Shiny programming concepts, check out the [Ma
## Getting Help
To ask a question about Shiny, please use the [RStudio Community website](https://community.rstudio.com/new-topic?category=shiny&tags=shiny).
To ask a question about Shiny, please use the [RStudio Community website](https://community.rstudio.com/new-topic?category=shiny&tags=shiny).
For bug reports, please use the [issue tracker](https://github.com/rstudio/shiny/issues) and also keep in mind that by [writing a good bug report](https://github.com/rstudio/shiny/wiki/Writing-Good-Bug-Reports), you're more likely to get help with your problem.
For bug reports, please use the [issue tracker](https://github.com/rstudio/shiny/issues) and also keep in mind that by [writing a good bug report](https://github.com/rstudio/shiny/wiki/Writing-Good-Bug-Reports), you're more likely to get help with your problem.
## Contributing
We welcome contributions to the **shiny** package. Please see our [CONTRIBUTING.md](https://github.com/rstudio/shiny/blob/main/.github/CONTRIBUTING.md) file for detailed guidelines of how to contribute.
We welcome contributions to the **shiny** package. Please see our [CONTRIBUTING.md](https://github.com/rstudio/shiny/blob/master/.github/CONTRIBUTING.md) file for detailed guidelines of how to contribute.
## License

View File

@@ -1,3 +1,3 @@
/*! shiny 1.7.1.9003 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.6.0.9022 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
(function(){var t="ws:";window.location.protocol==="https:"&&(t="wss:");var o=window.location.pathname;/\/$/.test(o)||(o+="/");o+="autoreload/";var n=new WebSocket(t+"//"+window.location.host+o);n.onmessage=function(a){a.data==="autoreload"&&window.location.reload()};})();
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjdHMvZXh0cmFzL3NoaW55LWF1dG9yZWxvYWQudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qIGVzbGludC1kaXNhYmxlIHVuaWNvcm4vZmlsZW5hbWUtY2FzZSAqL1xudmFyIHByb3RvY29sID0gXCJ3czpcIjtcbmlmICh3aW5kb3cubG9jYXRpb24ucHJvdG9jb2wgPT09IFwiaHR0cHM6XCIpIHByb3RvY29sID0gXCJ3c3M6XCI7XG52YXIgZGVmYXVsdFBhdGggPSB3aW5kb3cubG9jYXRpb24ucGF0aG5hbWU7XG5pZiAoIS9cXC8kLy50ZXN0KGRlZmF1bHRQYXRoKSkgZGVmYXVsdFBhdGggKz0gXCIvXCI7XG5kZWZhdWx0UGF0aCArPSBcImF1dG9yZWxvYWQvXCI7XG52YXIgd3MgPSBuZXcgV2ViU29ja2V0KHByb3RvY29sICsgXCIvL1wiICsgd2luZG93LmxvY2F0aW9uLmhvc3QgKyBkZWZhdWx0UGF0aCk7XG5cbndzLm9ubWVzc2FnZSA9IGZ1bmN0aW9uIChldmVudCkge1xuICBpZiAoZXZlbnQuZGF0YSA9PT0gXCJhdXRvcmVsb2FkXCIpIHtcbiAgICB3aW5kb3cubG9jYXRpb24ucmVsb2FkKCk7XG4gIH1cbn07XG5cbmV4cG9ydCB7fTsiXSwKICAibWFwcGluZ3MiOiAiO1lBQ0EsR0FBSSxHQUFXLE1BQ2YsQUFBSSxPQUFPLFNBQVMsV0FBYSxVQUFVLEdBQVcsUUFDdEQsR0FBSSxHQUFjLE9BQU8sU0FBUyxTQUNsQyxBQUFLLE1BQU0sS0FBSyxJQUFjLElBQWUsS0FDN0MsR0FBZSxjQUNmLEdBQUksR0FBSyxHQUFJLFdBQVUsRUFBVyxLQUFPLE9BQU8sU0FBUyxLQUFPLEdBRWhFLEVBQUcsVUFBWSxTQUFVLEVBQU8sQ0FDOUIsQUFBSSxFQUFNLE9BQVMsY0FDakIsT0FBTyxTQUFTIiwKICAibmFtZXMiOiBbXQp9Cg==

View File

@@ -1,2 +1,2 @@
/*! shiny 1.7.1.9003 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.6.0.9022 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,"Courier New",monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:normal}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav{margin-bottom:0}#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
/*! shiny 1.7.1.9003 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.6.0.9022 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
(function(){var a=eval;window.addEventListener("message",function(i){var e=i.data;e.code&&a(e.code)});})();
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjdHMvc3JjL3V0aWxzL2V2YWwudHMiLCAiLi4vLi4vLi4vc3JjdHMvZXh0cmFzL3NoaW55LXRlc3Rtb2RlLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvL2VzYnVpbGQuZ2l0aHViLmlvL2NvbnRlbnQtdHlwZXMvI2RpcmVjdC1ldmFsXG4vL3RsL2RyO1xuLy8gKiBEaXJlY3QgdXNhZ2Ugb2YgYGV2YWwoXCJ4XCIpYCBpcyBiYWQgd2l0aCBidW5kbGVkIGNvZGUuXG4vLyAqIEluc3RlYWQsIHVzZSBpbmRpcmVjdCBjYWxscyB0byBgZXZhbGAgc3VjaCBhcyBgaW5kaXJlY3RFdmFsKFwieFwiKWBcbi8vICAgKiBFdmVuIGp1c3QgcmVuYW1pbmcgdGhlIGZ1bmN0aW9uIHdvcmtzIHdlbGwgZW5vdWdoLlxuLy8gPiBUaGlzIGlzIGtub3duIGFzIFwiaW5kaXJlY3QgZXZhbFwiIGJlY2F1c2UgZXZhbCBpcyBub3QgYmVpbmcgY2FsbGVkIGRpcmVjdGx5LCBhbmQgc28gZG9lcyBub3QgdHJpZ2dlciB0aGUgZ3JhbW1hdGljYWwgc3BlY2lhbCBjYXNlIGZvciBkaXJlY3QgZXZhbCBpbiB0aGUgSmF2YVNjcmlwdCBWTS4gWW91IGNhbiBjYWxsIGluZGlyZWN0IGV2YWwgdXNpbmcgYW55IHN5bnRheCBhdCBhbGwgZXhjZXB0IGZvciBhbiBleHByZXNzaW9uIG9mIHRoZSBleGFjdCBmb3JtIGV2YWwoJ3gnKS4gRm9yIGV4YW1wbGUsIHZhciBldmFsMiA9IGV2YWw7IGV2YWwyKCd4JykgYW5kIFtldmFsXVswXSgneCcpIGFuZCB3aW5kb3cuZXZhbCgneCcpIGFyZSBhbGwgaW5kaXJlY3QgZXZhbCBjYWxscy5cbi8vID4gV2hlbiB5b3UgdXNlIGluZGlyZWN0IGV2YWwsIHRoZSBjb2RlIGlzIGV2YWx1YXRlZCBpbiB0aGUgZ2xvYmFsIHNjb3BlIGluc3RlYWQgb2YgaW4gdGhlIGlubGluZSBzY29wZSBvZiB0aGUgY2FsbGVyLlxudmFyIGluZGlyZWN0RXZhbCA9IGV2YWw7XG5leHBvcnQgeyBpbmRpcmVjdEV2YWwgfTsiLCAiLyogZXNsaW50LWRpc2FibGUgdW5pY29ybi9maWxlbmFtZS1jYXNlICovXG5pbXBvcnQgeyBpbmRpcmVjdEV2YWwgfSBmcm9tIFwiLi4vc3JjL3V0aWxzL2V2YWxcIjsgLy8gTGlzdGVuIGZvciBtZXNzYWdlcyBmcm9tIHBhcmVudCBmcmFtZS4gVGhpcyBmaWxlIGlzIG9ubHkgYWRkZWQgd2hlbiB0aGVcbi8vIHNoaW55LnRlc3Rtb2RlIG9wdGlvbiBpcyBUUlVFLlxuXG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcIm1lc3NhZ2VcIiwgZnVuY3Rpb24gKGUpIHtcbiAgdmFyIG1lc3NhZ2UgPSBlLmRhdGE7XG4gIGlmIChtZXNzYWdlLmNvZGUpIGluZGlyZWN0RXZhbChtZXNzYWdlLmNvZGUpO1xufSk7Il0sCiAgIm1hcHBpbmdzIjogIjtZQU9BLEdBQUksR0FBZSxLQ0huQixPQUFPLGlCQUFpQixVQUFXLFNBQVUsRUFBRyxDQUM5QyxHQUFJLEdBQVUsRUFBRSxLQUNoQixBQUFJLEVBQVEsTUFBTSxFQUFhLEVBQVEiLAogICJuYW1lcyI6IFtdCn0K

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -295,15 +295,6 @@ pre.shiny-text-output {
margin-top: 0px;
}
/* Workaround for radio buttons and checkboxes not showing on Qt on Mac.
This occurs in the RStudio IDE on macOS 11.5.
https://github.com/rstudio/shiny/issues/3484
*/
.qtmac input[type="radio"],
.qtmac input[type="checkbox"] {
zoom: 1.0000001;
}
/* consistency with bootstrap.css for selectize.js */
.selectize-control {
margin-bottom: 10px;

View File

@@ -40,7 +40,7 @@ sense, namely:
In the example here, the \code{bindCache()} key consists of \code{input$x} and
\code{input$y} combined, and the value is \code{input$x * input$y}. In this simple
example, for any given key, there is only one possible returned value.\if{html}{\out{<div class="sourceCode NA">}}\preformatted{r <- reactive(\{ input$x * input$y \}) \%>\%
example, for any given key, there is only one possible returned value.\if{html}{\out{<div class="NA">}}\preformatted{r <- reactive(\{ input$x * input$y \}) \%>\%
bindCache(input$x, input$y)
}\if{html}{\out{</div>}}
@@ -63,7 +63,7 @@ to do some sort of reduction on the data that still captures information
about whether a value can be retrieved from the cache. For example, if you
have a large data set with timestamps, it might make sense to extract the
most recent timestamp and return that. Then, instead of hashing the entire
data object, the cached reactive only needs to hash the timestamp.\if{html}{\out{<div class="sourceCode NA">}}\preformatted{r <- reactive(\{ compute(bigdata()) \} \%>\%
data object, the cached reactive only needs to hash the timestamp.\if{html}{\out{<div class="NA">}}\preformatted{r <- reactive(\{ compute(bigdata()) \} \%>\%
bindCache(\{ extract_most_recent_time(bigdata()) \})
}\if{html}{\out{</div>}}
@@ -105,7 +105,7 @@ cache key is not too expensive.
Remember that the key is \emph{reactive}, so it is not re-executed every single
time that someone accesses the cached reactive. It is only re-executed if
it has been invalidated by one of the reactives it depends on. For
example, suppose we have this cached reactive:\if{html}{\out{<div class="sourceCode NA">}}\preformatted{r <- reactive(\{ input$x * input$y \}) \%>\%
example, suppose we have this cached reactive:\if{html}{\out{<div class="NA">}}\preformatted{r <- reactive(\{ input$x * input$y \}) \%>\%
bindCache(input$x, input$y)
}\if{html}{\out{</div>}}
@@ -244,7 +244,7 @@ cache collisions, dealing with internal state that may be set by the,
the cache.
You may need to provide a \code{cacheHint} to \code{\link[=createRenderFunction]{createRenderFunction()}} (or
\code{htmlwidgets::shinyRenderWidget()}, if you've authored an htmlwidget) in
\code{\link[htmlwidgets:htmlwidgets-shiny]{htmlwidgets::shinyRenderWidget()}}, if you've authored an htmlwidget) in
order for \code{bindCache()} to correctly compute a cache key.
The potential problem is a cache collision. Consider the following:\preformatted{output$x1 <- renderText(\{ input$x \}) \%>\% bindCache(input$x)
@@ -258,7 +258,7 @@ this, a \emph{cache hint} is automatically added when \code{\link[=renderText]{r
\code{\link[=createRenderFunction]{createRenderFunction()}}. The cache hint is used as part of the actual
cache key, in addition to the one passed to \code{bindCache()} by the user. The
cache hint can be viewed by calling the internal Shiny function
\code{extractCacheHint()}:\if{html}{\out{<div class="sourceCode NA">}}\preformatted{r <- renderText(\{ input$x \})
\code{extractCacheHint()}:\if{html}{\out{<div class="NA">}}\preformatted{r <- renderText(\{ input$x \})
shiny:::extractCacheHint(r)
}\if{html}{\out{</div>}}

View File

@@ -171,7 +171,7 @@ user sets both \code{x} and \code{y}, and then clicks on an \link{actionButton}
\code{go}.
To use both caching and events, the object should first be passed to
\code{bindCache()}, then \code{bindEvent()}. For example:\if{html}{\out{<div class="sourceCode NA">}}\preformatted{r <- reactive(\{
\code{bindCache()}, then \code{bindEvent()}. For example:\if{html}{\out{<div class="NA">}}\preformatted{r <- reactive(\{
Sys.sleep(2) # Pretend this is an expensive computation
input$x * input$y
\}) \%>\%

View File

@@ -101,7 +101,7 @@ displays a message once every 8 hrs (by default)
value and Developer message. This registration method allows package
authors to write one message in a single location.
For example, the following Shiny Developer Mode options are registered:\if{html}{\out{<div class="sourceCode r">}}\preformatted{# Reload the Shiny app when a sourced R file changes
For example, the following Shiny Developer Mode options are registered:\if{html}{\out{<div class="r">}}\preformatted{# Reload the Shiny app when a sourced R file changes
register_devmode_option(
"shiny.autoreload",
"Turning on shiny autoreload. To disable, call `options(shiny.autoreload = FALSE)`",
@@ -126,7 +126,7 @@ register_devmode_option(
Other known, non-Shiny Developer Mode options:
\itemize{
\item Sass:
}\if{html}{\out{<div class="sourceCode r">}}\preformatted{# Display the full stack trace when errors occur during Shiny app execution
}\if{html}{\out{<div class="r">}}\preformatted{# Display the full stack trace when errors occur during Shiny app execution
register_devmode_option(
"sass.cache",
"Turning off sass cache. To use default caching, call `options(sass.cache = TRUE)`",
@@ -165,7 +165,7 @@ re-implementing these two functions:
This function should return \code{TRUE} if \code{getOption("shiny.devmode")} is set.
In addition, we strongly recommend that it also checks to make sure
\code{testthat} is not testing.\if{html}{\out{<div class="sourceCode r">}}\preformatted{in_devmode <- function() \{
\code{testthat} is not testing.\if{html}{\out{<div class="r">}}\preformatted{in_devmode <- function() \{
isTRUE(getOption("shiny.devmode", FALSE)) &&
!identical(Sys.getenv("TESTTHAT"), "true")
\}
@@ -201,7 +201,7 @@ recommend displaying a message (\code{devmode_message}) to \code{stderr()} once
hours using \code{\link[rlang:abort]{rlang::inform()}}. This will keep the author up to date as to
which Dev Mode options are being altered. To allow developers a chance to
disable Dev Mode messages, the message should be skipped if
\code{getOption("shiny.devmode.verbose", TRUE)} is not \code{TRUE}.\if{html}{\out{<div class="sourceCode r">}}\preformatted{get_devmode_option <- function(name, default = NULL, devmode_default, devmode_message) \{
\code{getOption("shiny.devmode.verbose", TRUE)} is not \code{TRUE}.\if{html}{\out{<div class="r">}}\preformatted{get_devmode_option <- function(name, default = NULL, devmode_default, devmode_message) \{
if (!in_devmode()) \{
# Dev Mode disabled, act like `getOption()`
return(getOption(name, default = default))

View File

@@ -51,9 +51,9 @@ registerInputHandler("mypackage.validint", function(x, shinysession, name) {
})
## On the Javascript side, the associated input binding must have a corresponding getType method:
# getType: function(el) {
# return "mypackage.validint";
# }
getType: function(el) {
return "mypackage.validint";
}
}
}

View File

@@ -219,7 +219,7 @@ shinyOptions(cache = cachem::cache_mem(max_size = 20e6, max_age = 3600))
# At the top of app.R, this set the application-scoped cache to be a disk
# cache that can be shared among multiple concurrent R processes, and is
# deleted when the system reboots.
shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache")))
shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache"))
# At the top of app.R, this set the application-scoped cache to be a disk
# cache that can be shared among multiple concurrent R processes, and

View File

@@ -63,9 +63,7 @@ call to \code{dataTableOutput()} when \code{renderDataTable()} is used
in an interactive R Markdown document.}
}
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} Please use
\href{https://rstudio.github.io/DT/shiny.html}{\code{DT::renderDataTable()}}.
(Shiny 0.11.1)
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} Please use \code{\link[DT:renderDataTable]{DT::renderDataTable()}}. (Shiny 0.11.1)
Makes a reactive version of the given function that returns a data frame (or
matrix), which will be rendered with the \href{https://datatables.net}{DataTables}

View File

@@ -37,7 +37,7 @@ ports will be tried.}
\item{launch.browser}{If true, the system's default web browser will be
launched automatically after the app is started. Defaults to true in
interactive sessions only. The value of this parameter can also be a
interactive sessions only. This value of this parameter can also be a
function to call with the application's URL.}
\item{host}{The IPv4 address that the application should listen on. Defaults

View File

@@ -13,7 +13,7 @@ runGist(gist, destdir = NULL, ...)
runGitHub(
repo,
username = getOption("github.user"),
ref = "HEAD",
ref = "master",
subdir = NULL,
destdir = NULL,
...
@@ -47,8 +47,7 @@ all valid values.}
\code{"username/repo"}, \code{username} will be taken from \code{repo}.}
\item{ref}{Desired git reference. Could be a commit, tag, or branch name.
Defaults to \code{"HEAD"}, which means the default branch on GitHub, typically
\code{"main"} or \code{"master"}.}
Defaults to \code{"master"}.}
}
\description{
\code{runUrl()} downloads and launches a Shiny application that is hosted at
@@ -63,10 +62,10 @@ respectively.
\examples{
## Only run this example in interactive R sessions
if (interactive()) {
runUrl('https://github.com/rstudio/shiny_example/archive/main.tar.gz')
runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
# Can run an app from a subdirectory in the archive
runUrl("https://github.com/rstudio/shiny_example/archive/main.zip",
runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
subdir = "inst/shinyapp/")
}
## Only run this example in interactive R sessions

View File

@@ -104,9 +104,6 @@ should only be used for debugging or demonstrations of reactivity at the
console.}
\item{shiny.testmode (defaults to \code{FALSE})}{If \code{TRUE}, then various features for testing Shiny
applications are enabled.}
\item{shiny.snapshotsortc (defaults to \code{FALSE})}{If \code{TRUE}, test snapshot keys
for \pkg{shinytest} will be sorted consistently using the C locale. Snapshots
retrieved by \pkg{shinytest2} will always sort using the C locale.}
\item{shiny.trace (defaults to \code{FALSE})}{Print messages sent between the R server and the web
browser client to the R console. This is useful for debugging. Possible
values are \code{"send"} (only print messages sent to the client),

View File

@@ -76,10 +76,10 @@ value when it is a single choice input and the empty string is not in the
The resulting server \code{input} value will be returned as:
\itemize{
\item A symbol if \code{multiple = FALSE}. The \code{input} value should be
used with rlang's \code{\link[rlang:injection-operator]{rlang::!!()}}. For example,
used with rlang's \code{\link[rlang:bang-bang]{rlang::!!()}}. For example,
\code{ggplot2::aes(!!input$variable)}.
\item A list of symbols if \code{multiple = TRUE}. The \code{input} value
should be used with rlang's \code{\link[rlang:splice-operator]{rlang::!!!()}} to expand
should be used with rlang's \code{\link[rlang:bang-bang]{rlang::!!!()}} to expand
the symbol list as individual arguments. For example,
\code{dplyr::select(mtcars, !!!input$variabls)} which is
equivalent to \code{dplyr::select(mtcars, !!input$variabls[[1]], !!input$variabls[[2]], ..., !!input$variabls[[length(input$variabls)]])}.

View File

@@ -3,7 +3,7 @@
"homepage": "https://shiny.rstudio.com",
"repository": "github:rstudio/shiny",
"name": "@types/rstudio-shiny",
"version": "1.7.1-alpha.9003",
"version": "1.6.0-alpha.9022",
"license": "GPL-3.0-only",
"main": "",
"browser": "",
@@ -46,8 +46,7 @@
"@typescript-eslint/parser": "^4.25.0",
"autoprefixer": "^10.2.6",
"bootstrap-datepicker": "1.9.0",
"browserslist": "^4.19.1",
"caniuse-lite": "^1.0.30001312",
"browserslist": "^4.16.6",
"core-js": "^3.13.0",
"esbuild": "^0.12.4",
"esbuild-plugin-babel": "https://github.com/schloerke/esbuild-plugin-babel#patch-2",

View File

@@ -196,7 +196,7 @@ Both JavaScript files will produce a sourcemap (`**.js.map`) that the browser wi
### GitHub Actions
On push to the `main` branch or push to a Pull Request to the `main` branch, a GitHub Action will be run to make sure the bundled JavaScript code is up to date. If the source code does not compile to the exact same file, it will be committed an pushed back to the outdated branch. (This makes it so the full build tools are not necessary for small tweaks and comments. 🎉)
On push to the `master` branch or push to a Pull Request to the `master` branch, a GitHub Action will be run to make sure the bundled JavaScript code is up to date. If the source code does not compile to the exact same file, it will be committed an pushed back to the outdated branch. (This makes it so the full build tools are not necessary for small tweaks and comments. 🎉)
<!-- #### Auto build and browser refresh

View File

@@ -70,7 +70,6 @@ async function build(
incremental: incremental,
watch: watch,
target: "es5",
preserveSymlinks: true,
...opts,
}).then((x) => {
onRebuild();

283
srcts/old/Gruntfile.js Normal file
View File

@@ -0,0 +1,283 @@
module.exports = function(grunt) {
var instdir = '../inst/';
var js_srcdir = '../srcjs/'
// Make sure all plattforms to use `\n` as eol char: https://stackoverflow.com/questions/29817511/grunt-issue-with-line-endings
grunt.util.linefeed = "\n";
gruntConfig = {
pkg: pkgInfo(),
clean: {
options: { force: true },
src: [
instdir + "www/shared/shiny.js",
instdir + "www/shared/shiny.js.map",
instdir + "www/shared/shiny.min.js",
instdir + "www/shared/shiny.min.js.map",
"./temp_concat/shiny.js",
"./temp_concat/shiny.js.map",
instdir + "www/shared/datepicker/js/bootstrap-datepicker.min.js",
instdir + "www/shared/ionrangeslider/js/ion.rangeSlider.min.js"
]
},
concat: {
options: {
process: function(src, filepath) {
return (
"//---------------------------------------------------------------------\n" +
"// Source file: " +
filepath +
"\n\n" +
src
);
},
sourceMap: true
},
shiny: {
src: [
js_srcdir + "_start.js",
js_srcdir + "utils.js",
js_srcdir + "browser.js",
js_srcdir + "input_rate.js",
js_srcdir + "shinyapp.js",
js_srcdir + "notifications.js",
js_srcdir + "modal.js",
js_srcdir + "file_processor.js",
js_srcdir + "binding_registry.js",
js_srcdir + "output_binding.js",
js_srcdir + "output_binding_text.js",
js_srcdir + "output_binding_image.js",
js_srcdir + "output_binding_html.js",
js_srcdir + "output_binding_downloadlink.js",
js_srcdir + "output_binding_datatable.js",
js_srcdir + "output_binding_adapter.js",
js_srcdir + "input_binding.js",
js_srcdir + "input_binding_text.js",
js_srcdir + "input_binding_textarea.js",
js_srcdir + "input_binding_password.js",
js_srcdir + "input_binding_number.js",
js_srcdir + "input_binding_checkbox.js",
js_srcdir + "input_binding_slider.js",
js_srcdir + "input_binding_date.js",
js_srcdir + "input_binding_daterange.js",
js_srcdir + "input_binding_select.js",
js_srcdir + "input_binding_radio.js",
js_srcdir + "input_binding_checkboxgroup.js",
js_srcdir + "input_binding_actionbutton.js",
js_srcdir + "input_binding_tabinput.js",
js_srcdir + "input_binding_fileinput.js",
js_srcdir + "init_shiny.js",
js_srcdir + "reactlog.js",
js_srcdir + "_end.js"
],
// The temp_concat/ directory would have gone under /srcjs/, but the
// Babel Grunt plugin has trouble finding presets if it operates on a
// file that's not under the current directory. So we'll put it under
// ./
dest: "./temp_concat/shiny.js",
nonull: true
}
},
"string-replace": {
version: {
files: {
"./temp_concat/shiny.js": "./temp_concat/shiny.js"
},
options: {
replacements: [
{
pattern: /{{\s*VERSION\s*}}/g,
replacement: pkgInfo().version
}
]
}
}
},
babel: {
options: {
sourceMap: true,
compact: false,
presets: ["@babel/preset-env"]
},
shiny: {
src: "./temp_concat/shiny.js",
dest: instdir + "/www/shared/shiny.js"
}
},
eslint: {
options: {
parser: "babel-eslint",
format: require("eslint-stylish-mapped"),
extends: "eslint:recommended",
rules: {
"consistent-return": 1,
"dot-location": [1, "property"],
eqeqeq: 1,
// "no-shadow": 1,
"no-implicit-globals": 1,
"no-restricted-globals": [
"error",
"name",
"length",
"top",
"location",
"parent",
"status"
],
"no-global-assign": 1,
"no-undef": 1,
"no-unused-vars": [1, { args: "none" }],
"guard-for-in": 1,
// "no-use-before-define": [1, {"functions": false}],
semi: [1, "always"]
},
envs: ["es6", "browser", "jquery"],
globals: ["strftime"]
},
shiny: ["./temp_concat/shiny.js"]
},
uglify: {
shiny: {
options: {
banner:
"/*! <%= pkg.name %> <%= pkg.version %> | " +
'(c) 2012-<%= grunt.template.today("yyyy") %> RStudio, Inc. | ' +
"License: <%= pkg.license %> */\n",
sourceMap: {
includeSources: true
},
// Base the .min.js sourcemap off of the .js sourcemap created by concat
sourceMapIn: instdir + "www/shared/shiny.js.map"
},
src: instdir + "www/shared/shiny.js",
dest: instdir + "www/shared/shiny.min.js"
},
datepicker: {
src: [
instdir + "www/shared/datepicker/js/bootstrap-datepicker.js",
instdir + "www/shared/datepicker/js/locales/bootstrap-datepicker.*.js"
],
dest: instdir + "www/shared/datepicker/js/bootstrap-datepicker.min.js"
},
ionrangeslider: {
src: instdir + "www/shared/ionrangeslider/js/ion.rangeSlider.js",
dest: instdir + "www/shared/ionrangeslider/js/ion.rangeSlider.min.js"
},
selectize: {
src: instdir + "www/shared/selectize/accessibility/js/selectize-plugin-a11y.js",
dest: instdir + "www/shared/selectize/accessibility/js/selectize-plugin-a11y.min.js"
}
},
watch: {
shiny: {
files: ["<%= concat.shiny.src %>", "../DESCRIPTION"],
tasks: ["default"]
},
datepicker: {
files: "<%= uglify.datepicker.src %>",
tasks: ["uglify:datepicker"]
}
},
newer: {
options: {
override: function(detail, include) {
// If DESCRIPTION is updated, we'll also need to re-minify shiny.js
// because the min.js file embeds the version number.
if (detail.task === "uglify" && detail.target === "shiny") {
include(isNewer("../DESCRIPTION", detail.time));
} else {
include(false);
}
}
}
}
};
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-string-replace');
grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-newer');
// Need this here so that babel reads in the source map file after it's
// generated. Without this task, it would read in the source map when Grunt
// runs, which is wrong, if the source map doesn't exist, or is change later.
grunt.task.registerTask("configureBabel", "configures babel options", function() {
gruntConfig.babel.options.inputSourceMap = grunt.file.readJSON('./temp_concat/shiny.js.map');
});
grunt.task.registerTask(
"validateStringReplace",
"tests to make sure the version value was replaced",
function() {
var shinyContent = require('fs').readFileSync('./temp_concat/shiny.js', 'utf8');
if (/{{\s*VERSION\s*}}/.test(shinyContent)) {
grunt.fail.fatal("{{ VERSION }} was not replaced in compiled shiny.js file!")
}
}
);
grunt.initConfig(gruntConfig);
grunt.registerTask('default', [
'concat',
'string-replace',
'validateStringReplace',
'eslint',
'configureBabel',
'babel',
'uglify'
]);
// ---------------------------------------------------------------------------
// Utility functions
// ---------------------------------------------------------------------------
// Return an object which merges information from package.json and the
// DESCRIPTION file.
function pkgInfo() {
var pkg = grunt.file.readJSON('package.json');
pkg.name = descKeyValue('Package');
pkg.version = descKeyValue('Version');
pkg.license = descKeyValue('License');
return pkg;
}
// From the DESCRIPTION file, get the value of a key. This presently only
// works if the value is on one line, the same line as the key.
function descKeyValue(key) {
var lines = require('fs').readFileSync('../DESCRIPTION', 'utf8').split(/\r?\n/);
var pattern = new RegExp('^' + key + ':');
var txt = lines.filter(function(line) {
return pattern.test(line);
});
txt = txt[0];
pattern = new RegExp(key + ': *');
txt = txt.replace(pattern, '');
return txt;
}
// Return true if file's mtime is newer than mtime; false otherwise.
function isNewer(file, mtime) {
return require('fs').statSync(file).mtime > mtime;
}
};

View File

@@ -0,0 +1,17 @@
(function() {
var protocol = 'ws:';
if (window.location.protocol === 'https:')
protocol = 'wss:';
var defaultPath = window.location.pathname;
if (!/\/$/.test(defaultPath))
defaultPath += '/';
defaultPath += 'autoreload/';
var ws = new WebSocket(protocol + '//' + window.location.host + defaultPath);
ws.onmessage = function(event) {
if (event.data === "autoreload") {
window.location.reload()
}
}
})();

View File

@@ -0,0 +1,87 @@
#showcase-well {
border-radius: 0;
-webkit-border-radius: 0;
-moz-border-radius: 0;
}
.shiny-code {
background-color: white;
margin-bottom: 0;
}
.shiny-code code {
font-family: Menlo, Consolas, "Courier New", monospace;
}
.shiny-code-container {
margin-top: 20px;
clear: both;
}
.shiny-code-container h3 {
display: inline;
margin-right: 15px;
}
.showcase-header {
font-size: 16px;
font-weight: normal;
}
.showcase-code-link {
text-align: right;
padding: 15px;
}
#showcase-app-container {
vertical-align: top;
}
#showcase-code-tabs pre {
border: none;
line-height: 1em;
}
#showcase-code-tabs .nav,
#showcase-code-tabs ul {
margin-bottom: 0px;
}
#showcase-app-code {
width: 100%;
}
#showcase-code-tabs {
margin-right: 15px;
}
#showcase-code-tabs .tab-content {
border-style: solid;
border-color: #e5e5e5;
border-width: 0px 1px 1px 1px;
overflow:auto;
-webkit-border-bottom-right-radius: 4px;
-webkit-border-bottom-left-radius: 4px;
-moz-border-radius-bottomright: 4px;
-moz-border-radius-bottomleft: 4px;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
#showcase-code-position-toggle {
float: right;
}
#showcase-sxs-code {
padding-top: 20px;
vertical-align: top;
}
.showcase-code-license {
display: block;
text-align: right;
}
#showcase-code-content pre {
background-color: white;
}

View File

@@ -0,0 +1,272 @@
/*jshint browser:true, jquery:true, strict:false, curly:false, indent:2*/
(function() {
var animateMs = 400;
// Given a DOM node and a column (count of characters), walk recursively
// through the node's siblings counting characters until the given number
// of characters have been found.
//
// If the given count is bigger than the number of characters contained by
// the node and its siblings, returns a null node and the number of
// characters found.
function findTextColPoint(node, col) {
var cols = 0;
if (node.nodeType === 3) {
var nchar = node.nodeValue.replace(/\n/g, "").length;
if (nchar >= col) {
return { element: node, offset: col };
} else {
cols += nchar;
}
} else if (node.nodeType === 1 && node.firstChild) {
var ret = findTextColPoint(node.firstChild, col);
if (ret.element !== null) {
return ret;
} else {
cols += ret.offset;
}
}
if (node.nextSibling)
return findTextColPoint(node.nextSibling, col - cols);
else
return { element: null, offset: cols };
}
// Returns an object indicating the element containing the given line and
// column of text, and the offset into that element where the text was found.
//
// If the given line and column are not found, returns a null element and
// the number of lines found.
function findTextPoint(el, line, col) {
var newlines = 0;
for (var childId = 0; childId < el.childNodes.length; childId++) {
var child = el.childNodes[childId];
// If this is a text node, count the number of newlines it contains.
if (child.nodeType === 3) { // TEXT_NODE
var newlinere = /\n/g;
var match;
while ((match = newlinere.exec(child.nodeValue)) !== null) {
newlines++;
// Found the desired line, now find the column.
if (newlines === line) {
return findTextColPoint(child, match.index + col + 1);
}
}
}
// If this is not a text node, descend recursively to see how many
// lines it contains.
else if (child.nodeType === 1) { // ELEMENT_NODE
var ret = findTextPoint(child, line - newlines, col);
if (ret.element !== null)
return ret;
else
newlines += ret.offset;
}
}
return { element: null, offset: newlines };
}
// Draw a highlight effect for the given source ref. srcref is assumed to be
// an integer array of length 6, following the standard R format for source
// refs.
function highlightSrcref (srcref, srcfile) {
// Check to see if the browser supports text ranges (IE8 doesn't)
if (!document.createRange)
return;
// Check to see if we already have a marker for this source ref
var el = document.getElementById("srcref_" + srcref);
if (!el) {
// We don't have a marker, create one
el = document.createElement("span");
el.id = "srcref_" + srcref;
var ref = srcref;
var code = document.getElementById(srcfile.replace(/\./g, "_") + "_code");
// if there is no code file (might be a shiny file), quit early
if (!code) return;
var start = findTextPoint(code, ref[0], ref[4]);
var end = findTextPoint(code, ref[2], ref[5]);
// If the insertion point can't be found, bail out now
if (start.element === null || end.element === null)
return;
var range = document.createRange();
// If the text points are inside different <SPAN>s, we may not be able to
// surround them without breaking apart the elements to keep the DOM tree
// intact. Just move the selection points to encompass the contents of
// the SPANs.
if (start.element.parentNode.nodeName === "SPAN" &&
start.element !== end.element) {
range.setStartBefore(start.element.parentNode);
} else {
range.setStart(start.element, start.offset);
}
if (end.element.parentNode.nodeName === "SPAN" &&
start.element !== end.element) {
range.setEndAfter(end.element.parentNode);
} else {
range.setEnd(end.element, end.offset);
}
range.surroundContents(el);
}
// End any previous highlight before starting this one
jQuery(el)
.stop(true, true)
.effect("highlight", null, 1600);
}
// If this is the main Shiny window, wire up our custom message handler.
if (window.Shiny) {
Shiny.addCustomMessageHandler('showcase-src', function(message) {
if (message.srcref && message.srcfile) {
highlightSrcref(message.srcref, message.srcfile);
}
});
}
var isCodeAbove = false;
var setCodePosition = function(above, animate) {
var animateCodeMs = animate ? animateMs : 1;
// set the source and targets for the tab move
var newHostElement = above ?
document.getElementById("showcase-sxs-code") :
document.getElementById("showcase-code-inline");
var currentHostElement = above ?
document.getElementById("showcase-code-inline") :
document.getElementById("showcase-sxs-code");
var metadataElement = document.getElementById("showcase-app-metadata");
if (metadataElement === null) {
// if there's no app metadata, show and hide the entire well container
// when the code changes position
var wellElement = $("#showcase-well");
if (above) {
wellElement.fadeOut(animateCodeMs);
} else {
wellElement.fadeIn(animateCodeMs);
}
}
// hide the new element before doing anything to it
$(newHostElement).hide();
$(currentHostElement).fadeOut(animateCodeMs, function() {
var tabs = document.getElementById("showcase-code-tabs");
currentHostElement.removeChild(tabs);
newHostElement.appendChild(tabs);
// remove or set the height of the code
if (above) {
setCodeHeightFromDocHeight();
} else {
document.getElementById("showcase-code-content").removeAttribute("style");
}
$(newHostElement).fadeIn(animateCodeMs);
if (!above) {
// remove the applied width and zoom on the app container, and
// scroll smoothly down to the code's new home
document.getElementById("showcase-app-container").removeAttribute("style");
if (animate)
$(document.body).animate({ scrollTop: $(newHostElement).offset().top });
}
// if there's a readme, move it either alongside the code or beneath
// the app
var readme = document.getElementById("readme-md");
if (readme !== null) {
readme.parentElement.removeChild(readme);
if (above) {
currentHostElement.appendChild(readme);
$(currentHostElement).fadeIn(animateCodeMs);
}
else
document.getElementById("showcase-app-metadata").appendChild(readme);
}
// change the text on the toggle button to reflect the new state
document.getElementById("showcase-code-position-toggle").innerHTML = above ?
'<i class="fa fa-level-down"></i> show below' :
'<i class="fa fa-level-up"></i> show with app';
});
if (above) {
$(document.body).animate({ scrollTop: 0 }, animateCodeMs);
}
isCodeAbove = above;
setAppCodeSxsWidths(above && animate);
$(window).trigger("resize");
};
var setAppCodeSxsWidths = function(animate) {
var appTargetWidth = 960;
var appWidth = appTargetWidth;
var zoom = 1.0;
var totalWidth = document.getElementById("showcase-app-code").offsetWidth;
if (totalWidth / 2 > appTargetWidth) {
// If the app can use only half the available space and still meet its
// target, take half the available space.
appWidth = totalWidth / 2;
} else if (totalWidth * 0.66 > appTargetWidth) {
// If the app can meet its target by taking up more space (up to 66%
// of its container), take up more space.
appWidth = 960;
} else {
// The space is too narrow for the app and code to live side-by-side
// in a friendly way. Keep the app at 2/3 of the space but scale it.
appWidth = totalWidth * 0.66;
zoom = appWidth/appTargetWidth;
}
var app = document.getElementById("showcase-app-container");
$(app).animate({
width: appWidth + "px",
zoom: (zoom*100) + "%"
}, animate ? animateMs : 0);
};
var toggleCodePosition = function() {
setCodePosition(!isCodeAbove, true);
};
// if the browser is sized to wider than 1350px, show the code next to the
// app by default
var setInitialCodePosition = function() {
if (document.body.offsetWidth > 1350) {
setCodePosition(true, false);
}
};
// make the code scrollable to about the height of the browser, less space
// for the tabs
var setCodeHeightFromDocHeight = function() {
document.getElementById("showcase-code-content").style.height =
$(window).height() + "px";
};
// if there's a block of markdown content, render it to HTML
var renderMarkdown = function() {
var mdContent = document.getElementById("showcase-markdown-content");
if (mdContent !== null) {
// IE8 puts the content of <script> tags into innerHTML but
// not innerText
var content = mdContent.innerText || mdContent.innerHTML;
document.getElementById("readme-md").innerHTML =
(new Showdown.converter()).makeHtml(content)
}
}
$(window).resize(function() {
if (isCodeAbove) {
setAppCodeSxsWidths(false);
setCodeHeightFromDocHeight();
}
});
window.toggleCodePosition = toggleCodePosition;
$(window).on("load", setInitialCodePosition);
$(window).on("load", renderMarkdown);
if (window.hljs)
hljs.initHighlightingOnLoad();
})();

View File

@@ -0,0 +1,8 @@
// Listen for messages from parent frame. This file is only added when the
// shiny.testmode option is TRUE.
window.addEventListener("message", function(e) {
var message = e.data;
if (message.code)
eval(message.code);
});

1
srcts/old/js/_end.js Normal file
View File

@@ -0,0 +1 @@
})();

17
srcts/old/js/_start.js Normal file
View File

@@ -0,0 +1,17 @@
(function() {
var $ = jQuery;
var exports = window.Shiny = window.Shiny || {};
exports.version = "{{ VERSION }}"; // Version number inserted by Grunt
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();
});

View File

@@ -0,0 +1,37 @@
var BindingRegistry = function() {
this.bindings = [];
this.bindingNames = {};
};
(function() {
this.register = function(binding, bindingName, priority) {
var bindingObj = {binding: binding, priority: priority || 0};
this.bindings.unshift(bindingObj);
if (bindingName) {
this.bindingNames[bindingName] = bindingObj;
binding.name = bindingName;
}
};
this.setPriority = function(bindingName, priority) {
var bindingObj = this.bindingNames[bindingName];
if (!bindingObj)
throw "Tried to set priority on unknown binding " + bindingName;
bindingObj.priority = priority || 0;
};
this.getPriority = function(bindingName) {
var bindingObj = this.bindingNames[bindingName];
if (!bindingObj)
return false;
return bindingObj.priority;
};
this.getBindings = function() {
// Sort the bindings. The ones with higher priority are consulted
// first; ties are broken by most-recently-registered.
return mergeSort(this.bindings, function(a, b) {
return b.priority - a.priority;
});
};
}).call(BindingRegistry.prototype);
var inputBindings = exports.inputBindings = new BindingRegistry();
var outputBindings = exports.outputBindings = new BindingRegistry();

41
srcts/old/js/browser.js Normal file
View File

@@ -0,0 +1,41 @@
var browser = (function() {
var isQt = false;
// For easy handling of Qt quirks using CSS
if (/\bQt\//.test(window.navigator.userAgent)) {
$(document.documentElement).addClass('qt');
isQt = true;
}
// Enable special treatment for Qt 5 quirks on Linux
if (/\bQt\/5/.test(window.navigator.userAgent) &&
/Linux/.test(window.navigator.userAgent)) {
$(document.documentElement).addClass('qt5');
}
// Detect IE and older (pre-Chromium) Edge
var ua = window.navigator.userAgent;
var isIE = /MSIE|Trident|Edge/.test(ua);
function getIEVersion() {
var msie = ua.indexOf('MSIE ');
if (isIE && msie > 0) {
// IE 10 or older => return version number
return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
}
var trident = ua.indexOf('Trident/');
if (trident > 0) {
// IE 11 => return version number
var rv = ua.indexOf('rv:');
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
}
return -1;
}
return {
isQt: isQt,
isIE: isIE,
IEVersion: getIEVersion()
};
})();

View File

@@ -0,0 +1,79 @@
// Generic driver class for doing chunk-wise asynchronous processing of a
// FileList object. Subclass/clone it and override the `on*` functions to
// make it do something useful.
var FileProcessor = function(files) {
this.files = files;
this.fileIndex = -1;
// Currently need to use small chunk size because R-Websockets can't
// handle continuation frames
this.aborted = false;
this.completed = false;
// TODO: Register error/abort callbacks
this.$run();
};
(function() {
// Begin callbacks. Subclassers/cloners may override any or all of these.
this.onBegin = function(files, cont) {
setTimeout(cont, 0);
};
this.onFile = function(file, cont) {
setTimeout(cont, 0);
};
this.onComplete = function() {
};
this.onAbort = function() {
};
// End callbacks
// Aborts processing, unless it's already completed
this.abort = function() {
if (this.completed || this.aborted)
return;
this.aborted = true;
this.onAbort();
};
// Returns a bound function that will call this.$run one time.
this.$getRun = function() {
var self = this;
var called = false;
return function() {
if (called)
return;
called = true;
self.$run();
};
};
// This function will be called multiple times to advance the process.
// It relies on the state of the object's fields to know what to do next.
this.$run = function() {
if (this.aborted || this.completed)
return;
if (this.fileIndex < 0) {
// Haven't started yet--begin
this.fileIndex = 0;
this.onBegin(this.files, this.$getRun());
return;
}
if (this.fileIndex === this.files.length) {
// Just ended
this.completed = true;
this.onComplete();
return;
}
// If we got here, then we have a file to process, or we are
// in the middle of processing a file, or have just finished
// processing a file.
var file = this.files[this.fileIndex++];
this.onFile(file, this.$getRun());
};
}).call(FileProcessor.prototype);

618
srcts/old/js/init_shiny.js Normal file
View File

@@ -0,0 +1,618 @@
function initShiny() {
var shinyapp = exports.shinyapp = new ShinyApp();
function bindOutputs(scope = document) {
scope = $(scope);
var bindings = outputBindings.getBindings();
for (var i = 0; i < bindings.length; i++) {
var binding = bindings[i].binding;
var matches = binding.find(scope) || [];
for (var j = 0; j < matches.length; j++) {
var el = matches[j];
var id = binding.getId(el);
// Check if ID is falsy
if (!id)
continue;
// In some uncommon cases, elements that are later in the
// matches array can be removed from the document by earlier
// iterations. See https://github.com/rstudio/shiny/issues/1399
if (!$.contains(document, el))
continue;
var $el = $(el);
if ($el.hasClass('shiny-bound-output')) {
// Already bound; can happen with nested uiOutput (bindAll
// gets called on two ancestors)
continue;
}
// If this element reports its CSS styles to getCurrentOutputInfo()
// then it should have a MutationObserver() to resend CSS if its
// style/class attributes change. This observer should already exist
// for _static_ UI, but not yet for _dynamic_ UI
maybeAddThemeObserver(el);
var bindingAdapter = new OutputBindingAdapter(el, binding);
shinyapp.bindOutput(id, bindingAdapter);
$el.data('shiny-output-binding', bindingAdapter);
$el.addClass('shiny-bound-output');
if (!$el.attr("aria-live")) $el.attr("aria-live", "polite");
$el.trigger({
type: 'shiny:bound',
binding: binding,
bindingType: 'output'
});
}
}
// Send later in case DOM layout isn't final yet.
setTimeout(sendImageSize, 0);
setTimeout(sendOutputHiddenState, 0);
}
function unbindOutputs(scope = document, includeSelf = false) {
var outputs = $(scope).find('.shiny-bound-output');
if (includeSelf && $(scope).hasClass('shiny-bound-output')) {
outputs.push(scope);
}
for (var i = 0; i < outputs.length; i++) {
var $el = $(outputs[i]);
var bindingAdapter = $el.data('shiny-output-binding');
if (!bindingAdapter)
continue;
var id = bindingAdapter.binding.getId(outputs[i]);
shinyapp.unbindOutput(id, bindingAdapter);
$el.removeClass('shiny-bound-output');
$el.removeData('shiny-output-binding');
$el.trigger({
type: 'shiny:unbound',
binding: bindingAdapter.binding,
bindingType: 'output'
});
}
// Send later in case DOM layout isn't final yet.
setTimeout(sendImageSize, 0);
setTimeout(sendOutputHiddenState, 0);
}
var inputBatchSender = new InputBatchSender(shinyapp);
var inputsNoResend = new InputNoResendDecorator(inputBatchSender);
var inputsEvent = new InputEventDecorator(inputsNoResend);
var inputsRate = new InputRateDecorator(inputsEvent);
var inputsDefer = new InputDeferDecorator(inputsEvent);
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;
$('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.setInputValue = exports.onInputChange = function(name, value, opts) {
opts = addDefaultInputOpts(opts);
inputs.setInput(name, value, opts);
};
// By default, Shiny deduplicates input value changes; that is, if
// `setInputValue` is called with the same value as the input already
// has, the call is ignored (unless opts.priority = "event"). Calling
// `forgetLastInputValue` tells Shiny that the very next call to
// `setInputValue` for this input id shouldn't be ignored, even if it
// is a dupe of the existing value.
exports.forgetLastInputValue = function(name) {
inputsNoResend.forget(name);
};
var boundInputs = {};
function valueChangeCallback(binding, el, allowDeferred) {
var id = binding.getId(el);
if (id) {
var value = binding.getValue(el);
var type = binding.getType(el);
if (type)
id = id + ":" + type;
let opts = {
priority: allowDeferred ? "deferred" : "immediate",
binding: binding,
el: el
};
inputs.setInput(id, value, opts);
}
}
function bindInputs(scope = document) {
var bindings = inputBindings.getBindings();
var inputItems = {};
for (var i = 0; i < bindings.length; i++) {
var binding = bindings[i].binding;
var matches = binding.find(scope) || [];
for (var j = 0; j < matches.length; j++) {
var el = matches[j];
var id = binding.getId(el);
// Check if ID is falsy, or if already bound
if (!id || boundInputs[id])
continue;
var type = binding.getType(el);
var effectiveId = type ? id + ":" + type : id;
inputItems[effectiveId] = {
value: binding.getValue(el),
opts: {
immediate: true,
binding: binding,
el: el
}
};
/*jshint loopfunc:true*/
var thisCallback = (function() {
var thisBinding = binding;
var thisEl = el;
return function(allowDeferred) {
valueChangeCallback(thisBinding, thisEl, allowDeferred);
};
})();
binding.subscribe(el, thisCallback);
$(el).data('shiny-input-binding', binding);
$(el).addClass('shiny-bound-input');
var ratePolicy = binding.getRatePolicy(el);
if (ratePolicy !== null) {
inputsRate.setRatePolicy(
effectiveId,
ratePolicy.policy,
ratePolicy.delay);
}
boundInputs[id] = {
binding: binding,
node: el
};
$(el).trigger({
type: 'shiny:bound',
binding: binding,
bindingType: 'input'
});
}
}
return inputItems;
}
function unbindInputs(scope = document, includeSelf = false) {
var inputs = $(scope).find('.shiny-bound-input');
if (includeSelf && $(scope).hasClass('shiny-bound-input')) {
inputs.push(scope);
}
for (var i = 0; i < inputs.length; i++) {
var el = inputs[i];
var binding = $(el).data('shiny-input-binding');
if (!binding)
continue;
var id = binding.getId(el);
$(el).removeClass('shiny-bound-input');
delete boundInputs[id];
binding.unsubscribe(el);
$(el).trigger({
type: 'shiny:unbound',
binding: binding,
bindingType: 'input'
});
}
}
function _bindAll(scope) {
bindOutputs(scope);
return bindInputs(scope);
}
function unbindAll(scope, includeSelf = false) {
unbindInputs(scope, includeSelf);
unbindOutputs(scope, includeSelf);
}
exports.bindAll = function(scope) {
// _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
// is a convenient place to hang it. bindAll will be called anytime new HTML
// appears that might contain inputs/outputs; it's reasonable to assume that
// any such HTML may contain iframes as well.
initDeferredIframes();
};
exports.unbindAll = unbindAll;
// Calls .initialize() for all of the input objects in all input bindings,
// in the given scope.
function initializeInputs(scope = document) {
var bindings = inputBindings.getBindings();
// Iterate over all bindings
for (var i = 0; i < bindings.length; i++) {
var binding = bindings[i].binding;
var inputObjects = binding.find(scope) || [];
// Iterate over all input objects for this binding
for (var j = 0; j < inputObjects.length; j++) {
if (!inputObjects[j]._shiny_initialized) {
inputObjects[j]._shiny_initialized = true;
binding.initialize(inputObjects[j]);
}
}
}
}
exports.initializeInputs = initializeInputs;
function getIdFromEl(el) {
var $el = $(el);
var bindingAdapter = $el.data("shiny-output-binding");
if (!bindingAdapter)
return null;
else
return bindingAdapter.getId();
}
// Initialize all input objects in the document, before binding
initializeInputs(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), x => x.value);
// The server needs to know the size of each image and plot output element,
// in case it is auto-sizing
$('.shiny-image-output, .shiny-plot-output, .shiny-report-size').each(function() {
var id = getIdFromEl(this);
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
initialValues['.clientdata_output_' + id + '_width'] = this.offsetWidth;
initialValues['.clientdata_output_' + id + '_height'] = this.offsetHeight;
}
});
function getComputedBgColor(el) {
if (!el) {
// Top of document, can't recurse further
return null;
}
let bgColor = getStyle(el, "background-color");
let m = bgColor.match(/^rgba\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/);
if (bgColor === "transparent" || (m && parseFloat(m[4]) === 0)) {
// No background color on this element. See if it has a background image.
let bgImage = getStyle(el, "background-image");
if (bgImage && bgImage !== "none") {
// Failed to detect background color, since it has a background image
return null;
} else {
// Recurse
return getComputedBgColor(el.parentElement);
}
}
return bgColor;
}
function getComputedFont(el) {
let fontFamily = getStyle(el, "font-family");
let fontSize = getStyle(el, "font-size");
return {
families: fontFamily.replace(/"/g, '').split(", "),
size: fontSize
};
}
$('.shiny-image-output, .shiny-plot-output, .shiny-report-theme').each(function() {
var el = this, id = getIdFromEl(el);
initialValues['.clientdata_output_' + id + '_bg'] = getComputedBgColor(el);
initialValues['.clientdata_output_' + id + '_fg'] = getStyle(el, "color");
initialValues['.clientdata_output_' + id + '_accent'] = getComputedLinkColor(el);
initialValues['.clientdata_output_' + id + '_font'] = getComputedFont(el);
maybeAddThemeObserver(el);
});
// Resend computed styles if *an output element's* class or style attribute changes.
// This gives us some level of confidence that getCurrentOutputInfo() will be
// properly invalidated if output container is mutated; but unfortunately,
// we don't have a reasonable way to detect change in *inherited* styles
// (other than session$setCurrentTheme())
// https://github.com/rstudio/shiny/issues/3196
// https://github.com/rstudio/shiny/issues/2998
function maybeAddThemeObserver(el) {
if (!window.MutationObserver) {
return; // IE10 and lower
}
var cl = el.classList;
var reportTheme = cl.contains('shiny-image-output') || cl.contains('shiny-plot-output') || cl.contains('shiny-report-theme');
if (!reportTheme) {
return;
}
var $el = $(el);
if ($el.data("shiny-theme-observer")) {
return; // i.e., observer is already observing
}
var observerCallback = new Debouncer(null, () => doSendTheme(el), 100);
var observer = new MutationObserver(() => observerCallback.normalCall());
var config = {attributes: true, attributeFilter: ['style', 'class']};
observer.observe(el, config);
$el.data("shiny-theme-observer", observer);
}
function doSendTheme(el) {
// Sending theme info on error isn't necessary (it'd add an unnecessary additional round-trip)
if (el.classList.contains("shiny-output-error")) {
return;
}
var id = getIdFromEl(el);
inputs.setInput('.clientdata_output_' + id + '_bg', getComputedBgColor(el));
inputs.setInput('.clientdata_output_' + id + '_fg', getStyle(el, "color"));
inputs.setInput('.clientdata_output_' + id + '_accent', getComputedLinkColor(el));
inputs.setInput('.clientdata_output_' + id + '_font', getComputedFont(el));
}
function doSendImageSize() {
$('.shiny-image-output, .shiny-plot-output, .shiny-report-size').each(function() {
var id = getIdFromEl(this);
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
inputs.setInput('.clientdata_output_' + id + '_width', this.offsetWidth);
inputs.setInput('.clientdata_output_' + id + '_height', this.offsetHeight);
}
});
$('.shiny-image-output, .shiny-plot-output, .shiny-report-theme').each(function() {
doSendTheme(this);
});
$('.shiny-bound-output').each(function() {
var $this = $(this), binding = $this.data('shiny-output-binding');
$this.trigger({
type: 'shiny:visualchange',
visible: !isHidden(this),
binding: binding
});
binding.onResize();
});
}
var sendImageSizeDebouncer = new Debouncer(null, doSendImageSize, 0);
function sendImageSize() {
sendImageSizeDebouncer.normalCall();
}
// Make sure sendImageSize actually gets called before the inputBatchSender
// sends data to the server.
inputBatchSender.lastChanceCallback.push(function() {
if (sendImageSizeDebouncer.isPending())
sendImageSizeDebouncer.immediateCall();
});
// Return true if the object or one of its ancestors in the DOM tree has
// style='display:none'; otherwise return false.
function isHidden(obj) {
// null means we've hit the top of the tree. If width or height is
// non-zero, then we know that no ancestor has display:none.
if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) {
return false;
} else if (getStyle(obj, 'display') === 'none') {
return true;
} else {
return(isHidden(obj.parentNode));
}
}
var lastKnownVisibleOutputs = {};
// Set initial state of outputs to hidden, if needed
$('.shiny-bound-output').each(function() {
var id = getIdFromEl(this);
if (isHidden(this)) {
initialValues['.clientdata_output_' + id + '_hidden'] = true;
} else {
lastKnownVisibleOutputs[id] = true;
initialValues['.clientdata_output_' + id + '_hidden'] = false;
}
});
// Send update when hidden state changes
function doSendOutputHiddenState() {
var visibleOutputs = {};
$('.shiny-bound-output').each(function() {
var id = getIdFromEl(this);
delete lastKnownVisibleOutputs[id];
// Assume that the object is hidden when width and height are 0
var hidden = isHidden(this), evt = {
type: 'shiny:visualchange',
visible: !hidden
};
if (hidden) {
inputs.setInput('.clientdata_output_' + id + '_hidden', true);
} else {
visibleOutputs[id] = true;
inputs.setInput('.clientdata_output_' + id + '_hidden', false);
}
var $this = $(this);
evt.binding = $this.data('shiny-output-binding');
$this.trigger(evt);
});
// Anything left in lastKnownVisibleOutputs is orphaned
for (var name in lastKnownVisibleOutputs) {
if (lastKnownVisibleOutputs.hasOwnProperty(name))
inputs.setInput('.clientdata_output_' + name + '_hidden', true);
}
// Update the visible outputs for next time
lastKnownVisibleOutputs = visibleOutputs;
}
// sendOutputHiddenState gets called each time DOM elements are shown or
// hidden. This can be in the hundreds or thousands of times at startup.
// We'll debounce it, so that we do the actual work once per tick.
var sendOutputHiddenStateDebouncer = new Debouncer(null, doSendOutputHiddenState, 0);
function sendOutputHiddenState() {
sendOutputHiddenStateDebouncer.normalCall();
}
// We need to make sure doSendOutputHiddenState actually gets called before
// the inputBatchSender sends data to the server. The lastChanceCallback
// here does that - if the debouncer has a pending call, flush it.
inputBatchSender.lastChanceCallback.push(function() {
if (sendOutputHiddenStateDebouncer.isPending())
sendOutputHiddenStateDebouncer.immediateCall();
});
// Given a namespace and a handler function, return a function that invokes
// the handler only when e's namespace matches. For example, if the
// namespace is "bs", it would match when e.namespace is "bs" or "bs.tab".
// If the namespace is "bs.tab", it would match for "bs.tab", but not "bs".
function filterEventsByNamespace(namespace, handler) {
namespace = namespace.split(".");
return function(e) {
var eventNamespace = e.namespace.split(".");
// If any of the namespace strings aren't present in this event, quit.
for (var i=0; i<namespace.length; i++) {
if (eventNamespace.indexOf(namespace[i]) === -1)
return;
}
handler.apply(this, arguments);
};
}
// The size of each image may change either because the browser window was
// resized, or because a tab was shown/hidden (hidden elements report size
// of 0x0). It's OK to over-report sizes because the input pipeline will
// filter out values that haven't changed.
$(window).resize(debounce(500, sendImageSize));
// Need to register callbacks for each Bootstrap 3 class.
var bs3classes = ['modal', 'dropdown', 'tab', 'tooltip', 'popover', 'collapse'];
$.each(bs3classes, function(idx, classname) {
$(document.body).on('shown.bs.' + classname + '.sendImageSize', '*',
filterEventsByNamespace('bs', sendImageSize));
$(document.body).on('shown.bs.' + classname + '.sendOutputHiddenState ' +
'hidden.bs.' + classname + '.sendOutputHiddenState',
'*', filterEventsByNamespace('bs', sendOutputHiddenState));
});
// This is needed for Bootstrap 2 compatibility and for non-Bootstrap
// related shown/hidden events (like conditionalPanel)
$(document.body).on('shown.sendImageSize', '*', sendImageSize);
$(document.body).on('shown.sendOutputHiddenState hidden.sendOutputHiddenState', '*',
sendOutputHiddenState);
// Send initial pixel ratio, and update it if it changes
initialValues['.clientdata_pixelratio'] = pixelRatio();
$(window).resize(function() {
inputs.setInput('.clientdata_pixelratio', pixelRatio());
});
// Send initial URL
initialValues['.clientdata_url_protocol'] = window.location.protocol;
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 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', window.location.hash);
});
// The server needs to know what singletons were rendered as part of
// the page loading
var singletonText = initialValues['.clientdata_singletons'] =
$('script[type="application/shiny-singletons"]').text();
singletons.registerNames(singletonText.split(/,/));
var dependencyText = $('script[type="application/html-dependencies"]').text();
$.each(dependencyText.split(/;/), function(i, depStr) {
var match = /\s*^(.+)\[(.+)\]\s*$/.exec(depStr);
if (match) {
registerDependency(match[1], match[2]);
}
});
// We've collected all the initial values--start the server process!
inputsNoResend.reset(initialValues);
shinyapp.connect(initialValues);
$(document).one("shiny:connected", function() {
initDeferredIframes();
});
} // function initShiny()
// Give any deferred iframes a chance to load.
function initDeferredIframes() {
if (!window.Shiny || !window.Shiny.shinyapp || !window.Shiny.shinyapp.isConnected()) {
// If somehow we accidentally call this before the server connection is
// established, just ignore the call. At the time of this writing it
// doesn't happen, but it's easy to imagine a later refactoring putting
// us in this situation and it'd be hard to notice with either manual
// testing or automated tests, because the only effect is on HTTP request
// timing. (Update: Actually Aron saw this being called without even
// window.Shiny being defined, but it was hard to repro.)
return;
}
$(".shiny-frame-deferred").each(function (i, el) {
var $el = $(el);
$el.removeClass("shiny-frame-deferred");
$el.attr("src", $el.attr("data-deferred-src"));
$el.attr("data-deferred-src", null);
});
}
$(function() {
// Init Shiny a little later than document ready, so user code can
// run first (i.e. to register bindings)
setTimeout(initShiny, 1);
});

View File

@@ -0,0 +1,44 @@
var InputBinding = exports.InputBinding = function() {};
(function() {
// Returns a jQuery object or element array that contains the
// descendants of scope that match this binding
this.find = function(scope) { throw "Not implemented"; };
this.getId = function(el) {
return el['data-input-id'] || el.id;
};
// Gives the input a type in case the server needs to know it
// to deserialize the JSON correctly
this.getType = function() { return false; };
this.getValue = function(el) { throw "Not implemented"; };
// The callback method takes one argument, whose value is boolean. If true,
// allow deferred (debounce or throttle) sending depending on the value of
// getRatePolicy. If false, send value immediately.
this.subscribe = function(el, callback) { };
this.unsubscribe = function(el) { };
// This is used for receiving messages that tell the input object to do
// things, such as setting values (including min, max, and others).
// 'data' should be an object with elements corresponding to value, min,
// max, etc., as appropriate for the type of input object. It also should
// trigger a change event.
this.receiveMessage = function(el, data) { throw "Not implemented"; };
this.getState = function(el, data) { throw "Not implemented"; };
this.getRatePolicy = function() { return null; };
// Some input objects need initialization before being bound. This is
// called when the document is ready (for statically-added input objects),
// and when new input objects are added to the document with
// htmlOutputBinding.renderValue() (for dynamically-added input objects).
// This is called before the input is bound.
this.initialize = function(el) { };
// This is called after unbinding the output.
this.dispose = function(el) { };
}).call(InputBinding.prototype);

View File

@@ -0,0 +1,65 @@
var actionButtonInputBinding = new InputBinding();
$.extend(actionButtonInputBinding, {
find: function(scope) {
return $(scope).find(".action-button");
},
getValue: function(el) {
return $(el).data('val') || 0;
},
setValue: function(el, value) {
$(el).data('val', value);
},
getType: function(el) {
return 'shiny.action';
},
subscribe: function(el, callback) {
$(el).on("click.actionButtonInputBinding", function(e) {
var $el = $(this);
var val = $el.data('val') || 0;
$el.data('val', val + 1);
callback();
});
},
getState: function(el) {
return { value: this.getValue(el) };
},
receiveMessage: function(el, data) {
var $el = $(el);
// retrieve current label and icon
var label = $el.text();
var icon = '';
// to check (and store) the previous icon, we look for a $el child
// object that has an i tag, and some (any) class (this prevents
// italicized text - which has an i tag but, usually, no class -
// from being mistakenly selected)
if ($el.find('i[class]').length > 0) {
var icon_html = $el.find('i[class]')[0];
if (icon_html === $el.children()[0]) { // another check for robustness
icon = $(icon_html).prop('outerHTML');
}
}
// update the requested properties
if (data.hasOwnProperty('label')) label = data.label;
if (data.hasOwnProperty('icon')) {
icon = data.icon;
// if the user entered icon=character(0), remove the icon
if (icon.length === 0) icon = '';
}
// produce new html
$el.html(icon + ' ' + label);
},
unsubscribe: function(el) {
$(el).off(".actionButtonInputBinding");
}
});
inputBindings.register(actionButtonInputBinding, 'shiny.actionButtonInput');
$(document).on('click', 'a.action-button', function(e) {
e.preventDefault();
});

View File

@@ -0,0 +1,38 @@
var checkboxInputBinding = new InputBinding();
$.extend(checkboxInputBinding, {
find: function(scope) {
return $(scope).find('input[type="checkbox"]');
},
getValue: function(el) {
return el.checked;
},
setValue: function(el, value) {
el.checked = value;
},
subscribe: function(el, callback) {
$(el).on('change.checkboxInputBinding', function(event) {
callback(true);
});
},
unsubscribe: function(el) {
$(el).off('.checkboxInputBinding');
},
getState: function(el) {
return {
label: $(el).parent().find('span').text(),
value: el.checked
};
},
receiveMessage: function(el, data) {
if (data.hasOwnProperty('value'))
el.checked = data.value;
// checkboxInput()'s label works different from other
// input labels...the label container should always exist
if (data.hasOwnProperty('label'))
$(el).parent().find('span').text(data.label);
$(el).trigger('change');
}
});
inputBindings.register(checkboxInputBinding, 'shiny.checkboxInput');

View File

@@ -0,0 +1,100 @@
var checkboxGroupInputBinding = new InputBinding();
$.extend(checkboxGroupInputBinding, {
find: function(scope) {
return $(scope).find('.shiny-input-checkboxgroup');
},
getValue: function(el) {
// Select the checkbox objects that have name equal to the grouping div's id
var $objs = $('input:checkbox[name="' + $escape(el.id) + '"]:checked');
var values = new Array($objs.length);
for (var i = 0; i < $objs.length; i ++) {
values[i] = $objs[i].value;
}
return values;
},
setValue: function(el, value) {
// Clear all checkboxes
$('input:checkbox[name="' + $escape(el.id) + '"]').prop('checked', false);
// Accept array
if (value instanceof Array) {
for (var i = 0; i < value.length; i++) {
$('input:checkbox[name="' + $escape(el.id) + '"][value="' + $escape(value[i]) + '"]')
.prop('checked', true);
}
// Else assume it's a single value
} else {
$('input:checkbox[name="' + $escape(el.id) + '"][value="' + $escape(value) + '"]')
.prop('checked', true);
}
},
getState: function(el) {
var $objs = $('input:checkbox[name="' + $escape(el.id) + '"]');
// Store options in an array of objects, each with with value and label
var options = new Array($objs.length);
for (var i = 0; i < options.length; i++) {
options[i] = { value: $objs[i].value,
label: this._getLabel($objs[i]) };
}
return { label: this._getLabelNode(el).text(),
value: this.getValue(el),
options: options
};
},
receiveMessage: function(el, data) {
var $el = $(el);
// This will replace all the options
if (data.hasOwnProperty('options')) {
// Clear existing options and add each new one
$el.find('div.shiny-options-group').remove();
// Backward compatibility: for HTML generated by shinybootstrap2 package
$el.find('label.checkbox').remove();
$el.append(data.options);
}
if (data.hasOwnProperty('value'))
this.setValue(el, data.value);
updateLabel(data.label, this._getLabelNode(el));
$(el).trigger('change');
},
subscribe: function(el, callback) {
$(el).on('change.checkboxGroupInputBinding', function(event) {
callback();
});
},
unsubscribe: function(el) {
$(el).off('.checkboxGroupInputBinding');
},
// Get the DOM element that contains the top-level label
_getLabelNode: function(el) {
return $(el).find('label[for="' + $escape(el.id) + '"]');
},
// Given an input DOM object, get the associated label. Handles labels
// that wrap the input as well as labels associated with 'for' attribute.
_getLabel: function(obj) {
// If <label><input /><span>label text</span></label>
if (obj.parentNode.tagName === "LABEL") {
return $(obj.parentNode).find('span').text().trim();
}
return null;
},
// Given an input DOM object, set the associated label. Handles labels
// that wrap the input as well as labels associated with 'for' attribute.
_setLabel: function(obj, value) {
// If <label><input /><span>label text</span></label>
if (obj.parentNode.tagName === "LABEL") {
$(obj.parentNode).find('span').text(value);
}
return null;
}
});
inputBindings.register(checkboxGroupInputBinding, 'shiny.checkboxGroupInput');

View File

@@ -0,0 +1,249 @@
var dateInputBinding = new InputBinding();
$.extend(dateInputBinding, {
find: function(scope) {
return $(scope).find('.shiny-date-input');
},
getType: function(el) {
return "shiny.date";
},
// Return the date in an unambiguous format, yyyy-mm-dd (as opposed to a
// format like mm/dd/yyyy)
getValue: function(el) {
var date = $(el).find('input').bsDatepicker('getUTCDate');
return formatDateUTC(date);
},
// value must be an unambiguous string like '2001-01-01', or a Date object.
setValue: function(el, value) {
// R's NA, which is null here will remove current value
if (value === null) {
$(el).find('input').val('').bsDatepicker('update');
return;
}
var date = this._newDate(value);
// If date is invalid, do nothing
if (isNaN(date))
return;
$(el).find('input').bsDatepicker('setUTCDate', date);
},
getState: function(el) {
var $el = $(el);
var $input = $el.find('input');
var min = $input.data('datepicker').startDate;
var max = $input.data('datepicker').endDate;
// Stringify min and max. If min and max aren't set, they will be
// -Infinity and Infinity; replace these with null.
min = (min === -Infinity) ? null : formatDateUTC(min);
max = (max === Infinity) ? null : formatDateUTC(max);
// startViewMode is stored as a number; convert to string
var startview = $input.data('datepicker').startViewMode;
if (startview === 2) startview = 'decade';
else if (startview === 1) startview = 'year';
else if (startview === 0) startview = 'month';
return {
label: this._getLabelNode(el).text(),
value: this.getValue(el),
valueString: $input.val(),
min: min,
max: max,
language: $input.data('datepicker').language,
weekstart: $input.data('datepicker').weekStart,
format: this._formatToString($input.data('datepicker').format),
startview: startview
};
},
receiveMessage: function(el, data) {
var $input = $(el).find('input');
updateLabel(data.label, this._getLabelNode(el));
if (data.hasOwnProperty('min'))
this._setMin($input[0], data.min);
if (data.hasOwnProperty('max'))
this._setMax($input[0], data.max);
// Must set value only after min and max have been set. If new value is
// outside the bounds of the previous min/max, then the result will be a
// blank input.
if (data.hasOwnProperty('value'))
this.setValue(el, data.value);
$(el).trigger('change');
},
subscribe: function(el, callback) {
$(el).on('keyup.dateInputBinding input.dateInputBinding', function(event) {
// Use normal debouncing policy when typing
callback(true);
});
$(el).on('changeDate.dateInputBinding change.dateInputBinding', function(event) {
// Send immediately when clicked
callback(false);
});
},
unsubscribe: function(el) {
$(el).off('.dateInputBinding');
},
getRatePolicy: function() {
return {
policy: 'debounce',
delay: 250
};
},
initialize: function(el) {
var $input = $(el).find('input');
// The challenge with dates is that we want them to be at 00:00 in UTC so
// that we can do comparisons with them. However, the Date object itself
// does not carry timezone information, so we should call _floorDateTime()
// on Dates as soon as possible so that we know we're always working with
// consistent objects.
var date = $input.data('initial-date');
// If initial_date is null, set to current date
if (date === undefined || date === null) {
// Get local date, but normalized to beginning of day in UTC.
date = this._floorDateTime(this._dateAsUTC(new Date()));
}
this.setValue(el, date);
// Set the start and end dates, from min-date and max-date. These always
// use yyyy-mm-dd format, instead of bootstrap-datepicker's built-in
// support for date-startdate and data-enddate, which use the current
// date format.
if ($input.data('min-date') !== undefined) {
this._setMin($input[0], $input.data('min-date'));
}
if ($input.data('max-date') !== undefined) {
this._setMax($input[0], $input.data('max-date'));
}
},
_getLabelNode: function(el) {
return $(el).find('label[for="' + $escape(el.id) + '"]');
},
// Given a format object from a date picker, return a string
_formatToString: function(format) {
// Format object has structure like:
// { parts: ['mm', 'dd', 'yy'], separators: ['', '/', '/' ,''] }
var str = '';
for (var i = 0; i < format.parts.length; i++) {
str += format.separators[i] + format.parts[i];
}
str += format.separators[i];
return str;
},
// Given an unambiguous date string or a Date object, set the min (start) date.
// null will unset. undefined will result in no change,
_setMin: function(el, date) {
if (date === undefined)
return;
if (date === null) {
$(el).bsDatepicker('setStartDate', null);
return;
}
date = this._newDate(date);
// If date parsing fails, do nothing
if (date === null)
return;
if (isNaN(date))
return;
// Workarounds for
// https://github.com/rstudio/shiny/issues/2335
var curValue = $(el).bsDatepicker('getUTCDate');
// Note that there's no `setUTCStartDate`, so we need to convert this Date.
// It starts at 00:00 UTC, and we convert it to 00:00 in local time, which
// is what's needed for `setStartDate`.
$(el).bsDatepicker('setStartDate', this._UTCDateAsLocal(date));
// If the new min is greater than the current date, unset the current date.
if (date && curValue && date.getTime() > curValue.getTime()) {
$(el).bsDatepicker('clearDates');
} else {
// Setting the date needs to be done AFTER `setStartDate`, because the
// datepicker has a bug where calling `setStartDate` will clear the date
// internally (even though it will still be visible in the UI) when a
// 2-digit year format is used.
// https://github.com/eternicode/bootstrap-datepicker/issues/2010
$(el).bsDatepicker('setUTCDate', curValue);
}
},
// Given an unambiguous date string or a Date object, set the max (end) date
// null will unset.
_setMax: function(el, date) {
if (date === undefined)
return;
if (date === null) {
$(el).bsDatepicker('setEndDate', null);
return;
}
date = this._newDate(date);
// If date parsing fails, do nothing
if (date === null)
return;
if (isNaN(date))
return;
// Workaround for same issue as in _setMin.
var curValue = $(el).bsDatepicker('getUTCDate');
$(el).bsDatepicker('setEndDate', this._UTCDateAsLocal(date));
// If the new min is greater than the current date, unset the current date.
if (date && curValue && date.getTime() < curValue.getTime()) {
$(el).bsDatepicker('clearDates');
} else {
$(el).bsDatepicker('setUTCDate', curValue);
}
},
// Given a date string of format yyyy-mm-dd, return a Date object with
// that date at 12AM UTC.
// If date is a Date object, return it unchanged.
_newDate: function(date) {
if (date instanceof Date)
return date;
if (!date)
return null;
// Get Date object - this will be at 12AM in UTC, but may print
// differently at the Javascript console.
var d = parseDate(date);
// If invalid date, return null
if (isNaN(d))
return null;
return d;
},
// A Date can have any time during a day; this will return a new Date object
// set to 00:00 in UTC.
_floorDateTime: function(date) {
date = new Date(date.getTime());
date.setUTCHours(0, 0, 0, 0);
return date;
},
// Given a Date object, return a Date object which has the same "clock time"
// in UTC. For example, if input date is 2013-02-01 23:00:00 GMT-0600 (CST),
// output will be 2013-02-01 23:00:00 UTC. Note that the JS console may
// print this in local time, as "Sat Feb 02 2013 05:00:00 GMT-0600 (CST)".
_dateAsUTC: function(date) {
return new Date(date.getTime() - date.getTimezoneOffset() * 60000);
},
// The inverse of _dateAsUTC. This is needed to adjust time zones because
// some bootstrap-datepicker methods only take local dates as input, and not
// UTC.
_UTCDateAsLocal: function(date) {
return new Date(date.getTime() + date.getTimezoneOffset() * 60000);
}
});
inputBindings.register(dateInputBinding, 'shiny.dateInput');

View File

@@ -0,0 +1,147 @@
var dateRangeInputBinding = {};
$.extend(dateRangeInputBinding, dateInputBinding, {
find: function(scope) {
return $(scope).find('.shiny-date-range-input');
},
// Return the date in an unambiguous format, yyyy-mm-dd (as opposed to a
// format like mm/dd/yyyy)
getValue: function(el) {
var $inputs = $(el).find('input');
var start = $inputs.eq(0).bsDatepicker('getUTCDate');
var end = $inputs.eq(1).bsDatepicker('getUTCDate');
return [formatDateUTC(start), formatDateUTC(end)];
},
// value must be an object, with optional fields `start` and `end`. These
// should be unambiguous strings like '2001-01-01', or Date objects.
setValue: function(el, value) {
if (!(value instanceof Object)) {
return;
}
// Get the start and end input objects
var $inputs = $(el).find('input');
// If value is undefined, don't try to set
// null will remove the current value
if (value.start !== undefined) {
if (value.start === null) {
$inputs.eq(0).val('').bsDatepicker('update');
} else {
var start = this._newDate(value.start);
$inputs.eq(0).bsDatepicker('setUTCDate', start);
}
}
if (value.end !== undefined) {
if (value.end === null) {
$inputs.eq(1).val('').bsDatepicker('update');
} else {
var end = this._newDate(value.end);
$inputs.eq(1).bsDatepicker('setUTCDate', end);
}
}
},
getState: function(el) {
var $el = $(el);
var $inputs = $el.find('input');
var $startinput = $inputs.eq(0);
var $endinput = $inputs.eq(1);
// For many of the properties, assume start and end have the same values
var min = $startinput.bsDatepicker('getStartDate');
var max = $startinput.bsDatepicker('getEndDate');
// Stringify min and max. If min and max aren't set, they will be
// -Infinity and Infinity; replace these with null.
min = (min === -Infinity) ? null : formatDateUTC(min);
max = (max === Infinity) ? null : formatDateUTC(max);
// startViewMode is stored as a number; convert to string
var startview = $startinput.data('datepicker').startView;
if (startview === 2) startview = 'decade';
else if (startview === 1) startview = 'year';
else if (startview === 0) startview = 'month';
return {
label: this._getLabelNode(el).text(),
value: this.getValue(el),
valueString: [ $startinput.val(), $endinput.val() ],
min: min,
max: max,
weekstart: $startinput.data('datepicker').weekStart,
format: this._formatToString($startinput.data('datepicker').format),
language: $startinput.data('datepicker').language,
startview: startview
};
},
receiveMessage: function(el, data) {
var $el = $(el);
var $inputs = $el.find('input');
var $startinput = $inputs.eq(0);
var $endinput = $inputs.eq(1);
updateLabel(data.label, this._getLabelNode(el));
if (data.hasOwnProperty('min')) {
this._setMin($startinput[0], data.min);
this._setMin($endinput[0], data.min);
}
if (data.hasOwnProperty('max')) {
this._setMax($startinput[0], data.max);
this._setMax($endinput[0], data.max);
}
// Must set value only after min and max have been set. If new value is
// outside the bounds of the previous min/max, then the result will be a
// blank input.
if (data.hasOwnProperty('value'))
this.setValue(el, data.value);
$el.trigger('change');
},
initialize: function(el) {
var $el = $(el);
var $inputs = $el.find('input');
var $startinput = $inputs.eq(0);
var $endinput = $inputs.eq(1);
var start = $startinput.data('initial-date');
var end = $endinput.data('initial-date');
// If empty/null, use local date, but as UTC
if (start === undefined || start === null)
start = this._dateAsUTC(new Date());
if (end === undefined || end === null)
end = this._dateAsUTC(new Date());
this.setValue(el, { "start": start, "end": end });
// // Set the start and end dates, from min-date and max-date. These always
// // use yyyy-mm-dd format, instead of bootstrap-datepicker's built-in
// // support for date-startdate and data-enddate, which use the current
// // date format.
this._setMin($startinput[0], $startinput.data('min-date'));
this._setMin($endinput[0], $startinput.data('min-date'));
this._setMax($startinput[0], $endinput.data('max-date'));
this._setMax($endinput[0], $endinput.data('max-date'));
},
subscribe: function(el, callback) {
$(el).on('keyup.dateRangeInputBinding input.dateRangeInputBinding', function(event) {
// Use normal debouncing policy when typing
callback(true);
});
$(el).on('changeDate.dateRangeInputBinding change.dateRangeInputBinding', function(event) {
// Send immediately when clicked
callback(false);
});
},
unsubscribe: function(el) {
$(el).off('.dateRangeInputBinding');
},
_getLabelNode: function(el) {
return $(el).find('label[for="' + $escape(el.id) + '"]');
},
});
inputBindings.register(dateRangeInputBinding, 'shiny.dateRangeInput');

View File

@@ -0,0 +1,401 @@
var FileUploader = function(shinyapp, id, files, el) {
this.shinyapp = shinyapp;
this.id = id;
this.el = el;
FileProcessor.call(this, files);
};
$.extend(FileUploader.prototype, FileProcessor.prototype);
(function() {
this.makeRequest = function(method, args, onSuccess, onFailure, blobs) {
this.shinyapp.makeRequest(method, args, onSuccess, onFailure, blobs);
};
this.onBegin = function(files, cont) {
var self = this;
// Reset progress bar
this.$setError(null);
this.$setActive(true);
this.$setVisible(true);
this.onProgress(null, 0);
this.totalBytes = 0;
this.progressBytes = 0;
$.each(files, function(i, file) {
self.totalBytes += file.size;
});
var fileInfo = $.map(files, function(file, i) {
return {
name: file.name,
size: file.size,
type: file.type
};
});
this.makeRequest(
'uploadInit', [fileInfo],
function(response) {
self.jobId = response.jobId;
self.uploadUrl = response.uploadUrl;
cont();
},
function(error) {
self.onError(error);
});
};
this.onFile = function(file, cont) {
var self = this;
this.onProgress(file, 0);
$.ajax(this.uploadUrl, {
type: 'POST',
cache: false,
xhr: function() {
var xhrVal = $.ajaxSettings.xhr();
if (xhrVal.upload) {
xhrVal.upload.onprogress = function(e) {
if (e.lengthComputable) {
self.onProgress(
file,
(self.progressBytes + e.loaded) / self.totalBytes);
}
};
}
return xhrVal;
},
data: file,
contentType: 'application/octet-stream',
processData: false,
success: function() {
self.progressBytes += file.size;
cont();
},
error: function(jqXHR, textStatus, errorThrown) {
self.onError(jqXHR.responseText || textStatus);
}
});
};
this.onComplete = function() {
var self = this;
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 || '');
this.$setActive(false);
};
this.onAbort = function() {
this.$setVisible(false);
};
this.onProgress = function(file, completed) {
this.$bar().width(Math.round(completed*100) + '%');
this.$bar().text(file ? file.name : '');
};
this.$container = function() {
return $('#' + $escape(this.id) + '_progress.shiny-file-input-progress');
};
this.$bar = function() {
return $('#' + $escape(this.id) + '_progress.shiny-file-input-progress .progress-bar');
};
this.$setVisible = function(visible) {
this.$container().css('visibility', visible ? 'visible' : 'hidden');
};
this.$setError = function(error) {
this.$bar().toggleClass('progress-bar-danger', (error !== null));
if (error !== null) {
this.onProgress(null, 1);
this.$bar().text(error);
}
};
this.$setActive = function(active) {
this.$container().toggleClass('active', !!active);
};
}).call(FileUploader.prototype);
// 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;
var id = fileInputBinding.getId(evt.target);
if (files.length === 0)
return;
// 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, 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(scope) {
return $(scope).find('input[type="file"]');
},
getId: function(el) {
return InputBinding.prototype.getId.call(this, el) || el.name;
},
getValue: function(el) {
// This returns a non-undefined value only when there's a 'data-restore'
// attribute, which is set only when restoring Shiny state. If a file is
// uploaded through the browser, 'data-restore' gets cleared.
var data = $(el).attr('data-restore');
if (data) {
data = JSON.parse(data);
// Set the label in the text box
var $fileText = $(el).closest('div.input-group').find('input[type=text]');
if (data.name.length === 1) {
$fileText.val(data.name[0]);
} else {
$fileText.val(data.name.length + " files");
}
// Manually set up progress bar. A bit inelegant because it duplicates
// code from FileUploader, but duplication is less bad than alternatives.
var $progress = $(el).closest('div.form-group').find('.progress');
var $bar = $progress.find('.progress-bar');
$progress.removeClass('active');
$bar.width('100%');
$bar.css('visibility', 'visible');
return data;
} else {
return null;
}
},
setValue: function(el, value) {
// Not implemented
},
getType: function(el) {
// This will be used only when restoring a file from a saved state.
return 'shiny.file';
},
_zoneOf: function(el) {
return $(el).closest("div.input-group");
},
// This function makes it possible to attach listeners to the dragenter,
// dragleave, and drop events of a single element with children. It's not
// intuitive to do directly because outer elements fire "dragleave" events
// both when the drag leaves the element and when the drag enters a child. To
// make it easier, we maintain a count of the elements being dragged across
// and trigger 3 new types of event:
//
// 1. draghover:enter - When a drag enters el and any of its children.
// 2. draghover:leave - When the drag leaves el and all of its children.
// 3. draghover:drop - When an item is dropped on el or any of its children.
_enableDraghover: function(el) {
let $el = $(el),
childCounter = 0;
$el.on({
"dragenter.draghover": e => {
if (childCounter++ === 0) {
$el.trigger("draghover:enter", e);
}
},
"dragleave.draghover": e => {
if (--childCounter === 0) {
$el.trigger("draghover:leave", e);
}
if (childCounter < 0) {
console.error("draghover childCounter is negative somehow");
}
},
"dragover.draghover": e => {
e.preventDefault();
},
"drop.draghover": e => {
childCounter = 0;
$el.trigger("draghover:drop", e);
e.preventDefault();
}
});
return $el;
},
_disableDraghover: function(el) {
return $(el).off(".draghover");
},
_ZoneClass: {
ACTIVE: "shiny-file-input-active",
OVER: "shiny-file-input-over"
},
_enableDocumentEvents: function() {
let $doc = $("html"),
{ACTIVE, OVER} = this._ZoneClass;
this._enableDraghover($doc)
.on({
"draghover:enter.draghover": e => {
this._zoneOf($fileInputs).addClass(ACTIVE);
},
"draghover:leave.draghover": e => {
this._zoneOf($fileInputs).removeClass(ACTIVE);
},
"draghover:drop.draghover": e => {
this._zoneOf($fileInputs)
.removeClass(OVER)
.removeClass(ACTIVE);
}
});
},
_disableDocumentEvents: function() {
let $doc = $("html");
$doc.off(".draghover");
this._disableDraghover($doc);
},
_canSetFiles: function(fileList) {
var testEl = document.createElement("input");
testEl.type = "file";
try {
testEl.files = fileList;
} catch (e) {
return false;
}
return true;
},
_handleDrop: function(e, el) {
const 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;
// Recent versions of Firefox (57+, or "Quantum" and beyond) don't seem to
// automatically trigger a change event, so we trigger one manually here.
// On browsers that do trigger change, this operation appears to be
// idempotent, as el.files doesn't change between events.
$el.trigger("change");
}
},
subscribe: function(el, callback) {
$(el).on("change.fileInputBinding", uploadFiles);
// Here we try to set up the necessary events for Drag and Drop ("DnD").
if ($fileInputs.length === 0) this._enableDocumentEvents();
$fileInputs = $fileInputs.add(el);
let $zone = this._zoneOf(el),
{OVER} = this._ZoneClass;
this._enableDraghover($zone)
.on({
"draghover:enter.draghover": e => {
$zone.addClass(OVER);
},
"draghover:leave.draghover": e => {
$zone.removeClass(OVER);
// Prevent this event from bubbling to the document handler,
// which would deactivate all zones.
e.stopPropagation();
},
"draghover:drop.draghover": (e, dropEvent) => {
this._handleDrop(dropEvent, el);
}
});
},
unsubscribe: function(el) {
let $el = $(el),
$zone = this._zoneOf(el);
$zone
.removeClass(this._ZoneClass.OVER)
.removeClass(this._ZoneClass.ACTIVE);
this._disableDraghover($zone);
$el.off(".fileInputBinding");
$zone.off(".draghover");
// 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');

View File

@@ -0,0 +1,42 @@
var numberInputBinding = {};
$.extend(numberInputBinding, textInputBinding, {
find: function(scope) {
return $(scope).find('input[type="number"]');
},
getValue: function(el) {
var numberVal = $(el).val();
if (/^\s*$/.test(numberVal)) // Return null if all whitespace
return null;
else if (!isNaN(numberVal)) // If valid Javascript number string, coerce to number
return +numberVal;
else
return numberVal; // If other string like "1e6", send it unchanged
},
setValue: function(el, value) {
el.value = value;
},
getType: function(el) {
return "shiny.number";
},
receiveMessage: function(el, data) {
if (data.hasOwnProperty('value')) el.value = data.value;
if (data.hasOwnProperty('min')) el.min = data.min;
if (data.hasOwnProperty('max')) el.max = data.max;
if (data.hasOwnProperty('step')) el.step = data.step;
updateLabel(data.label, this._getLabelNode(el));
$(el).trigger('change');
},
getState: function(el) {
return { label: this._getLabelNode(el).text(),
value: this.getValue(el),
min: Number(el.min),
max: Number(el.max),
step: Number(el.step) };
},
_getLabelNode: function(el) {
return $(el).parent().find('label[for="' + $escape(el.id) + '"]');
}
});
inputBindings.register(numberInputBinding, 'shiny.numberInput');

View File

@@ -0,0 +1,10 @@
var passwordInputBinding = {};
$.extend(passwordInputBinding, textInputBinding, {
find: function(scope) {
return $(scope).find('input[type="password"]');
},
getType: function(el) {
return "shiny.password";
}
});
inputBindings.register(passwordInputBinding, 'shiny.passwordInput');

View File

@@ -0,0 +1,95 @@
var radioInputBinding = new InputBinding();
$.extend(radioInputBinding, {
find: function(scope) {
return $(scope).find('.shiny-input-radiogroup');
},
getValue: function(el) {
// Select the radio objects that have name equal to the grouping div's id
var checked_items = $('input:radio[name="' + $escape(el.id) + '"]:checked');
if (checked_items.length === 0) {
// If none are checked, the input will return null (it's the default on load,
// but it wasn't emptied when calling updateRadioButtons with character(0)
return null;
}
return checked_items.val();
},
setValue: function(el, value) {
if ($.isArray(value) && value.length === 0) {
// Removing all checked item if the sent data is empty
$('input:radio[name="' + $escape(el.id) + '"]').prop('checked', false);
} else {
$('input:radio[name="' + $escape(el.id) + '"][value="' + $escape(value) + '"]').prop('checked', true);
}
},
getState: function(el) {
var $objs = $('input:radio[name="' + $escape(el.id) + '"]');
// Store options in an array of objects, each with with value and label
var options = new Array($objs.length);
for (var i = 0; i < options.length; i++) {
options[i] = { value: $objs[i].value,
label: this._getLabel($objs[i]) };
}
return {
label: this._getLabelNode(el).text(),
value: this.getValue(el),
options: options
};
},
receiveMessage: function(el, data) {
var $el = $(el);
// This will replace all the options
if (data.hasOwnProperty('options')) {
// Clear existing options and add each new one
$el.find('div.shiny-options-group').remove();
// Backward compatibility: for HTML generated by shinybootstrap2 package
$el.find('label.radio').remove();
$el.append(data.options);
}
if (data.hasOwnProperty('value'))
this.setValue(el, data.value);
updateLabel(data.label, this._getLabelNode(el));
$(el).trigger('change');
},
subscribe: function(el, callback) {
$(el).on('change.radioInputBinding', function(event) {
callback();
});
},
unsubscribe: function(el) {
$(el).off('.radioInputBinding');
},
// Get the DOM element that contains the top-level label
_getLabelNode: function(el) {
return $(el).parent().find('label[for="' + $escape(el.id) + '"]');
},
// Given an input DOM object, get the associated label. Handles labels
// that wrap the input as well as labels associated with 'for' attribute.
_getLabel: function(obj) {
// If <label><input /><span>label text</span></label>
if (obj.parentNode.tagName === "LABEL") {
return $(obj.parentNode).find('span').text().trim();
}
return null;
},
// Given an input DOM object, set the associated label. Handles labels
// that wrap the input as well as labels associated with 'for' attribute.
_setLabel: function(obj, value) {
// If <label><input /><span>label text</span></label>
if (obj.parentNode.tagName === "LABEL") {
$(obj.parentNode).find('span').text(value);
}
return null;
}
});
inputBindings.register(radioInputBinding, 'shiny.radioInput');

View File

@@ -0,0 +1,206 @@
var selectInputBinding = new InputBinding();
$.extend(selectInputBinding, {
find: function(scope) {
return $(scope).find('select');
},
getType: function(el) {
var $el = $(el);
if (!$el.hasClass("symbol")) {
// default character type
return null;
}
if ($el.attr("multiple") === "multiple") {
return 'shiny.symbolList';
} else {
return 'shiny.symbol';
}
},
getId: function(el) {
return InputBinding.prototype.getId.call(this, el) || el.name;
},
getValue: function(el) {
return $(el).val();
},
setValue: function(el, value) {
if (!this._is_selectize(el)) {
$(el).val(value);
} else {
let selectize = this._selectize(el);
if (selectize) {
selectize.setValue(value);
}
}
},
getState: function(el) {
// Store options in an array of objects, each with with value and label
var options = new Array(el.length);
for (var i = 0; i < el.length; i++) {
options[i] = { value: el[i].value,
label: el[i].label };
}
return {
label: this._getLabelNode(el),
value: this.getValue(el),
options: options
};
},
receiveMessage: function(el, data) {
var $el = $(el), selectize;
// This will replace all the options
if (data.hasOwnProperty('options')) {
selectize = this._selectize(el);
// Must destroy selectize before appending new options, otherwise
// selectize will restore the original select
if (selectize) selectize.destroy();
// Clear existing options and add each new one
$el.empty().append(data.options);
this._selectize(el);
}
// re-initialize selectize
if (data.hasOwnProperty('config')) {
$el.parent()
.find('script[data-for="' + $escape(el.id) + '"]')
.replaceWith(data.config);
this._selectize(el, true);
}
// use server-side processing for selectize
if (data.hasOwnProperty('url')) {
selectize = this._selectize(el);
selectize.clearOptions();
var loaded = false;
selectize.settings.load = function(query, callback) {
var settings = selectize.settings;
$.ajax({
url: data.url,
data: {
query: query,
field: JSON.stringify([settings.searchField]),
value: settings.valueField,
conju: settings.searchConjunction,
maxop: settings.maxOptions
},
type: 'GET',
error: function() {
callback();
},
success: function(res) {
// res = [{label: '1', value: '1', group: '1'}, ...]
// success is called after options are added, but
// groups need to be added manually below
$.each(res, function(index, elem) {
// Call selectize.addOptionGroup once for each optgroup; the
// first argument is the group ID, the second is an object with
// the group's label and value. We use the current settings of
// the selectize object to decide the fieldnames of that obj.
let optgroupId = elem[settings.optgroupField || "optgroup"];
let optgroup = {};
optgroup[settings.optgroupLabelField || "label"] = optgroupId;
optgroup[settings.optgroupValueField || "value"] = optgroupId;
selectize.addOptionGroup(optgroupId, optgroup);
});
callback(res);
if (!loaded) {
if (data.hasOwnProperty('value')) {
selectize.setValue(data.value);
} else if (settings.maxItems === 1) {
// first item selected by default only for single-select
selectize.setValue(res[0].value);
}
}
loaded = true;
}
});
};
// perform an empty search after changing the `load` function
selectize.load(function(callback) {
selectize.settings.load.apply(selectize, ['', callback]);
});
} else if (data.hasOwnProperty('value')) {
this.setValue(el, data.value);
}
updateLabel(data.label, this._getLabelNode(el));
$(el).trigger('change');
},
subscribe: function(el, callback) {
$(el).on('change.selectInputBinding', event => {
// https://github.com/rstudio/shiny/issues/2162
// Prevent spurious events that are gonna be squelched in
// a second anyway by the onItemRemove down below
if (el.nonempty && this.getValue(el) === "") {
return;
}
callback();
});
},
unsubscribe: function(el) {
$(el).off('.selectInputBinding');
},
initialize: function(el) {
this._selectize(el);
},
_getLabelNode: function(el) {
let escaped_id = $escape(el.id);
if (this._is_selectize(el)) {
escaped_id += "-selectized";
}
return $(el).parent().parent().find('label[for="' + escaped_id + '"]');
},
// Return true if it's a selectize input, false if it's a regular select input.
_is_selectize: function(el) {
var config = $(el).parent().find('script[data-for="' + $escape(el.id) + '"]');
return (config.length > 0);
},
_selectize: function(el, update) {
if (!$.fn.selectize) return undefined;
var $el = $(el);
var config = $el.parent().find('script[data-for="' + $escape(el.id) + '"]');
if (config.length === 0) return undefined;
var options = $.extend({
labelField: 'label',
valueField: 'value',
searchField: ['label']
}, JSON.parse(config.html()));
// selectize created from selectInput()
if (typeof(config.data('nonempty')) !== 'undefined') {
el.nonempty = true;
options = $.extend(options, {
onItemRemove: function(value) {
if (this.getValue() === "")
$("select#" + $escape(el.id)).empty().append($("<option/>", {
"value": value,
"selected": true
})).trigger("change");
},
onDropdownClose: function($dropdown) {
if (this.getValue() === "")
this.setValue($("select#" + $escape(el.id)).val());
}
});
} else {
el.nonempty = false;
}
// options that should be eval()ed
if (config.data('eval') instanceof Array)
$.each(config.data('eval'), function(i, x) {
/*jshint evil: true*/
options[x] = eval('(' + options[x] + ')');
});
var control = $el.selectize(options)[0].selectize;
// .selectize() does not really update settings; must destroy and rebuild
if (update) {
var settings = $.extend(control.settings, options);
control.destroy();
control = $el.selectize(settings)[0].selectize;
}
return control;
}
});
inputBindings.register(selectInputBinding, 'shiny.selectInput');

View File

@@ -0,0 +1,311 @@
// Necessary to get hidden sliders to send their updated values
function forceIonSliderUpdate(slider) {
if (slider.$cache && slider.$cache.input)
slider.$cache.input.trigger('change');
else
console.log("Couldn't force ion slider to update");
}
function getTypePrettifyer(dataType, timeFormat, timezone) {
var timeFormatter;
var prettify;
if (dataType === 'date') {
timeFormatter = strftime.utc();
prettify = function(num) {
return timeFormatter(timeFormat, new Date(num));
};
} else if (dataType === 'datetime') {
if (timezone)
timeFormatter = strftime.timezone(timezone);
else
timeFormatter = strftime;
prettify = function(num) {
return timeFormatter(timeFormat, new Date(num));
};
} else {
// The default prettify function for ion.rangeSlider adds thousands
// separators after the decimal mark, so we have our own version here.
// (#1958)
prettify = function(num) {
// When executed, `this` will refer to the `IonRangeSlider.options`
// object.
return formatNumber(num, this.prettify_separator);
};
}
return prettify;
}
var sliderInputBinding = {};
$.extend(sliderInputBinding, textInputBinding, {
find: function(scope) {
// Check if ionRangeSlider plugin is loaded
if (!$.fn.ionRangeSlider)
return [];
return $(scope).find('input.js-range-slider');
},
getType: function(el) {
var dataType = $(el).data('data-type');
if (dataType === 'date')
return 'shiny.date';
else if (dataType === 'datetime')
return 'shiny.datetime';
else
return false;
},
getValue: function(el) {
var $el = $(el);
var result = $(el).data('ionRangeSlider').result;
// Function for converting numeric value from slider to appropriate type.
var convert;
var dataType = $el.data('data-type');
if (dataType === 'date') {
convert = function(val) {
return formatDateUTC(new Date(+val));
};
} else if (dataType === 'datetime') {
convert = function(val) {
// Convert ms to s
return +val / 1000;
};
} else {
convert = function(val) { return +val; };
}
if (this._numValues(el) === 2) {
return [convert(result.from), convert(result.to)];
}
else {
return convert(result.from);
}
},
setValue: function(el, value) {
var $el = $(el);
var slider = $el.data('ionRangeSlider');
$el.data('immediate', true);
try {
if (this._numValues(el) === 2 && value instanceof Array) {
slider.update({ from: value[0], to: value[1] });
} else {
slider.update({ from: value });
}
forceIonSliderUpdate(slider);
} finally {
$el.data('immediate', false);
}
},
subscribe: function(el, callback) {
$(el).on('change.sliderInputBinding', function(event) {
callback(!$(el).data('immediate') && !$(el).data('animating'));
});
},
unsubscribe: function(el) {
$(el).off('.sliderInputBinding');
},
receiveMessage: function(el, data) {
var $el = $(el);
var slider = $el.data('ionRangeSlider');
var msg = {};
if (data.hasOwnProperty('value')) {
if (this._numValues(el) === 2 && data.value instanceof Array) {
msg.from = data.value[0];
msg.to = data.value[1];
} else {
msg.from = data.value;
}
}
var sliderFeatures = ['min', 'max', 'step'];
for (var i = 0; i < sliderFeatures.length; i++) {
var feats = sliderFeatures[i];
if (data.hasOwnProperty(feats)) {
msg[feats] = data[feats];
}
}
updateLabel(data.label, this._getLabelNode(el));
var domElements = ['data-type', 'time-format', 'timezone'];
for (var i = 0; i < domElements.length; i++) {
var elem = domElements[i];
if (data.hasOwnProperty(elem)) {
$el.data(elem, data[elem]);
}
}
var dataType = $el.data('data-type');
var timeFormat = $el.data('time-format');
var timezone = $el.data('timezone');
msg.prettify = getTypePrettifyer(dataType, timeFormat, timezone);
$el.data('immediate', true);
try {
slider.update(msg);
forceIonSliderUpdate(slider);
} finally {
$el.data('immediate', false);
}
},
getRatePolicy: function() {
return {
policy: 'debounce',
delay: 250
};
},
getState: function(el) {
},
initialize: function(el) {
var opts = {};
var $el = $(el);
var dataType = $el.data('data-type');
var timeFormat = $el.data('time-format');
var timezone = $el.data('timezone');
opts.prettify = getTypePrettifyer(dataType, timeFormat, timezone);
$el.ionRangeSlider(opts);
},
_getLabelNode: function(el) {
return $(el).parent().find('label[for="' + $escape(el.id) + '"]');
},
// Number of values; 1 for single slider, 2 for range slider
_numValues: function(el) {
if ($(el).data('ionRangeSlider').options.type === 'double')
return 2;
else
return 1;
}
});
inputBindings.register(sliderInputBinding, 'shiny.sliderInput');
// Format numbers for nicer output.
// formatNumber(1234567.12345) === "1,234,567.12345"
// formatNumber(1234567.12345, ".", ",") === "1.234.567,12345"
// formatNumber(1000, " ") === "1 000"
// formatNumber(20) === "20"
// formatNumber(1.2345e24) === "1.2345e+24"
function formatNumber(num, thousand_sep = ",", decimal_sep = ".") {
let parts = num.toString().split(".");
// Add separators to portion before decimal mark.
parts[0] = parts[0].replace(/(\d{1,3}(?=(?:\d\d\d)+(?!\d)))/g, "$1" + thousand_sep);
if (parts.length === 1)
return parts[0];
else if (parts.length === 2)
return parts[0] + decimal_sep + parts[1];
else
return "";
};
$(document).on('click', '.slider-animate-button', function(evt) {
evt.preventDefault();
var self = $(this);
var target = $('#' + $escape(self.attr('data-target-id')));
var startLabel = 'Play';
var stopLabel = 'Pause';
var loop = self.attr('data-loop') !== undefined &&
!/^\s*false\s*$/i.test(self.attr('data-loop'));
var animInterval = self.attr('data-interval');
if (isNaN(animInterval))
animInterval = 1500;
else
animInterval = +animInterval;
if (!target.data('animTimer')) {
var slider;
var timer;
// Separate code paths:
// Backward compatible code for old-style jsliders (Shiny <= 0.10.2.2),
// and new-style ionsliders.
if (target.hasClass('jslider')) {
slider = target.slider();
// If we're currently at the end, restart
if (!slider.canStepNext())
slider.resetToStart();
timer = setInterval(function() {
if (loop && !slider.canStepNext()) {
slider.resetToStart();
}
else {
slider.stepNext();
if (!loop && !slider.canStepNext()) {
self.click(); // stop the animation
}
}
}, animInterval);
} else {
slider = target.data('ionRangeSlider');
// Single sliders have slider.options.type == "single", and only the
// `from` value is used. Double sliders have type == "double", and also
// use the `to` value for the right handle.
var sliderCanStep = function() {
if (slider.options.type === "double")
return slider.result.to < slider.result.max;
else
return slider.result.from < slider.result.max;
};
var sliderReset = function() {
var val = { from: slider.result.min };
// Preserve the current spacing for double sliders
if (slider.options.type === "double")
val.to = val.from + (slider.result.to - slider.result.from);
slider.update(val);
forceIonSliderUpdate(slider);
};
var sliderStep = function() {
// Don't overshoot the end
var val = {
from: Math.min(slider.result.max, slider.result.from + slider.options.step)
};
if (slider.options.type === "double")
val.to = Math.min(slider.result.max, slider.result.to + slider.options.step);
slider.update(val);
forceIonSliderUpdate(slider);
};
// If we're currently at the end, restart
if (!sliderCanStep())
sliderReset();
timer = setInterval(function() {
if (loop && !sliderCanStep()) {
sliderReset();
}
else {
sliderStep();
if (!loop && !sliderCanStep()) {
self.click(); // stop the animation
}
}
}, animInterval);
}
target.data('animTimer', timer);
self.attr('title', stopLabel);
self.addClass('playing');
target.data('animating', true);
}
else {
clearTimeout(target.data('animTimer'));
target.removeData('animTimer');
self.attr('title', startLabel);
self.removeClass('playing');
target.removeData('animating');
}
});

View File

@@ -0,0 +1,53 @@
var bootstrapTabInputBinding = new InputBinding();
$.extend(bootstrapTabInputBinding, {
find: function(scope) {
return $(scope).find('ul.nav.shiny-tab-input');
},
getValue: function(el) {
var anchor = $(el).find('li:not(.dropdown).active').children('a');
if (anchor.length === 1)
return this._getTabName(anchor);
return null;
},
setValue: function(el, value) {
let self = this;
let success = false;
if (value) {
let 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(el) {
return { value: this.getValue(el) };
},
receiveMessage: function(el, data) {
if (data.hasOwnProperty('value'))
this.setValue(el, data.value);
$(el).trigger("change");
},
subscribe: function(el, callback) {
$(el).on('change shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding', function(event) {
callback();
});
},
unsubscribe: function(el) {
$(el).off('.bootstrapTabInputBinding');
},
_getTabName: function(anchor) {
return anchor.attr('data-value') || anchor.text();
}
});
inputBindings.register(bootstrapTabInputBinding, 'shiny.bootstrapTabInput');

View File

@@ -0,0 +1,59 @@
var textInputBinding = new InputBinding();
$.extend(textInputBinding, {
find: function(scope) {
var $inputs = $(scope).find('input[type="text"], input[type="search"], input[type="url"], input[type="email"]');
// selectize.js 0.12.4 inserts a hidden text input with an
// id that ends in '-selectized'. The .not() selector below
// is to prevent textInputBinding from accidentally picking up
// this hidden element as a shiny input (#2396)
return $inputs.not('input[type="text"][id$="-selectized"]');
},
getId: function(el) {
return InputBinding.prototype.getId.call(this, el) || el.name;
},
getValue: function(el) {
return el.value;
},
setValue: function(el, value) {
el.value = value;
},
subscribe: function(el, callback) {
$(el).on('keyup.textInputBinding input.textInputBinding', function(event) {
callback(true);
});
$(el).on('change.textInputBinding', function(event) {
callback(false);
});
},
unsubscribe: function(el) {
$(el).off('.textInputBinding');
},
receiveMessage: function(el, data) {
if (data.hasOwnProperty('value'))
this.setValue(el, data.value);
updateLabel(data.label, this._getLabelNode(el));
if (data.hasOwnProperty('placeholder'))
el.placeholder = data.placeholder;
$(el).trigger('change');
},
getState: function(el) {
return {
label: this._getLabelNode(el).text(),
value: el.value,
placeholder: el.placeholder
};
},
getRatePolicy: function() {
return {
policy: 'debounce',
delay: 250
};
},
_getLabelNode: function(el) {
return $(el).parent().find('label[for="' + $escape(el.id) + '"]');
}
});
inputBindings.register(textInputBinding, 'shiny.textInput');

View File

@@ -0,0 +1,7 @@
var textareaInputBinding = {};
$.extend(textareaInputBinding, textInputBinding, {
find: function(scope) {
return $(scope).find('textarea');
}
});
inputBindings.register(textareaInputBinding, 'shiny.textareaInput');

409
srcts/old/js/input_rate.js Normal file
View File

@@ -0,0 +1,409 @@
var Invoker = function(target, func) {
this.target = target;
this.func = func;
};
(function() {
this.normalCall =
this.immediateCall = function() {
this.func.apply(this.target, arguments);
};
}).call(Invoker.prototype);
var Debouncer = function(target, func, delayMs) {
this.target = target;
this.func = func;
this.delayMs = delayMs;
this.timerId = null;
this.args = null;
};
(function() {
this.normalCall = function() {
var self = this;
this.$clearTimer();
this.args = arguments;
this.timerId = setTimeout(function() {
// IE8 doesn't reliably clear timeout, so this additional
// check is needed
if (self.timerId === null)
return;
self.$clearTimer();
self.$invoke();
}, this.delayMs);
};
this.immediateCall = function() {
this.$clearTimer();
this.args = arguments;
this.$invoke();
};
this.isPending = function() {
return this.timerId !== null;
};
this.$clearTimer = function() {
if (this.timerId !== null) {
clearTimeout(this.timerId);
this.timerId = null;
}
};
this.$invoke = function() {
this.func.apply(this.target, this.args);
this.args = null;
};
}).call(Debouncer.prototype);
var Throttler = function(target, func, delayMs) {
this.target = target;
this.func = func;
this.delayMs = delayMs;
this.timerId = null;
this.args = null;
};
(function() {
this.normalCall = function() {
var self = this;
this.args = arguments;
if (this.timerId === null) {
this.$invoke();
this.timerId = setTimeout(function() {
// IE8 doesn't reliably clear timeout, so this additional
// check is needed
if (self.timerId === null)
return;
self.$clearTimer();
if (self.args)
self.normalCall.apply(self, self.args);
}, this.delayMs);
}
};
this.immediateCall = function() {
this.$clearTimer();
this.args = arguments;
this.$invoke();
};
this.isPending = function() {
return this.timerId !== null;
};
this.$clearTimer = function() {
if (this.timerId !== null) {
clearTimeout(this.timerId);
this.timerId = null;
}
};
this.$invoke = function() {
this.func.apply(this.target, this.args);
this.args = null;
};
}).call(Throttler.prototype);
// Returns a debounced version of the given function.
// Debouncing means that when the function is invoked,
// there is a delay of `threshold` milliseconds before
// it is actually executed, and if the function is
// invoked again before that threshold has elapsed then
// the clock starts over.
//
// For example, if a function is debounced with a
// threshold of 1000ms, then calling it 17 times at
// 900ms intervals will result in a single execution
// of the underlying function, 1000ms after the 17th
// call.
function debounce(threshold, func) {
var timerId = null;
var self, args;
return function() {
self = this;
args = arguments;
if (timerId !== null) {
clearTimeout(timerId);
timerId = null;
}
timerId = setTimeout(function() {
// IE8 doesn't reliably clear timeout, so this additional
// check is needed
if (timerId === null)
return;
timerId = null;
func.apply(self, args);
}, threshold);
};
}
// Returns a throttled version of the given function.
// Throttling means that the underlying function will
// be executed no more than once every `threshold`
// milliseconds.
//
// For example, if a function is throttled with a
// threshold of 1000ms, then calling it 17 times at
// 900ms intervals will result in something like 15
// or 16 executions of the underlying function.
// eslint-disable-next-line no-unused-vars
function throttle(threshold, func) {
var executionPending = false;
var timerId = null;
var self, args;
function throttled() {
self = null;
args = null;
if (timerId === null) {
// Haven't seen a call recently. Execute now and
// start a timer to buffer any subsequent calls.
timerId = setTimeout(function() {
// When time expires, clear the timer; and if
// there has been a call in the meantime, repeat.
timerId = null;
if (executionPending) {
executionPending = false;
throttled.apply(self, args);
}
}, threshold);
func.apply(this, arguments);
}
else {
// Something executed recently. Don't do anything
// except set up target/arguments to be called later
executionPending = true;
self = this;
args = arguments;
}
}
return throttled;
}
// Schedules data to be sent to shinyapp at the next setTimeout(0).
// Batches multiple input calls into one websocket message.
var InputBatchSender = function(shinyapp) {
this.shinyapp = shinyapp;
this.timerId = null;
this.pendingData = {};
this.reentrant = false;
this.lastChanceCallback = [];
};
(function() {
this.setInput = function(nameType, value, opts) {
this.pendingData[nameType] = value;
if (!this.reentrant) {
if (opts.priority === "event") {
this.$sendNow();
} else if (!this.timerId) {
this.timerId = setTimeout(this.$sendNow.bind(this), 0);
}
}
};
this.$sendNow = function() {
if (this.reentrant) {
console.trace("Unexpected reentrancy in InputBatchSender!");
}
this.reentrant = true;
try {
this.timerId = null;
$.each(this.lastChanceCallback, (i, callback) => {
callback();
});
var currentData = this.pendingData;
this.pendingData = {};
this.shinyapp.sendInput(currentData);
} finally {
this.reentrant = false;
}
};
}).call(InputBatchSender.prototype);
var InputNoResendDecorator = function(target, initialValues) {
this.target = target;
this.lastSentValues = this.reset(initialValues);
};
(function() {
this.setInput = function(nameType, value, opts) {
const { name: inputName, inputType: inputType } = splitInputNameType(nameType);
const jsonValue = JSON.stringify(value);
if (opts.priority !== "event" &&
this.lastSentValues[inputName] &&
this.lastSentValues[inputName].jsonValue === jsonValue &&
this.lastSentValues[inputName].inputType === inputType) {
return;
}
this.lastSentValues[inputName] = { jsonValue, inputType };
this.target.setInput(nameType, value, opts);
};
this.reset = function(values = {}) {
// 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" } }
const cacheValues = {};
for (let inputName in values) {
if (values.hasOwnProperty(inputName)) {
let { name, inputType } = splitInputNameType(inputName);
cacheValues[name] = {
jsonValue: JSON.stringify(values[inputName]),
inputType: inputType
};
}
}
this.lastSentValues = cacheValues;
};
this.forget = function(name) {
delete this.lastSentValues[name];
};
}).call(InputNoResendDecorator.prototype);
var InputEventDecorator = function(target) {
this.target = target;
};
(function() {
this.setInput = function(nameType, value, opts) {
var evt = jQuery.Event("shiny:inputchanged");
const input = splitInputNameType(nameType);
evt.name = input.name;
evt.inputType = input.inputType;
evt.value = value;
evt.binding = opts.binding;
evt.el = opts.el;
evt.priority = opts.priority;
$(opts.el).trigger(evt);
if (!evt.isDefaultPrevented()) {
let name = evt.name;
if (evt.inputType !== '') name += ':' + evt.inputType;
// Most opts aren't passed along to lower levels in the input decorator
// stack.
this.target.setInput(name, evt.value, { priority: opts.priority });
}
};
}).call(InputEventDecorator.prototype);
var InputRateDecorator = function(target) {
this.target = target;
this.inputRatePolicies = {};
};
(function() {
// Note that the first argument of setInput() and setRatePolicy()
// are passed both the input name (i.e., inputId) and type.
// https://github.com/rstudio/shiny/blob/67d3a/srcjs/init_shiny.js#L111-L126
// However, $ensureInit() and $doSetInput() are meant to be passed just
// the input name (i.e., inputId), which is why we distinguish between
// nameType and name.
this.setInput = function(nameType, value, opts) {
const {name: inputName} = splitInputNameType(nameType);
this.$ensureInit(inputName);
if (opts.priority !== "deferred")
this.inputRatePolicies[inputName].immediateCall(nameType, value, opts);
else
this.inputRatePolicies[inputName].normalCall(nameType, value, opts);
};
this.setRatePolicy = function(nameType, mode, millis) {
const {name: inputName} = splitInputNameType(nameType);
if (mode === 'direct') {
this.inputRatePolicies[inputName] = new Invoker(this, this.$doSetInput);
}
else if (mode === 'debounce') {
this.inputRatePolicies[inputName] = new Debouncer(this, this.$doSetInput, millis);
}
else if (mode === 'throttle') {
this.inputRatePolicies[inputName] = new Throttler(this, this.$doSetInput, millis);
}
};
this.$ensureInit = function(name) {
if (!(name in this.inputRatePolicies))
this.setRatePolicy(name, 'direct');
};
this.$doSetInput = function(nameType, value, opts) {
this.target.setInput(nameType, value, opts);
};
}).call(InputRateDecorator.prototype);
var InputDeferDecorator = function(target) {
this.target = target;
this.pendingInput = {};
};
(function() {
this.setInput = function(nameType, value, opts) {
if (/^\./.test(nameType))
this.target.setInput(nameType, value, opts);
else
this.pendingInput[nameType] = { value, opts };
};
this.submit = function() {
for (var nameType in this.pendingInput) {
if (this.pendingInput.hasOwnProperty(nameType)) {
let { value, opts } = this.pendingInput[nameType];
this.target.setInput(nameType, value, opts);
}
}
};
}).call(InputDeferDecorator.prototype);
const InputValidateDecorator = function(target) {
this.target = target;
};
(function() {
this.setInput = function(nameType, value, opts) {
if (!nameType)
throw "Can't set input with empty name.";
opts = addDefaultInputOpts(opts);
this.target.setInput(nameType, value, opts);
};
}).call(InputValidateDecorator.prototype);
// Merge opts with defaults, and return a new object.
function addDefaultInputOpts(opts) {
opts = $.extend({
priority: "immediate",
binding: null,
el: null
}, opts);
if (opts && typeof(opts.priority) !== "undefined") {
switch (opts.priority) {
case "deferred":
case "immediate":
case "event":
break;
default:
throw new Error("Unexpected input value mode: '" + opts.priority + "'");
}
}
return opts;
}
function splitInputNameType(nameType) {
const name2 = nameType.split(':');
return {
name: name2[0],
inputType: name2.length > 1 ? name2[1] : ''
};
}

66
srcts/old/js/modal.js Normal file
View File

@@ -0,0 +1,66 @@
exports.modal = {
// Show a modal dialog. This is meant to handle two types of cases: one is
// that the content is a Bootstrap modal dialog, and the other is that the
// content is non-Bootstrap. Bootstrap modals require some special handling,
// which is coded in here.
show: function({ html='', deps=[] } = {}) {
// If there was an existing Bootstrap modal, then there will be a modal-
// backdrop div that was added outside of the modal wrapper, and it must be
// removed; otherwise there can be multiple of these divs.
$('.modal-backdrop').remove();
// Get existing wrapper DOM element, or create if needed.
let $modal = $('#shiny-modal-wrapper');
if ($modal.length === 0) {
$modal = $('<div id="shiny-modal-wrapper"></div>');
$(document.body).append($modal);
// If the wrapper's content is a Bootstrap modal, then when the inner
// modal is hidden, remove the entire thing, including wrapper.
$modal.on('hidden.bs.modal', function(e) {
if (e.target === $("#shiny-modal")[0]) {
exports.unbindAll($modal);
$modal.remove();
}
});
}
$modal.on('keydown.shinymodal', function(e) {
// If we're listening for Esc, don't let the event propagate. See
// https://github.com/rstudio/shiny/issues/1453. The value of
// data("keyboard") needs to be checked inside the handler, because at
// the time that $modal.on() is called, the $("#shiny-modal") div doesn't
// yet exist.
if ($("#shiny-modal").data("keyboard") === false)
return;
if (e.keyCode === 27) {
e.stopPropagation();
e.preventDefault();
}
});
// Set/replace contents of wrapper with html.
exports.renderContent($modal, { html: html, deps: deps });
},
remove: function() {
const $modal = $('#shiny-modal-wrapper');
$modal.off('keydown.shinymodal');
// Look for a Bootstrap modal and if present, trigger hide event. This will
// trigger the hidden.bs.modal callback that we set in show(), which unbinds
// and removes the element.
if ($modal.find('.modal').length > 0) {
$modal.find('.modal').modal('hide');
} else {
// If not a Bootstrap modal dialog, simply unbind and remove it.
exports.unbindAll($modal);
$modal.remove();
}
}
};

View File

@@ -0,0 +1,154 @@
exports.notifications = (function() {
// Milliseconds to fade in or out
const fadeDuration = 250;
function show({ html='', action='', deps=[], duration=5000,
id=null, closeButton=true, type=null } = {})
{
if (!id)
id = randomId();
// Create panel if necessary
_createPanel();
// Get existing DOM element for this ID, or create if needed.
let $notification = _get(id);
if ($notification.length === 0)
$notification = _create(id);
// Render html and dependencies
const newHtml = `<div class="shiny-notification-content-text">${html}</div>` +
`<div class="shiny-notification-content-action">${action}</div>`;
const $content = $notification.find('.shiny-notification-content');
exports.renderContent($content, { html: newHtml, deps: deps });
// Remove any existing classes of the form 'shiny-notification-xxxx'.
// The xxxx would be strings like 'warning'.
const classes = $notification.attr('class')
.split(/\s+/)
.filter(cls => cls.match(/^shiny-notification-/))
.join(' ');
$notification.removeClass(classes);
// Add class. 'default' means no additional CSS class.
if (type && type !== 'default')
$notification.addClass('shiny-notification-' + type);
// Make sure that the presence/absence of close button matches with value
// of `closeButton`.
const $close = $notification.find('.shiny-notification-close');
if (closeButton && $close.length === 0) {
$notification.append('<div class="shiny-notification-close">&times;</div>');
} else if (!closeButton && $close.length !== 0) {
$close.remove();
}
// If duration was provided, schedule removal. If not, clear existing
// removal callback (this happens if a message was first added with
// a duration, and then updated with no duration).
if (duration)
_addRemovalCallback(id, duration);
else
_clearRemovalCallback(id);
return id;
}
function remove(id) {
_get(id).fadeOut(fadeDuration, function() {
exports.unbindAll(this);
$(this).remove();
// If no more notifications, remove the panel from the DOM.
if (_ids().length === 0) {
_getPanel().remove();
}
});
}
// Returns an individual notification DOM object (wrapped in jQuery).
function _get(id) {
if (!id)
return null;
return _getPanel().find('#shiny-notification-' + $escape(id));
}
// Return array of all notification IDs
function _ids() {
return _getPanel()
.find('.shiny-notification')
.map(function() { return this.id.replace(/shiny-notification-/, ''); })
.get();
}
// Returns the notification panel DOM object (wrapped in jQuery).
function _getPanel() {
return $('#shiny-notification-panel');
}
// Create notifications panel and return the jQuery object. If the DOM
// element already exists, just return it.
function _createPanel() {
let $panel = _getPanel();
if ($panel.length > 0)
return $panel;
$(document.body).append('<div id="shiny-notification-panel">');
return $panel;
}
// Create a notification DOM element and return the jQuery object. If the
// DOM element already exists for the ID, just return it without creating.
function _create(id) {
let $notification = _get(id);
if ($notification.length === 0) {
$notification = $(
`<div id="shiny-notification-${id}" class="shiny-notification">` +
'<div class="shiny-notification-close">&times;</div>' +
'<div class="shiny-notification-content"></div>' +
'</div>'
);
$notification.find('.shiny-notification-close').on('click', e => {
e.preventDefault();
e.stopPropagation();
remove(id);
});
_getPanel().append($notification);
}
return $notification;
}
// Add a callback to remove a notification after a delay in ms.
function _addRemovalCallback(id, delay) {
// If there's an existing removalCallback, clear it before adding the new
// one.
_clearRemovalCallback(id);
// Attach new removal callback
const removalCallback = setTimeout(function() { remove(id); }, delay);
_get(id).data('removalCallback', removalCallback);
}
// Clear a removal callback from a notification, if present.
function _clearRemovalCallback(id) {
const $notification = _get(id);
const oldRemovalCallback = $notification.data('removalCallback');
if (oldRemovalCallback) {
clearTimeout(oldRemovalCallback);
}
}
return {
show,
remove
};
})();

View File

@@ -0,0 +1,46 @@
var OutputBinding = exports.OutputBinding = function() {};
(function() {
// Returns a jQuery object or element array that contains the
// descendants of scope that match this binding
this.find = function(scope) { throw "Not implemented"; };
this.getId = function(el) {
return el['data-input-id'] || el.id;
};
this.onValueChange = function(el, data) {
this.clearError(el);
this.renderValue(el, data);
};
this.onValueError = function(el, err) {
this.renderError(el, err);
};
this.renderError = function(el, err) {
this.clearError(el);
if (err.message === '') {
// not really error, but we just need to wait (e.g. action buttons)
$(el).empty();
return;
}
var errClass = 'shiny-output-error';
if (err.type !== null) {
// use the classes of the error condition as CSS class names
errClass = errClass + ' ' + $.map(asArray(err.type), function(type) {
return errClass + '-' + type;
}).join(' ');
}
$(el).addClass(errClass).text(err.message);
};
this.clearError = function(el) {
$(el).attr('class', function(i, c) {
return c.replace(/(^|\s)shiny-output-error\S*/g, '');
});
};
this.showProgress = function(el, show) {
var RECALC_CLASS = 'recalculating';
if (show)
$(el).addClass(RECALC_CLASS);
else
$(el).removeClass(RECALC_CLASS);
};
}).call(OutputBinding.prototype);

View File

@@ -0,0 +1,29 @@
var OutputBindingAdapter = function(el, binding) {
this.el = el;
this.binding = binding;
// If the binding actually has a resize method, override the prototype of
// onResize with a version that does a makeResizeFilter on the element.
if (binding.resize) {
this.onResize = makeResizeFilter(el, function(width, height) {
binding.resize(el, width, height);
});
}
};
(function() {
this.getId = function() {
return this.binding.getId(this.el);
};
this.onValueChange = function(data) {
this.binding.onValueChange(this.el, data);
};
this.onValueError = function(err) {
this.binding.onValueError(this.el, err);
};
this.showProgress = function(show) {
this.binding.showProgress(this.el, show);
};
this.onResize = function() {
// Intentionally left blank; see constructor
};
}).call(OutputBindingAdapter.prototype);

View File

@@ -0,0 +1,86 @@
var datatableOutputBinding = new OutputBinding();
$.extend(datatableOutputBinding, {
find: function(scope) {
return $(scope).find('.shiny-datatable-output');
},
onValueError: function(el, err) {
exports.unbindAll(el);
this.renderError(el, err);
},
renderValue: function(el, data) {
var $el = $(el).empty();
if (!data || !data.colnames) return;
var colnames = $.makeArray(data.colnames);
var header = $.map(colnames, function(x) {
return '<th>' + x + '</th>';
}).join('');
header = '<thead><tr>' + header + '</tr></thead>';
var footer = '';
if (data.options === null || data.options.searching !== false) {
footer = $.map(colnames, function(x) {
// placeholder needs to be escaped (and HTML tags are stripped off)
return '<th><input type="text" placeholder="' +
escapeHTML(x.replace(/(<([^>]+)>)/ig, '')) +
'" /></th>';
}).join('');
footer = '<tfoot>' + footer + '</tfoot>';
}
var content = '<table class="table table-striped table-hover">' +
header + footer + '</table>';
$el.append(content);
// options that should be eval()ed
if (data.evalOptions)
$.each(data.evalOptions, function(i, x) {
/*jshint evil: true */
data.options[x] = eval('(' + data.options[x] + ')');
});
// caseInsensitive searching? default true
var searchCI = data.options === null || typeof(data.options.search) === 'undefined' ||
data.options.search.caseInsensitive !== false;
var oTable = $(el).children("table").DataTable($.extend({
"processing": true,
"serverSide": true,
"order": [],
"orderClasses": false,
"pageLength": 25,
"ajax": {
"url": data.action,
"type": "POST",
"data": function(d) {
d.search.caseInsensitive = searchCI;
d.escape = data.escape;
}
}
}, data.options));
// the table object may need post-processing
if (typeof data.callback === 'string') {
/*jshint evil: true */
var callback = eval('(' + data.callback + ')');
if (typeof callback === 'function') callback(oTable);
}
// use debouncing for searching boxes
$el.find('label input').first().unbind('keyup')
.keyup(debounce(data.searchDelay, function() {
oTable.search(this.value).draw();
}));
var searchInputs = $el.find("tfoot input");
if (searchInputs.length > 0) {
// this is a little weird: aoColumns/bSearchable are still in DT 1.10
// https://github.com/DataTables/DataTables/issues/388
$.each(oTable.settings()[0].aoColumns, function(i, x) {
// hide the text box if not searchable
if (!x.bSearchable) searchInputs.eq(i).hide();
});
searchInputs.keyup(debounce(data.searchDelay, function() {
oTable.column(searchInputs.index(this)).search(this.value).draw();
}));
}
// FIXME: ugly scrollbars in tab panels b/c Bootstrap uses 'visible: auto'
$el.parents('.tab-content').css('overflow', 'visible');
}
});
outputBindings.register(datatableOutputBinding, 'shiny.datatableOutput');

View File

@@ -0,0 +1,19 @@
var downloadLinkOutputBinding = new OutputBinding();
$.extend(downloadLinkOutputBinding, {
find: function(scope) {
return $(scope).find('a.shiny-download-link');
},
renderValue: function(el, data) {
$(el).attr('href', data);
}
});
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);
});

View File

@@ -0,0 +1,297 @@
var htmlOutputBinding = new OutputBinding();
$.extend(htmlOutputBinding, {
find: function(scope) {
return $(scope).find('.shiny-html-output');
},
onValueError: function(el, err) {
exports.unbindAll(el);
this.renderError(el, err);
},
renderValue: function(el, data) {
exports.renderContent(el, data);
}
});
outputBindings.register(htmlOutputBinding, 'shiny.htmlOutput');
var renderDependencies = exports.renderDependencies = function(dependencies) {
if (dependencies) {
$.each(dependencies, function(i, dep) {
renderDependency(dep);
});
}
};
// Render HTML in a DOM element, add dependencies, and bind Shiny
// inputs/outputs. `content` can be null, a string, or an object with
// properties 'html' and 'deps'.
exports.renderContent = function(el, content, where="replace") {
if (where === "replace") {
exports.unbindAll(el);
}
var html;
var dependencies = [];
if (content === null) {
html = '';
} else if (typeof(content) === 'string') {
html = content;
} else if (typeof(content) === 'object') {
html = content.html;
dependencies = content.deps || [];
}
exports.renderHtml(html, el, dependencies, where);
var scope = el;
if (where === "replace") {
exports.initializeInputs(el);
exports.bindAll(el);
} else {
var $parent = $(el).parent();
if ($parent.length > 0) {
scope = $parent;
if (where === "beforeBegin" || where === "afterEnd") {
var $grandparent = $parent.parent();
if ($grandparent.length > 0) scope = $grandparent;
}
}
exports.initializeInputs(scope);
exports.bindAll(scope);
}
};
// Render HTML in a DOM element, inserting singletons into head as needed
exports.renderHtml = function(html, el, dependencies, where = 'replace') {
renderDependencies(dependencies);
return singletons.renderHtml(html, el, where);
};
var htmlDependencies = {};
function registerDependency(name, version) {
htmlDependencies[name] = version;
}
// Re-render stylesheet(s) if the dependency has specificially requested it
// and it matches an existing dependency (name and version)
function needsRestyle(dep) {
if (!dep.restyle) {
return false;
}
var names = Object.keys(htmlDependencies);
var idx = names.indexOf(dep.name);
if (idx === -1) {
return false;
}
return htmlDependencies[names[idx]] === dep.version;
}
// Client-side dependency resolution and rendering
function renderDependency(dep) {
var restyle = needsRestyle(dep);
if (htmlDependencies.hasOwnProperty(dep.name) && !restyle)
return false;
registerDependency(dep.name, dep.version);
var href = dep.src.href;
var $head = $("head").first();
if (dep.meta && !restyle) {
var metas = $.map(asArray(dep.meta), function(obj, idx) {
// only one named pair is expected in obj as it's already been decomposed
var name = Object.keys(obj)[0];
return $("<meta>").attr("name", name).attr("content", obj[name]);
});
$head.append(metas);
}
if (dep.stylesheet) {
var links = $.map(asArray(dep.stylesheet), function(stylesheet) {
return $("<link rel='stylesheet' type='text/css'>").attr("href", href + "/" + encodeURI(stylesheet));
});
if (!restyle) {
$head.append(links);
} else {
$.map(links, function(link) {
// Find any document.styleSheets that match this link's href
// so we can remove it after bringing in the new stylesheet
var oldSheet = findSheet(link.attr("href"));
// Add a timestamp to the href to prevent caching
var href = link.attr("href") + "?restyle=" + new Date().getTime();
// Use inline <style> approach for IE, otherwise use the more elegant
// <link> -based approach
if (browser.isIE) {
refreshStyle(href, oldSheet);
} else {
link.attr("href", href);
// Once the new <link> is loaded, schedule the old <link> to be removed
// on the next tick which is needed to avoid FOUC
link.attr("onload", () => {
setTimeout(() => removeSheet(oldSheet), 500);
});
$head.append(link);
}
});
// Once the new styles are applied, CSS values that are accessible server-side
// (e.g., getCurrentOutputInfo(), output visibility, etc) may become outdated.
// At the time of writing, that means we need to do sendImageSize() &
// sendOutputHiddenState() again, which can be done by re-binding.
/* global Shiny */
var bindDebouncer = new Debouncer(null, Shiny.bindAll, 100);
setTimeout(() => bindDebouncer.normalCall(), 100);
// This inline <style> based approach works for IE11
function refreshStyle(href, oldSheet) {
var xhr = new XMLHttpRequest();
xhr.open('GET', href);
xhr.onload = function() {
var id = "shiny_restyle_" + href.split("?restyle")[0].replace(/\W/g, '_');
var oldStyle = $head.find("style#" + id);
var newStyle = $("<style>").attr("id", id).html(xhr.responseText);
$head.append(newStyle);
setTimeout(() => oldStyle.remove(), 500);
setTimeout(() => removeSheet(oldSheet), 500);
};
xhr.send();
}
function findSheet(href) {
for (var i = 0; i < document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
// The sheet's href is a full URL
if (typeof sheet.href === "string" && sheet.href.indexOf(href) > -1) {
return sheet;
}
}
return null;
}
function removeSheet(sheet) {
if (!sheet) return;
sheet.disabled = true;
if (browser.isIE) sheet.cssText = "";
$(sheet.ownerNode).remove();
}
}
}
if (dep.script && !restyle) {
var scripts = $.map(asArray(dep.script), function(scriptName) {
return $("<script>").attr("src", href + "/" + encodeURI(scriptName));
});
$head.append(scripts);
}
if (dep.attachment && !restyle) {
// dep.attachment might be a single string, an array, or an object.
var attachments = dep.attachment;
if (typeof(attachments) === "string")
attachments = [attachments];
if ($.isArray(attachments)) {
// The contract for attachments is that arrays of attachments are
// addressed using 1-based indexes. Convert this array to an object.
var tmp = {};
$.each(attachments, function(index, attachment) {
tmp[(index + 1) + ""] = attachment;
});
attachments = tmp;
}
var attach = $.map(attachments, function(attachment, key) {
return $("<link rel='attachment'>")
.attr("id", dep.name + "-" + key + "-attachment")
.attr("href", href + "/" + encodeURI(attachment));
});
$head.append(attach);
}
if (dep.head && !restyle) {
var $newHead = $("<head></head>");
$newHead.html(dep.head);
$head.append($newHead.children());
}
return true;
}
var singletons = {
knownSingletons: {},
renderHtml: function(html, el, where) {
var processed = this._processHtml(html);
this._addToHead(processed.head);
this.register(processed.singletons);
if (where === "replace") {
$(el).html(processed.html);
} else {
el.insertAdjacentHTML(where, processed.html);
}
return processed;
},
// Take an object where keys are names of singletons, and merges it into
// knownSingletons
register: function(s) {
$.extend(this.knownSingletons, s);
},
// Takes a string or array of strings and adds them to knownSingletons
registerNames: function(s) {
if (typeof s === 'string') {
this.knownSingletons[s] = true;
} else if (s instanceof Array) {
for (var i = 0; i < s.length; i++) {
this.knownSingletons[s[i]] = true;
}
}
},
// Inserts new content into document head
_addToHead: function(head) {
if (head.length > 0) {
var tempDiv = $("<div>" + head + "</div>")[0];
var $head = $('head');
while (tempDiv.hasChildNodes()) {
$head.append(tempDiv.firstChild);
}
}
},
// Reads HTML and returns an object with info about singletons
_processHtml: function(val) {
var self = this;
var newSingletons = {};
var newVal;
var findNewPayload = function(match, p1, sig, payload) {
if (self.knownSingletons[sig] || newSingletons[sig])
return "";
newSingletons[sig] = true;
return payload;
};
while (true) {
newVal = val.replace(self._reSingleton, findNewPayload);
if (val.length === newVal.length)
break;
val = newVal;
}
var heads = [];
var headAddPayload = function(match, payload) {
heads.push(payload);
return "";
};
while (true) {
newVal = val.replace(self._reHead, headAddPayload);
if (val.length === newVal.length)
break;
val = newVal;
}
return {
html: val,
head: heads.join("\n"),
singletons: newSingletons
};
},
_reSingleton: /<!--(SHINY.SINGLETON\[([\w]+)\])-->([\s\S]*?)<!--\/\1-->/,
_reHead: /<head(?:\s[^>]*)?>([\s\S]*?)<\/head>/
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
var textOutputBinding = new OutputBinding();
$.extend(textOutputBinding, {
find: function(scope) {
return $(scope).find('.shiny-text-output');
},
renderValue: function(el, data) {
$(el).text(data);
}
});
outputBindings.register(textOutputBinding, 'shiny.textOutput');

43
srcts/old/js/reactlog.js Normal file
View File

@@ -0,0 +1,43 @@
$(document).on('keydown', function(e) {
if (e.which !== 114 || (!e.ctrlKey && !e.metaKey) || (e.shiftKey || e.altKey))
return;
var url = 'reactlog?w=' + window.escape(exports.shinyapp.config.workerId) +
"&s=" + window.escape(exports.shinyapp.config.sessionId);
window.open(url);
e.preventDefault();
});
$(document).on('keydown', function(e) {
if (
// if not one of the key combos below
!(
// cmd/ctrl + fn + f4
(e.which === 115 && (e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) ||
// cmd/ctrl + shift + fn + f3
(e.which === 114 && (e.ctrlKey || e.metaKey) && e.shiftKey && !e.altKey)
)
) {
return;
}
var url = 'reactlog/mark?w=' + window.escape(exports.shinyapp.config.workerId) +
"&s=" + window.escape(exports.shinyapp.config.sessionId);
// send notification
$.get(url, function(result) {
if (result !== "marked") return;
var html = '<span id="shiny-reactlog-mark-text">Marked time point in reactlog</span>';
exports.notifications.show({
html: html,
closeButton: true,
});
}).fail(function() {
// found returned error while marking, should open webpage
window.open(url);
});
e.preventDefault();
});

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