mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 23:48:01 -05:00
Compare commits
45 Commits
v1.7.1
...
ui-docs-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4671eccd84 | ||
|
|
6c0cf4f7e5 | ||
|
|
dc7d4b46d8 | ||
|
|
509acb62b0 | ||
|
|
d05b2c305e | ||
|
|
a562e286d0 | ||
|
|
a40903c7e3 | ||
|
|
1faefe66fa | ||
|
|
fc7e237000 | ||
|
|
de8134742d | ||
|
|
f814034835 | ||
|
|
6d9fad29f3 | ||
|
|
313ae9044d | ||
|
|
9389160af0 | ||
|
|
6a7ffeff68 | ||
|
|
bc6ff57cb7 | ||
|
|
b52b9e4520 | ||
|
|
fb71ab6146 | ||
|
|
d8c7a634ff | ||
|
|
396dd2632e | ||
|
|
c11875a5f0 | ||
|
|
2e599faf1f | ||
|
|
a5a8385420 | ||
|
|
33ed698e5b | ||
|
|
ed547fdf40 | ||
|
|
0b1c35c92b | ||
|
|
d304bdf333 | ||
|
|
a9255e6b12 | ||
|
|
45429fb798 | ||
|
|
1206d1d3ba | ||
|
|
af44a447a1 | ||
|
|
d7fb6d1793 | ||
|
|
cb0083adb2 | ||
|
|
77bae68f26 | ||
|
|
e9f8b4d552 | ||
|
|
aee6b74cfb | ||
|
|
29b6b03297 | ||
|
|
b5ebd8a645 | ||
|
|
356ba8c5a1 | ||
|
|
5aa5cb1794 | ||
|
|
09c609e417 | ||
|
|
10e7d11846 | ||
|
|
4e442312a7 | ||
|
|
8ea97df3f2 | ||
|
|
a8c14dab96 |
12
.github/shiny-workflows/routine.sh
vendored
Normal file
12
.github/shiny-workflows/routine.sh
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/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
|
||||
146
.github/workflows/R-CMD-check.yaml
vendored
146
.github/workflows/R-CMD-check.yaml
vendored
@@ -1,139 +1,23 @@
|
||||
# NOTE: This workflow is overkill for most R packages
|
||||
# check-standard.yaml is likely a better choice
|
||||
# usethis::use_github_action("check-standard") will install it.
|
||||
# Workflow derived from https://github.com/rstudio/shiny-workflows
|
||||
#
|
||||
# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag.
|
||||
# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions
|
||||
# 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
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
branches: [main, rc-**]
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- rc-v**
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 5 * * 1' # every monday
|
||||
|
||||
name: R-CMD-check
|
||||
name: Package checks
|
||||
|
||||
jobs:
|
||||
|
||||
rversions:
|
||||
name: R Versions
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
devel: ${{ steps.devel.outputs.installed-r-version }}
|
||||
release: ${{ steps.release.outputs.installed-r-version }}
|
||||
oldrel1: ${{ steps.oldrel1.outputs.installed-r-version }}
|
||||
oldrel2: ${{ steps.oldrel2.outputs.installed-r-version }}
|
||||
oldrel3: ${{ steps.oldrel3.outputs.installed-r-version }}
|
||||
oldrel4: ${{ steps.oldrel4.outputs.installed-r-version }}
|
||||
steps:
|
||||
- { name: devel, uses: r-lib/actions/setup-r@master, id: devel, with: { r-version: devel, install-r: false }}
|
||||
- { name: release, uses: r-lib/actions/setup-r@master, id: release, with: { r-version: release, install-r: false }}
|
||||
- { name: oldrel/1, uses: r-lib/actions/setup-r@master, id: oldrel1, with: { r-version: oldrel/1, install-r: false }}
|
||||
- { name: oldrel/2, uses: r-lib/actions/setup-r@master, id: oldrel2, with: { r-version: oldrel/2, install-r: false }}
|
||||
- { name: oldrel/3, uses: r-lib/actions/setup-r@master, id: oldrel3, with: { r-version: oldrel/3, install-r: false }}
|
||||
- { name: oldrel/4, uses: r-lib/actions/setup-r@master, id: oldrel4, with: { r-version: oldrel/4, install-r: false }}
|
||||
|
||||
|
||||
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:
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
|
||||
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
|
||||
|
||||
needs:
|
||||
- rversions
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {os: macOS-latest, r: '${{ needs.rversions.outputs.release }}'}
|
||||
- {os: windows-latest, r: '${{ needs.rversions.outputs.release }}'}
|
||||
- {os: windows-latest, r: '3.6'}
|
||||
|
||||
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.devel }}', http-user-agent: "release" }
|
||||
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.release }}'}
|
||||
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.oldrel1 }}'}
|
||||
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.oldrel2 }}'}
|
||||
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.oldrel3 }}'}
|
||||
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.oldrel4 }}'}
|
||||
|
||||
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.devel }}', http-user-agent: "release" }
|
||||
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.release }}'}
|
||||
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.oldrel1 }}'}
|
||||
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.oldrel2 }}'}
|
||||
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.oldrel3 }}'}
|
||||
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.oldrel4 }}'}
|
||||
|
||||
env:
|
||||
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@v1
|
||||
id: install-r
|
||||
with:
|
||||
r-version: ${{ matrix.config.r }}
|
||||
http-user-agent: ${{ matrix.config.http-user-agent }}
|
||||
use-public-rspm: true
|
||||
|
||||
- uses: r-lib/actions/setup-pandoc@v1
|
||||
|
||||
- uses: r-lib/actions/setup-r-dependencies@v1
|
||||
with:
|
||||
extra-packages: rcmdcheck
|
||||
|
||||
# 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
|
||||
|
||||
- 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: Check
|
||||
env:
|
||||
_R_CHECK_CRAN_INCOMING_: false
|
||||
_R_CHECK_FORCE_SUGGESTS_: ${{ matrix.config.r != 'devel' }}
|
||||
run: |
|
||||
options(crayon.enabled = TRUE)
|
||||
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@main
|
||||
with:
|
||||
name: ${{ matrix.config.os }}-r${{ matrix.config.r }}-results
|
||||
path: check
|
||||
uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1
|
||||
|
||||
171
.github/workflows/rituals.yaml
vendored
171
.github/workflows/rituals.yaml
vendored
@@ -1,171 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- ghactions
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
name: Rituals
|
||||
|
||||
jobs:
|
||||
rituals:
|
||||
name: Rituals
|
||||
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- { os: ubuntu-20.04, r: 'release', node: "14.x", rspm: "https://packagemanager.rstudio.com/all/__linux__/focal/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
|
||||
@@ -1,3 +0,0 @@
|
||||
trailingComma: "es5"
|
||||
arrowParens: always
|
||||
endOfLine: lf
|
||||
16
.vscode/settings.json
vendored
16
.vscode/settings.json
vendored
@@ -1,10 +1,18 @@
|
||||
{
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"eslint.nodePath": ".yarn/sdks",
|
||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.js",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": 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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 1.7.1
|
||||
Version: 1.7.1.9002
|
||||
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"),
|
||||
@@ -189,6 +189,7 @@ Collate:
|
||||
'shinywrappers.R'
|
||||
'showcase.R'
|
||||
'snapshot.R'
|
||||
'staticimports.R'
|
||||
'tar.R'
|
||||
'test-export.R'
|
||||
'test-server.R'
|
||||
|
||||
21
NEWS.md
21
NEWS.md
@@ -1,3 +1,22 @@
|
||||
shiny 1.7.1.9001
|
||||
================
|
||||
|
||||
## Full changelog
|
||||
|
||||
### 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)
|
||||
|
||||
### 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.
|
||||
|
||||
shiny 1.7.1
|
||||
===========
|
||||
|
||||
@@ -40,7 +59,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/master/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/v1.7.0/srcts). (#3296)
|
||||
|
||||
* Switched from `digest::digest()` to `rlang::hash()` for hashing. (#3264)
|
||||
|
||||
|
||||
@@ -125,14 +125,14 @@ shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
|
||||
}
|
||||
|
||||
if ("shinytest" %in% examples) {
|
||||
if (!is_available("shinytest", "1.4.0"))
|
||||
if (!is_installed("shinytest", "1.4.0"))
|
||||
{
|
||||
message(
|
||||
"The tests/shinytest directory needs shinytest 1.4.0 or later to work properly."
|
||||
)
|
||||
if (is_available("shinytest")) {
|
||||
if (is_installed("shinytest")) {
|
||||
message("You currently have shinytest ",
|
||||
utils::packageVersion("shinytest"), " installed.")
|
||||
get_package_version("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
|
||||
|
||||
@@ -1,87 +1,4 @@
|
||||
|
||||
#' Create a page with fluid layout
|
||||
#'
|
||||
#' Functions for creating fluid page layouts. A fluid page layout consists of
|
||||
#' rows which in turn include columns. Rows exist for the purpose of making sure
|
||||
#' their elements appear on the same line (if the browser has adequate width).
|
||||
#' Columns exist for the purpose of defining how much horizontal space within a
|
||||
#' 12-unit wide grid it's elements should occupy. Fluid pages scale their
|
||||
#' components in realtime to fill all available browser width.
|
||||
#'
|
||||
#' @param ... Elements to include within the page
|
||||
#' @param title The browser window title (defaults to the host URL of the page).
|
||||
#' Can also be set as a side effect of the [titlePanel()] function.
|
||||
#' @inheritParams bootstrapPage
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the [shinyUI] function.
|
||||
#'
|
||||
#' @details To create a fluid page use the `fluidPage` function and include
|
||||
#' instances of `fluidRow` and [column()] within it. As an
|
||||
#' alternative to low-level row and column functions you can also use
|
||||
#' higher-level layout functions like [sidebarLayout()].
|
||||
#'
|
||||
#' @note See the [
|
||||
#' Shiny-Application-Layout-Guide](https://shiny.rstudio.com/articles/layout-guide.html) for additional details on laying out fluid
|
||||
#' pages.
|
||||
#'
|
||||
#' @family layout functions
|
||||
#' @seealso [column()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' # Example of UI with fluidPage
|
||||
#' ui <- fluidPage(
|
||||
#'
|
||||
#' # Application title
|
||||
#' titlePanel("Hello Shiny!"),
|
||||
#'
|
||||
#' sidebarLayout(
|
||||
#'
|
||||
#' # Sidebar with a slider input
|
||||
#' sidebarPanel(
|
||||
#' sliderInput("obs",
|
||||
#' "Number of observations:",
|
||||
#' min = 0,
|
||||
#' max = 1000,
|
||||
#' value = 500)
|
||||
#' ),
|
||||
#'
|
||||
#' # Show a plot of the generated distribution
|
||||
#' mainPanel(
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' # Server logic
|
||||
#' server <- function(input, output) {
|
||||
#' output$distPlot <- renderPlot({
|
||||
#' hist(rnorm(input$obs))
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' # Complete app with UI and server components
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#'
|
||||
#' # UI demonstrating column layouts
|
||||
#' ui <- fluidPage(
|
||||
#' title = "Hello Shiny!",
|
||||
#' fluidRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#' @rdname fluidPage
|
||||
#' @rdname bootstrapPage
|
||||
#' @export
|
||||
fluidPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
|
||||
bootstrapPage(div(class = "container-fluid", ...),
|
||||
@@ -91,62 +8,13 @@ fluidPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
|
||||
}
|
||||
|
||||
|
||||
#' @rdname fluidPage
|
||||
#' @rdname column
|
||||
#' @export
|
||||
fluidRow <- function(...) {
|
||||
div(class = "row", ...)
|
||||
}
|
||||
|
||||
#' Create a page with a fixed layout
|
||||
#'
|
||||
#' Functions for creating fixed page layouts. A fixed page layout consists of
|
||||
#' rows which in turn include columns. Rows exist for the purpose of making sure
|
||||
#' their elements appear on the same line (if the browser has adequate width).
|
||||
#' Columns exist for the purpose of defining how much horizontal space within a
|
||||
#' 12-unit wide grid it's elements should occupy. Fixed pages limit their width
|
||||
#' to 940 pixels on a typical display, and 724px or 1170px on smaller and larger
|
||||
#' displays respectively.
|
||||
#'
|
||||
#' @param ... Elements to include within the container
|
||||
#' @param title The browser window title (defaults to the host URL of the page)
|
||||
#' @inheritParams bootstrapPage
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the [shinyUI] function.
|
||||
#'
|
||||
#' @details To create a fixed page use the `fixedPage` function and include
|
||||
#' instances of `fixedRow` and [column()] within it. Note that
|
||||
#' unlike [fluidPage()], fixed pages cannot make use of higher-level
|
||||
#' layout functions like `sidebarLayout`, rather, all layout must be done
|
||||
#' with `fixedRow` and `column`.
|
||||
#'
|
||||
#' @note See the [
|
||||
#' Shiny Application Layout Guide](https://shiny.rstudio.com/articles/layout-guide.html) for additional details on laying out fixed
|
||||
#' pages.
|
||||
#'
|
||||
#' @family layout functions
|
||||
#'
|
||||
#' @seealso [column()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fixedPage(
|
||||
#' title = "Hello, Shiny!",
|
||||
#' fixedRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
#' }
|
||||
#'
|
||||
#' @rdname fixedPage
|
||||
#' @rdname bootstrapPage
|
||||
#' @export
|
||||
fixedPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
|
||||
bootstrapPage(div(class = "container", ...),
|
||||
@@ -155,28 +23,39 @@ fixedPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
|
||||
lang = lang)
|
||||
}
|
||||
|
||||
#' @rdname fixedPage
|
||||
#' @rdname column
|
||||
#' @export
|
||||
fixedRow <- function(...) {
|
||||
div(class = "row", ...)
|
||||
}
|
||||
|
||||
|
||||
#' Create a column within a UI definition
|
||||
#' Responsive row-column based layout
|
||||
#'
|
||||
#' Create a column for use within a [fluidRow()] or
|
||||
#' [fixedRow()]
|
||||
#' Layout UI components using Bootstrap's grid layout system. Use
|
||||
#' `fluidRow()` to group elements that should appear on the same line (if the
|
||||
#' browser has adequate width) and `column()` to define how much horizontal
|
||||
#' space within a 12-unit wide grid each on of these elements should occupy. See
|
||||
#' the [layout guide](https://shiny.rstudio.com/articles/layout-guide.html) for
|
||||
#' more context and examples.
|
||||
#'
|
||||
#' @param width The grid width of the column (must be between 1 and 12)
|
||||
#' @param ... Elements to include within the column
|
||||
#' To work properly, these functions need [Bootstrap](https://getbootstrap.com)
|
||||
#' included on the page. Since most Shiny apps use [bootstrapPage()]
|
||||
#' under-the-hood, this is usually the case, but custom page containers (i.e.,
|
||||
#' [htmlTemplate()]) may need to explicitly include [bootstrapLib()]
|
||||
#' dependencies.
|
||||
#'
|
||||
#' @param width The grid width of the column (must be between 1 and 12). When
|
||||
#' the device width is small (e.g., the viewer is on a mobile phone), the
|
||||
#' width is always 12. For more control over these responsive breakpoints, use
|
||||
#' Bootstrap's grid system more directly (e.g., `fluidRow(div(class =
|
||||
#' "col-lg-2", ...))`).
|
||||
#' @param ... UI elements (i.e., [tags]). For `fluidRow()`, `...` should be a set of `column()`s.
|
||||
#' @param offset The number of columns to offset this column from the end of the
|
||||
#' previous column.
|
||||
#'
|
||||
#' @return A column that can be included within a
|
||||
#' [fluidRow()] or [fixedRow()].
|
||||
#' @return A UI element (i.e., [tags]).
|
||||
#'
|
||||
#'
|
||||
#' @seealso [fluidRow()], [fixedRow()].
|
||||
#' @seealso [fluidPage()]
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -202,16 +81,10 @@ fixedRow <- function(...) {
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#'
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' fluidRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' column(width = 4, "4"),
|
||||
#' column(width = 3, offset = 2, "3 offset 2")
|
||||
#' )
|
||||
#' )
|
||||
#' shinyApp(ui, server = function(input, output) { })
|
||||
|
||||
106
R/bootstrap.R
106
R/bootstrap.R
@@ -1,36 +1,84 @@
|
||||
#' @include utils.R
|
||||
NULL
|
||||
|
||||
#' Create a Bootstrap page
|
||||
#' Create a Bootstrap UI page container
|
||||
#'
|
||||
#' Create a Shiny UI page that loads the CSS and JavaScript for
|
||||
#' [Bootstrap](https://getbootstrap.com/), and has no content in the page
|
||||
#' body (other than what you provide).
|
||||
#' @description
|
||||
#' Create a user interface (UI) page container based on
|
||||
#' [Bootstrap](https://getbootstrap.com/)'s CSS and JavaScript. Most Shiny apps
|
||||
#' should use [fluidPage()] (or [navbarPage()]) to get a page container with a
|
||||
#' responsive page width, but in some cases you may want a fixed width container
|
||||
#' (`fixedPage()`) or just a bare `<body>` container (`bootstrapPage()`).
|
||||
#'
|
||||
#' This function is primarily intended for users who are proficient in HTML/CSS,
|
||||
#' and know how to lay out pages in Bootstrap. Most applications should use
|
||||
#' [fluidPage()] along with layout functions like
|
||||
#' [fluidRow()] and [sidebarLayout()].
|
||||
#' Most Shiny apps make use of other Shiny UI functions for [managing
|
||||
#' layout](https://shiny.rstudio.com/articles/layout-guide.html) (e.g.,
|
||||
#' [sidebarLayout()], [fluidRow()], etc), navigation (e.g., [tabPanel()]), and
|
||||
#' other styling (e.g., [wellPanel()], [inputPanel()]). A good portion of these
|
||||
#' Shiny UI functions require Bootstrap to work properly (so most Shiny apps
|
||||
#' should use these functions to start their UI definitions), but more advanced
|
||||
#' usage (i.e., custom HTML/CSS/JS) can avoid Bootstrap entirely by using
|
||||
#' [htmlTemplate()] and/or HTML [tags].
|
||||
#'
|
||||
#' @param ... The contents of the document body.
|
||||
#' @param title The browser window title (defaults to the host URL of the page)
|
||||
#' @param ... UI elements (i.e., [tags]).
|
||||
#' @param title The browser window title (defaults to the host URL of the page).
|
||||
#' Can also be set as a side effect of the [titlePanel()] function.
|
||||
#' @param theme One of the following:
|
||||
#' * `NULL` (the default), which implies a "stock" build of Bootstrap 3.
|
||||
#' * A [bslib::bs_theme()] object. This can be used to replace a stock
|
||||
#' build of Bootstrap 3 with a customized version of Bootstrap 3 or higher.
|
||||
#' * A character string pointing to an alternative Bootstrap stylesheet
|
||||
#' (normally a css file within the www directory, e.g. `www/bootstrap.css`).
|
||||
#' This option is here mainly for legacy reasons.
|
||||
#' @param lang ISO 639-1 language code for the HTML page, such as "en" or "ko".
|
||||
#' This will be used as the lang in the \code{<html>} tag, as in \code{<html lang="en">}.
|
||||
#' This will be used as the lang in the `<html>` tag, as in `<html lang="en">`.
|
||||
#' The default (NULL) results in an empty string.
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the [shinyUI] function.
|
||||
#' @return A UI definition (i.e., a [tags] object) that can be passed to [shinyApp()].
|
||||
#'
|
||||
#' @note The `basicPage` function is deprecated, you should use the
|
||||
#' [fluidPage()] function instead.
|
||||
#'
|
||||
#' @seealso [fluidPage()], [fixedPage()]
|
||||
#' @seealso [navbarPage()], [fillPage()], [column()], [tabPanel()]
|
||||
#' @export
|
||||
#' @examples
|
||||
#'
|
||||
#' # First create some UI content.
|
||||
#' # See the layout guide to learn more about creating different layouts
|
||||
#' # https://shiny.rstudio.com/articles/layout-guide.html
|
||||
#' ui <- sidebarLayout(
|
||||
#' sidebarPanel(sliderInput("obs", "Number of observations:", 0, 1000, 500)),
|
||||
#' mainPanel(plotOutput("distPlot"))
|
||||
#' )
|
||||
#' server <- function(input, output) {
|
||||
#' output$distPlot <- renderPlot(hist(rnorm(input$obs)))
|
||||
#' }
|
||||
#'
|
||||
#' # Demonstrating difference between fluidPage(), fixedPage(), bootstrapPage()
|
||||
#' if (interactive()) {
|
||||
#' # Container width scales _fluidly_ with window size
|
||||
#' shinyApp(fluidPage(ui), server)
|
||||
#' # Container width changes with window size at fixed breakpoints
|
||||
#' shinyApp(fixedPage(ui), server)
|
||||
#' # Container width is equal to the window's width
|
||||
#' shinyApp(bootstrapPage(ui), server)
|
||||
#' }
|
||||
#'
|
||||
#' # The default look is provided by Bootstrap 3, but {bslib} can be
|
||||
#' # used to customize the Bootstrap version and its default styling
|
||||
#' theme <- bslib::bs_theme(
|
||||
#' version = 5,
|
||||
#' bg = "#101010",
|
||||
#' fg = "#FDF7F7",
|
||||
#' primary = "#ED79F9",
|
||||
#' base_font = bslib::font_google("Prompt"),
|
||||
#' code_font = bslib::font_google("JetBrains Mono")
|
||||
#' )
|
||||
#' if (interactive()) {
|
||||
#' # Call thematic::thematic_shiny(font = "auto") to automatically
|
||||
#' # translate the theme/CSS to the R plot
|
||||
#' shinyApp(
|
||||
#' fluidPage(ui, theme = theme, title = "Hello Bootstrap 5"),
|
||||
#' server
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
bootstrapPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
|
||||
|
||||
args <- list(
|
||||
@@ -138,8 +186,7 @@ bs_theme_deps <- function(theme) {
|
||||
}
|
||||
|
||||
is_bs_theme <- function(x) {
|
||||
is_available("bslib", "0.2.0.9000") &&
|
||||
bslib::is_bs_theme(x)
|
||||
bslib::is_bs_theme(x)
|
||||
}
|
||||
|
||||
#' Obtain Shiny's Bootstrap Sass theme
|
||||
@@ -215,11 +262,10 @@ registerThemeDependency <- function(func) {
|
||||
|
||||
bootstrapDependency <- function(theme) {
|
||||
htmlDependency(
|
||||
"bootstrap", bootstrapVersion,
|
||||
c(
|
||||
href = "shared/bootstrap",
|
||||
file = system.file("www/shared/bootstrap", package = "shiny")
|
||||
),
|
||||
"bootstrap",
|
||||
bootstrapVersion,
|
||||
src = "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)
|
||||
@@ -236,14 +282,12 @@ bootstrapDependency <- function(theme) {
|
||||
|
||||
bootstrapVersion <- "3.4.1"
|
||||
|
||||
|
||||
#' @rdname bootstrapPage
|
||||
#' @export
|
||||
basicPage <- function(...) {
|
||||
bootstrapPage(div(class="container-fluid", list(...)))
|
||||
}
|
||||
|
||||
|
||||
#' Create a page that fills the window
|
||||
#'
|
||||
#' `fillPage` creates a page whose height and width always fill the
|
||||
@@ -1109,11 +1153,17 @@ tableOutput <- function(outputId) {
|
||||
|
||||
dataTableDependency <- list(
|
||||
htmlDependency(
|
||||
"datatables", "1.10.5", c(href = "shared/datatables"),
|
||||
"datatables",
|
||||
"1.10.5",
|
||||
src = "www/shared/datatables",
|
||||
package = "shiny",
|
||||
script = "js/jquery.dataTables.min.js"
|
||||
),
|
||||
htmlDependency(
|
||||
"datatables-bootstrap", "1.10.5", c(href = "shared/datatables"),
|
||||
"datatables-bootstrap",
|
||||
"1.10.5",
|
||||
src = "www/shared/datatables",
|
||||
package = "shiny",
|
||||
stylesheet = c("css/dataTables.bootstrap.css", "css/dataTables.extra.css"),
|
||||
script = "js/dataTables.bootstrap.js"
|
||||
)
|
||||
@@ -1153,7 +1203,7 @@ dataTableOutput <- function(outputId) {
|
||||
htmlOutput <- function(outputId, inline = FALSE,
|
||||
container = if (inline) span else div, ...)
|
||||
{
|
||||
if (anyUnnamed(list(...))) {
|
||||
if (any_unnamed(list(...))) {
|
||||
warning("Unnamed elements in ... will be replaced with dynamic UI.")
|
||||
}
|
||||
container(id = outputId, class="shiny-html-output", ...)
|
||||
|
||||
@@ -228,7 +228,9 @@ 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 (inherits(cond, "shiny.silent.error")) return()
|
||||
if (cnd_inherits(cond, "shiny.silent.error")) {
|
||||
return()
|
||||
}
|
||||
if (isTRUE(getOption("show.error.messages"))) {
|
||||
printError(cond, full = full, offset = offset)
|
||||
}
|
||||
@@ -239,7 +241,7 @@ withLogErrors <- function(expr,
|
||||
},
|
||||
error = function(cond) {
|
||||
# Don't print shiny.silent.error (i.e. validation errors)
|
||||
if (inherits(cond, "shiny.silent.error")) return()
|
||||
if (cnd_inherits(cond, "shiny.silent.error")) return()
|
||||
if (isTRUE(getOption("show.error.messages"))) {
|
||||
printError(cond, full = full, offset = offset)
|
||||
}
|
||||
|
||||
55
R/globals.R
55
R/globals.R
@@ -1,55 +1,6 @@
|
||||
# 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
|
||||
@@ -62,9 +13,9 @@ register_upgrade_message <- function(pkg, version) {
|
||||
|
||||
# Make sure these methods are available to knitr if shiny is loaded but not
|
||||
# attached.
|
||||
register_s3_method("knitr", "knit_print", "reactive")
|
||||
register_s3_method("knitr", "knit_print", "shiny.appobj")
|
||||
register_s3_method("knitr", "knit_print", "shiny.render.function")
|
||||
s3_register("knitr::knit_print", "reactive")
|
||||
s3_register("knitr::knit_print", "shiny.appobj")
|
||||
s3_register("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
|
||||
|
||||
36
R/graph.R
36
R/graph.R
@@ -4,7 +4,7 @@
|
||||
# @param version The version of the package
|
||||
check_suggested <- function(package, version = NULL) {
|
||||
|
||||
if (is_available(package, version)) {
|
||||
if (is_installed(package, version)) {
|
||||
return()
|
||||
}
|
||||
|
||||
@@ -115,22 +115,28 @@ check_reactlog <- function() {
|
||||
}
|
||||
# read reactlog version from description file
|
||||
# prevents version mismatch in code and description file
|
||||
reactlog_version <- function() {
|
||||
desc <- read.dcf(system.file("DESCRIPTION", package = "shiny", mustWork = TRUE))
|
||||
suggests <- desc[1,"Suggests"][[1]]
|
||||
suggests_pkgs <- strsplit(suggests, "\n")[[1]]
|
||||
reactlog_version <- local({
|
||||
version <- NULL
|
||||
function() {
|
||||
if (!is.null(version)) return(version)
|
||||
|
||||
reactlog_info <- suggests_pkgs[grepl("reactlog", suggests_pkgs)]
|
||||
if (length(reactlog_info) == 0) {
|
||||
stop("reactlog can not be found in shiny DESCRIPTION file")
|
||||
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 <- sub("^[^\\(]*\\(", "", reactlog_info)
|
||||
reactlog_info <- sub("\\)[^\\)]*$", "", reactlog_info)
|
||||
reactlog_info <- sub("^[>= ]*", "", reactlog_info)
|
||||
|
||||
package_version(reactlog_info)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
RLog <- R6Class(
|
||||
|
||||
@@ -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_available("ragg")) {
|
||||
if ((getOption('shiny.useragg') %||% FALSE) && is_installed("ragg")) {
|
||||
pngfun <- ragg::agg_png
|
||||
} else if (capabilities("aqua")) {
|
||||
# i.e., png(type = 'quartz')
|
||||
pngfun <- grDevices::png
|
||||
} else if ((getOption('shiny.usecairo') %||% TRUE) && is_available("Cairo")) {
|
||||
} else if ((getOption('shiny.usecairo') %||% TRUE) && is_installed("Cairo")) {
|
||||
pngfun <- Cairo::CairoPNG
|
||||
} else {
|
||||
# i.e., png(type = 'cairo')
|
||||
|
||||
@@ -138,7 +138,8 @@ datePickerDependency <- function(theme) {
|
||||
htmlDependency(
|
||||
name = "bootstrap-datepicker-js",
|
||||
version = version_bs_date_picker,
|
||||
src = c(href = "shared/datepicker"),
|
||||
src = "www/shared/datepicker",
|
||||
package = "shiny",
|
||||
script = if (getOption("shiny.minified", TRUE)) "js/bootstrap-datepicker.min.js"
|
||||
else "js/bootstrap-datepicker.js",
|
||||
# Need to enable noConflict mode. See #1346.
|
||||
@@ -157,18 +158,19 @@ datePickerCSS <- function(theme) {
|
||||
return(htmlDependency(
|
||||
name = "bootstrap-datepicker-css",
|
||||
version = version_bs_date_picker,
|
||||
src = c(href = "shared/datepicker"),
|
||||
src = "www/shared/datepicker",
|
||||
package = "shiny",
|
||||
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 = shinyPackageVersion()
|
||||
cache_key_extra = get_package_version("shiny")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -213,14 +213,7 @@ selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
|
||||
deps <- list(selectizeDependency())
|
||||
|
||||
if ('drag_drop' %in% options$plugins) {
|
||||
deps <- c(
|
||||
deps,
|
||||
list(htmlDependency(
|
||||
'jqueryui', '1.12.1',
|
||||
c(href = 'shared/jqueryui'),
|
||||
script = 'jquery-ui.min.js'
|
||||
))
|
||||
)
|
||||
deps[[length(deps) + 1]] <- jqueryuiDependency()
|
||||
}
|
||||
|
||||
# Insert script on same level as <select> tag
|
||||
@@ -247,7 +240,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")
|
||||
@@ -267,15 +260,17 @@ selectizeDependencyFunc <- function(theme) {
|
||||
theme = theme,
|
||||
name = "selectize",
|
||||
version = version_selectize,
|
||||
cache_key_extra = shinyPackageVersion(),
|
||||
cache_key_extra = get_package_version("shiny"),
|
||||
.dep_args = list(script = script)
|
||||
)
|
||||
}
|
||||
|
||||
selectizeStaticDependency <- function(version) {
|
||||
htmlDependency(
|
||||
"selectize", version,
|
||||
src = c(href = "shared/selectize"),
|
||||
"selectize",
|
||||
version,
|
||||
src = "www/shared/selectize",
|
||||
package = "shiny",
|
||||
stylesheet = "css/selectize.bootstrap3.css",
|
||||
script = c(
|
||||
"js/selectize.min.js",
|
||||
|
||||
@@ -205,13 +205,17 @@ ionRangeSliderDependency <- function() {
|
||||
list(
|
||||
# ion.rangeSlider also needs normalize.css, which is already included in Bootstrap.
|
||||
htmlDependency(
|
||||
"ionrangeslider-javascript", version_ion_range_slider,
|
||||
src = c(href = "shared/ionrangeslider"),
|
||||
"ionrangeslider-javascript",
|
||||
version_ion_range_slider,
|
||||
src = "www/shared/ionrangeslider",
|
||||
package = "shiny",
|
||||
script = "js/ion.rangeSlider.min.js"
|
||||
),
|
||||
htmlDependency(
|
||||
"strftime", version_strftime,
|
||||
src = c(href = "shared/strftime"),
|
||||
"strftime",
|
||||
version_strftime,
|
||||
src = "www/shared/strftime",
|
||||
package = "shiny",
|
||||
script = "strftime-min.js"
|
||||
),
|
||||
bslib::bs_dependency_defer(ionRangeSliderDependencyCSS)
|
||||
@@ -223,7 +227,8 @@ ionRangeSliderDependencyCSS <- function(theme) {
|
||||
return(htmlDependency(
|
||||
"ionrangeslider-css",
|
||||
version_ion_range_slider,
|
||||
src = c(href = "shared/ionrangeslider"),
|
||||
src = "www/shared/ionrangeslider",
|
||||
package = "shiny",
|
||||
stylesheet = "css/ion.rangeSlider.css"
|
||||
))
|
||||
}
|
||||
@@ -232,13 +237,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 = shinyPackageVersion()
|
||||
cache_key_extra = get_package_version("shiny")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ normalizeChoicesArgs <- function(choices, choiceNames, choiceValues,
|
||||
if (length(choiceNames) != length(choiceValues)) {
|
||||
stop("`choiceNames` and `choiceValues` must have the same length.")
|
||||
}
|
||||
if (anyNamed(choiceNames) || anyNamed(choiceValues)) {
|
||||
if (any_named(choiceNames) || any_named(choiceValues)) {
|
||||
stop("`choiceNames` and `choiceValues` must not be named.")
|
||||
}
|
||||
} else {
|
||||
|
||||
13
R/jqueryui.R
13
R/jqueryui.R
@@ -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,3 +99,14 @@ 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'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
output <- knitr::knit_print(tagList(x), ..., inline = inline)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Promise helpers taken from:
|
||||
# https://github.com/rstudio/promises/blob/master/tests/testthat/common.R
|
||||
# https://github.com/rstudio/promises/blob/main/tests/testthat/common.R
|
||||
# Block until all pending later tasks have executed
|
||||
wait_for_it <- function() {
|
||||
while (!later::loop_empty()) {
|
||||
|
||||
@@ -1203,7 +1203,7 @@ Observer <- R6Class(
|
||||
# validation = function(e) NULL,
|
||||
# shiny.output.cancel = function(e) NULL
|
||||
|
||||
if (inherits(e, "shiny.silent.error")) {
|
||||
if (cnd_inherits(e, "shiny.silent.error")) {
|
||||
return()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <- utils::packageVersion("ggplot2")
|
||||
ggplot_ver <- get_package_version("ggplot2")
|
||||
|
||||
if (ggplot_ver > "2.2.1") {
|
||||
find_panel_info_api(b)
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
#' runUrl('https://github.com/rstudio/shiny_example/archive/main.tar.gz')
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the archive
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/main.zip",
|
||||
#' subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, destdir = NULL, ...) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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(class(val), "shinyActionButtonValue")
|
||||
class(val) <- c("shinyActionButtonValue", class(val))
|
||||
val
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
15
R/shiny.R
15
R/shiny.R
@@ -403,7 +403,7 @@ ShinySession <- R6Class(
|
||||
sendMessage = function(...) {
|
||||
# This function is a wrapper for $write
|
||||
msg <- list(...)
|
||||
if (anyUnnamed(msg)) {
|
||||
if (any_unnamed(msg)) {
|
||||
stop("All arguments to sendMessage must be named.")
|
||||
}
|
||||
private$write(toJSON(msg))
|
||||
@@ -825,7 +825,7 @@ ShinySession <- R6Class(
|
||||
dots <- eval(substitute(alist(...)))
|
||||
}
|
||||
|
||||
if (anyUnnamed(dots))
|
||||
if (any_unnamed(dots))
|
||||
stop("exportTestValues: all arguments must be named.")
|
||||
|
||||
names(dots) <- ns(names(dots))
|
||||
@@ -913,7 +913,7 @@ ShinySession <- R6Class(
|
||||
|
||||
# Copy `values` from scopeState to state, adding namespace
|
||||
if (length(scopeState$values) != 0) {
|
||||
if (anyUnnamed(scopeState$values)) {
|
||||
if (any_unnamed(scopeState$values)) {
|
||||
stop("All scope values in must be named.")
|
||||
}
|
||||
|
||||
@@ -1114,7 +1114,12 @@ ShinySession <- R6Class(
|
||||
structure(list(), class = "try-error", condition = cond)
|
||||
} else if (inherits(cond, "shiny.output.cancel")) {
|
||||
structure(list(), class = "cancel-output")
|
||||
} else if (inherits(cond, "shiny.silent.error")) {
|
||||
} 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
|
||||
}
|
||||
# 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
|
||||
@@ -1701,7 +1706,7 @@ ShinySession <- R6Class(
|
||||
dots <- eval(substitute(alist(...)))
|
||||
}
|
||||
|
||||
if (anyUnnamed(dots))
|
||||
if (any_unnamed(dots))
|
||||
stop("exportTestValues: all arguments must be named.")
|
||||
|
||||
# Create a named list where each item is a list with an expression and
|
||||
|
||||
@@ -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
|
||||
|
||||
46
R/shinyui.R
46
R/shinyui.R
@@ -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,8 +55,14 @@ 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", shinyPackageVersion(),
|
||||
c(href="shared"), script = "shiny-testmode.js")
|
||||
htmlDependency(
|
||||
"shiny-testmode",
|
||||
get_package_version("shiny"),
|
||||
src = "www/shared",
|
||||
package = "shiny",
|
||||
script = "shiny-testmode.js",
|
||||
all_files = FALSE
|
||||
)
|
||||
}
|
||||
|
||||
html <- renderDocument(ui, shiny_deps, processDep = createWebDependency)
|
||||
@@ -68,23 +74,19 @@ jqueryDependency <- function() {
|
||||
if (version == 3) {
|
||||
return(htmlDependency(
|
||||
"jquery", version_jquery,
|
||||
src = c(
|
||||
href = "shared",
|
||||
file = "www/shared"
|
||||
),
|
||||
src = "www/shared",
|
||||
package = "shiny",
|
||||
script = "jquery.min.js"
|
||||
script = "jquery.min.js",
|
||||
all_files = FALSE
|
||||
))
|
||||
}
|
||||
if (version == 1) {
|
||||
return(htmlDependency(
|
||||
"jquery", "1.12.4",
|
||||
src = c(
|
||||
href = "shared/legacy",
|
||||
file = "www/shared/legacy"
|
||||
),
|
||||
src = "www/shared/legacy",
|
||||
package = "shiny",
|
||||
script = "jquery.min.js"
|
||||
script = "jquery.min.js",
|
||||
all_files = FALSE
|
||||
))
|
||||
}
|
||||
stop("Unsupported version of jQuery: ", version)
|
||||
@@ -95,8 +97,9 @@ shinyDependencies <- function() {
|
||||
bslib::bs_dependency_defer(shinyDependencyCSS),
|
||||
htmlDependency(
|
||||
name = "shiny-javascript",
|
||||
version = shinyPackageVersion(),
|
||||
src = c(href = "shared"),
|
||||
version = get_package_version("shiny"),
|
||||
src = "www/shared",
|
||||
package = "shiny",
|
||||
script =
|
||||
if (isTRUE(
|
||||
get_devmode_option(
|
||||
@@ -106,24 +109,27 @@ shinyDependencies <- function() {
|
||||
))
|
||||
"shiny.min.js"
|
||||
else
|
||||
"shiny.js"
|
||||
"shiny.js",
|
||||
all_files = FALSE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
shinyDependencyCSS <- function(theme) {
|
||||
version <- shinyPackageVersion()
|
||||
version <- get_package_version("shiny")
|
||||
|
||||
if (!is_bs_theme(theme)) {
|
||||
return(htmlDependency(
|
||||
name = "shiny-css",
|
||||
version = version,
|
||||
src = c(href = "shared"),
|
||||
stylesheet = "shiny.min.css"
|
||||
src = "www/shared",
|
||||
package = "shiny",
|
||||
stylesheet = "shiny.min.css",
|
||||
all_files = FALSE
|
||||
))
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -953,7 +953,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
|
||||
|
||||
42
R/showcase.R
42
R/showcase.R
@@ -32,26 +32,40 @@ licenseLink <- function(licenseName) {
|
||||
showcaseHead <- function() {
|
||||
|
||||
deps <- list(
|
||||
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")
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
mdfile <- file.path.ci(getwd(), 'Readme.md')
|
||||
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"),
|
||||
html <- tagList(
|
||||
if (file.exists(mdfile))
|
||||
script(type="text/markdown", id="showcase-markdown-content",
|
||||
tags$script(type="text/markdown", id="showcase-markdown-content",
|
||||
paste(readUTF8(mdfile), collapse="\n"))
|
||||
else ""
|
||||
))
|
||||
)
|
||||
|
||||
return(attachDependencies(html, deps))
|
||||
}
|
||||
|
||||
216
R/staticimports.R
Normal file
216
R/staticimports.R
Normal file
@@ -0,0 +1,216 @@
|
||||
# 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, ...)
|
||||
}
|
||||
})
|
||||
69
R/utils.R
69
R/utils.R
@@ -2,6 +2,11 @@
|
||||
#' @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
|
||||
@@ -126,34 +131,6 @@ 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) {
|
||||
@@ -173,7 +150,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 (anyUnnamed(a) || anyUnnamed(b)) {
|
||||
if (any_unnamed(a) || any_unnamed(b)) {
|
||||
stop("Vectors must be either NULL or have names for all elements")
|
||||
}
|
||||
|
||||
@@ -186,7 +163,7 @@ mergeVectors <- function(a, b) {
|
||||
# same name, preserve the original order of those items. For empty
|
||||
# vectors/lists/NULL, return the original value.
|
||||
sortByName <- function(x) {
|
||||
if (anyUnnamed(x))
|
||||
if (any_unnamed(x))
|
||||
stop("All items must be named")
|
||||
|
||||
# Special case for empty vectors/lists, and NULL
|
||||
@@ -495,7 +472,7 @@ shinyCallingHandlers <- function(expr) {
|
||||
withCallingHandlers(captureStackTraces(expr),
|
||||
error = function(e) {
|
||||
# Don't intercept shiny.silent.error (i.e. validation errors)
|
||||
if (inherits(e, "shiny.silent.error"))
|
||||
if (cnd_inherits(e, "shiny.silent.error"))
|
||||
return()
|
||||
|
||||
handle <- getOption('shiny.error')
|
||||
@@ -1716,24 +1693,20 @@ findEnclosingApp <- function(path = ".") {
|
||||
}
|
||||
}
|
||||
|
||||
# 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)
|
||||
}
|
||||
installed && isTRUE(utils::packageVersion(package) >= version)
|
||||
# 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)
|
||||
|
||||
|
||||
# cached version of utils::packageVersion("shiny")
|
||||
shinyPackageVersion <- local({
|
||||
version <- NULL
|
||||
function() {
|
||||
if (is.null(version)) {
|
||||
version <<- utils::packageVersion("shiny")
|
||||
while (rlang::is_condition(.cnd)) {
|
||||
if (.p(.cnd, ...)) {
|
||||
return(TRUE)
|
||||
}
|
||||
version
|
||||
|
||||
.cnd <- .cnd$parent
|
||||
}
|
||||
})
|
||||
|
||||
FALSE
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<!-- badges: start -->
|
||||
[](https://CRAN.R-project.org/package=shiny)
|
||||
[](https://github.com/rstudio/shiny/actions)
|
||||
[](https://github.com/rstudio/shiny/actions)
|
||||
[](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/master/.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/main/.github/CONTRIBUTING.md) file for detailed guidelines of how to contribute.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.7.0 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.7.1.9002 | (c) 2012-2022 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==
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.7.0 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.7.1.9002 | (c) 2012-2022 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
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.7.0 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.7.1.9002 | (c) 2012-2022 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*! shiny 1.7.0 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.7.1.9002 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
(function() {
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
@@ -2131,6 +2131,19 @@
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/core-js/internals/array-for-each.js
|
||||
var require_array_for_each = __commonJS({
|
||||
"node_modules/core-js/internals/array-for-each.js": function(exports, module) {
|
||||
"use strict";
|
||||
var $forEach2 = require_array_iteration().forEach;
|
||||
var arrayMethodIsStrict4 = require_array_method_is_strict();
|
||||
var STRICT_METHOD4 = arrayMethodIsStrict4("forEach");
|
||||
module.exports = !STRICT_METHOD4 ? function forEach3(callbackfn) {
|
||||
return $forEach2(this, callbackfn, arguments.length > 1 ? arguments[1] : void 0);
|
||||
} : [].forEach;
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/core-js/internals/object-to-array.js
|
||||
var require_object_to_array = __commonJS({
|
||||
"node_modules/core-js/internals/object-to-array.js": function(exports, module) {
|
||||
@@ -2769,19 +2782,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/core-js/internals/array-for-each.js
|
||||
var require_array_for_each = __commonJS({
|
||||
"node_modules/core-js/internals/array-for-each.js": function(exports, module) {
|
||||
"use strict";
|
||||
var $forEach2 = require_array_iteration().forEach;
|
||||
var arrayMethodIsStrict4 = require_array_method_is_strict();
|
||||
var STRICT_METHOD4 = arrayMethodIsStrict4("forEach");
|
||||
module.exports = !STRICT_METHOD4 ? function forEach3(callbackfn) {
|
||||
return $forEach2(this, callbackfn, arguments.length > 1 ? arguments[1] : void 0);
|
||||
} : [].forEach;
|
||||
}
|
||||
});
|
||||
|
||||
// srcts/src/initialize/disableForm.ts
|
||||
var import_jquery = __toModule(require_jquery());
|
||||
function disableFormSubmission() {
|
||||
@@ -3762,7 +3762,7 @@
|
||||
}, {
|
||||
key: "getId",
|
||||
value: function getId(el) {
|
||||
return el["data-input-id"] || el.id;
|
||||
return el.getAttribute("data-input-id") || el.id;
|
||||
}
|
||||
}, {
|
||||
key: "getType",
|
||||
@@ -8040,7 +8040,7 @@
|
||||
}, {
|
||||
key: "getId",
|
||||
value: function getId(el) {
|
||||
return el["data-input-id"] || el.id;
|
||||
return el.getAttribute("data-input-id") || el.id;
|
||||
}
|
||||
}, {
|
||||
key: "onValueChange",
|
||||
@@ -8825,29 +8825,53 @@
|
||||
var import_es_array_iterator20 = __toModule(require_es_array_iterator());
|
||||
var import_jquery28 = __toModule(require_jquery());
|
||||
|
||||
// srcts/src/shiny/render.ts
|
||||
var import_es_regexp_exec6 = __toModule(require_es_regexp_exec());
|
||||
// node_modules/core-js/modules/es.array.for-each.js
|
||||
"use strict";
|
||||
var $45 = require_export();
|
||||
var forEach = require_array_for_each();
|
||||
$45({ target: "Array", proto: true, forced: [].forEach != forEach }, {
|
||||
forEach: forEach
|
||||
});
|
||||
|
||||
// node_modules/core-js/modules/web.dom-collections.for-each.js
|
||||
var global6 = require_global();
|
||||
var DOMIterables2 = require_dom_iterables();
|
||||
var forEach2 = require_array_for_each();
|
||||
var createNonEnumerableProperty3 = require_create_non_enumerable_property();
|
||||
for (var COLLECTION_NAME in DOMIterables2) {
|
||||
Collection = global6[COLLECTION_NAME];
|
||||
CollectionPrototype = Collection && Collection.prototype;
|
||||
if (CollectionPrototype && CollectionPrototype.forEach !== forEach2)
|
||||
try {
|
||||
createNonEnumerableProperty3(CollectionPrototype, "forEach", forEach2);
|
||||
} catch (error) {
|
||||
CollectionPrototype.forEach = forEach2;
|
||||
}
|
||||
}
|
||||
var Collection;
|
||||
var CollectionPrototype;
|
||||
|
||||
// node_modules/core-js/modules/es.object.entries.js
|
||||
var $45 = require_export();
|
||||
var $46 = require_export();
|
||||
var $entries = require_object_to_array().entries;
|
||||
$45({ target: "Object", stat: true }, {
|
||||
$46({ target: "Object", stat: true }, {
|
||||
entries: function entries(O) {
|
||||
return $entries(O);
|
||||
}
|
||||
});
|
||||
|
||||
// srcts/src/shiny/render.ts
|
||||
var import_es_regexp_exec6 = __toModule(require_es_regexp_exec());
|
||||
var import_es_array_iterator19 = __toModule(require_es_array_iterator());
|
||||
|
||||
// node_modules/core-js/modules/es.array.from.js
|
||||
var $46 = require_export();
|
||||
var $47 = require_export();
|
||||
var from = require_array_from();
|
||||
var checkCorrectnessOfIteration = require_check_correctness_of_iteration();
|
||||
var INCORRECT_ITERATION = !checkCorrectnessOfIteration(function(iterable) {
|
||||
Array.from(iterable);
|
||||
});
|
||||
$46({ target: "Array", stat: true, forced: INCORRECT_ITERATION }, {
|
||||
$47({ target: "Array", stat: true, forced: INCORRECT_ITERATION }, {
|
||||
from: from
|
||||
});
|
||||
|
||||
@@ -9064,9 +9088,7 @@
|
||||
}
|
||||
function renderDependencies(dependencies) {
|
||||
if (dependencies) {
|
||||
import_jquery27.default.each(dependencies, function(i, dep) {
|
||||
renderDependency(dep);
|
||||
});
|
||||
dependencies.forEach(renderDependency);
|
||||
}
|
||||
}
|
||||
function renderContent(el, content) {
|
||||
@@ -9074,7 +9096,7 @@
|
||||
if (where === "replace") {
|
||||
shinyUnbindAll(el);
|
||||
}
|
||||
var html;
|
||||
var html = "";
|
||||
var dependencies = [];
|
||||
if (content === null) {
|
||||
html = "";
|
||||
@@ -9123,128 +9145,208 @@
|
||||
}
|
||||
return htmlDependencies[names[idx]] === dep.version;
|
||||
}
|
||||
function renderDependency(dep) {
|
||||
var restyle = needsRestyle(dep);
|
||||
if (hasOwnProperty(htmlDependencies, dep.name) && !restyle)
|
||||
function renderDependency(dep_) {
|
||||
var dep = normalizeHtmlDependency(dep_);
|
||||
var stylesheetLinks = dep.stylesheet.map(function(x) {
|
||||
if (!hasOwnProperty(x, "rel"))
|
||||
x.rel = "stylesheet";
|
||||
if (!hasOwnProperty(x, "type"))
|
||||
x.type = "text/css";
|
||||
var link = document.createElement("link");
|
||||
Object.entries(x).forEach(function(_ref) {
|
||||
var _ref2 = _slicedToArray(_ref, 2), attr = _ref2[0], val = _ref2[1];
|
||||
if (attr === "href") {
|
||||
val = encodeURI(val);
|
||||
}
|
||||
link.setAttribute(attr, val ? val : "");
|
||||
});
|
||||
return link;
|
||||
});
|
||||
if (needsRestyle(dep)) {
|
||||
addStylesheetsAndRestyle(stylesheetLinks);
|
||||
return true;
|
||||
}
|
||||
if (hasOwnProperty(htmlDependencies, dep.name))
|
||||
return false;
|
||||
registerDependency(dep.name, dep.version);
|
||||
var href = dep.src.href;
|
||||
var $head = (0, import_jquery27.default)("head").first();
|
||||
if (dep.meta && !restyle) {
|
||||
var metas = import_jquery27.default.map(asArray(dep.meta), function(obj, idx) {
|
||||
var name = Object.keys(obj)[0];
|
||||
return (0, import_jquery27.default)("<meta>").attr("name", name).attr("content", obj[name]);
|
||||
idx;
|
||||
});
|
||||
$head.append(metas);
|
||||
}
|
||||
if (dep.stylesheet) {
|
||||
var links = import_jquery27.default.map(asArray(dep.stylesheet), function(stylesheet) {
|
||||
return (0, import_jquery27.default)("<link rel='stylesheet' type='text/css'>").attr("href", href + "/" + encodeURI(stylesheet));
|
||||
});
|
||||
if (!restyle) {
|
||||
$head.append(links);
|
||||
} else {
|
||||
var refreshStyle = function refreshStyle2(href2, oldSheet) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", href2);
|
||||
xhr.onload = function() {
|
||||
var id = "shiny_restyle_" + href2.split("?restyle")[0].replace(/\W/g, "_");
|
||||
var oldStyle = $head.find("style#" + id);
|
||||
var newStyle = (0, import_jquery27.default)("<style>").attr("id", id).html(xhr.responseText);
|
||||
$head.append(newStyle);
|
||||
oldStyle.remove();
|
||||
removeSheet(oldSheet);
|
||||
sendImageSizeFns.transitioned();
|
||||
};
|
||||
xhr.send();
|
||||
};
|
||||
var findSheet = function findSheet2(href2) {
|
||||
for (var i = 0; i < document.styleSheets.length; i++) {
|
||||
var sheet = document.styleSheets[i];
|
||||
if (typeof sheet.href === "string" && sheet.href.indexOf(href2) > -1) {
|
||||
return sheet;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
var removeSheet = function removeSheet2(sheet) {
|
||||
if (!sheet)
|
||||
return;
|
||||
sheet.disabled = true;
|
||||
if (isIE())
|
||||
sheet.cssText = "";
|
||||
(0, import_jquery27.default)(sheet.ownerNode).remove();
|
||||
};
|
||||
import_jquery27.default.map(links, function(link) {
|
||||
var oldSheet = findSheet(link.attr("href"));
|
||||
var href2 = link.attr("href") + "?restyle=" + new Date().getTime();
|
||||
if (isIE()) {
|
||||
refreshStyle(href2, oldSheet);
|
||||
} else {
|
||||
link.attr("href", href2);
|
||||
link.attr("onload", function() {
|
||||
var $dummyEl = (0, import_jquery27.default)("<div>").css("transition", "0.1s all").css("position", "absolute").css("top", "-1000px").css("left", "0");
|
||||
$dummyEl.one("transitionend", function() {
|
||||
$dummyEl.remove();
|
||||
removeSheet(oldSheet);
|
||||
sendImageSizeFns.transitioned();
|
||||
});
|
||||
(0, import_jquery27.default)(document.body).append($dummyEl);
|
||||
var color = "#" + Math.floor(Math.random() * 16777215).toString(16);
|
||||
setTimeout(function() {
|
||||
return $dummyEl.css("color", color);
|
||||
}, 10);
|
||||
});
|
||||
$head.append(link);
|
||||
}
|
||||
});
|
||||
dep.meta.forEach(function(x) {
|
||||
var meta = document.createElement("meta");
|
||||
for (var _i2 = 0, _Object$entries = Object.entries(x); _i2 < _Object$entries.length; _i2++) {
|
||||
var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2), attr = _Object$entries$_i[0], val = _Object$entries$_i[1];
|
||||
meta.setAttribute(attr, val);
|
||||
}
|
||||
$head.append(meta);
|
||||
});
|
||||
if (stylesheetLinks.length !== 0) {
|
||||
$head.append(stylesheetLinks);
|
||||
}
|
||||
if (dep.script && !restyle) {
|
||||
var scriptsAttrs = asArray(dep.script);
|
||||
var scripts = import_jquery27.default.map(scriptsAttrs, function(x) {
|
||||
var script = document.createElement("script");
|
||||
if (typeof x === "string") {
|
||||
x = {
|
||||
src: x
|
||||
};
|
||||
dep.script.forEach(function(x) {
|
||||
var script = document.createElement("script");
|
||||
Object.entries(x).forEach(function(_ref3) {
|
||||
var _ref4 = _slicedToArray(_ref3, 2), attr = _ref4[0], val = _ref4[1];
|
||||
if (attr === "src") {
|
||||
val = encodeURI(val);
|
||||
}
|
||||
for (var _i = 0, _Object$entries = Object.entries(x); _i < _Object$entries.length; _i++) {
|
||||
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), attr = _Object$entries$_i[0], val = _Object$entries$_i[1];
|
||||
if (attr === "src") {
|
||||
val = href + "/" + encodeURI(val);
|
||||
}
|
||||
script.setAttribute(attr, val ? val : "");
|
||||
}
|
||||
return script;
|
||||
script.setAttribute(attr, val ? val : "");
|
||||
});
|
||||
$head.append(scripts);
|
||||
}
|
||||
if (dep.attachment && !restyle) {
|
||||
var attachments = dep.attachment;
|
||||
if (typeof attachments === "string")
|
||||
attachments = [attachments];
|
||||
if (Array.isArray(attachments)) {
|
||||
var tmp = {};
|
||||
import_jquery27.default.each(attachments, function(index, attachment) {
|
||||
var key = index + 1 + "";
|
||||
tmp[key] = attachment;
|
||||
});
|
||||
attachments = tmp;
|
||||
}
|
||||
var attach = import_jquery27.default.map(attachments, function(attachment, key) {
|
||||
return (0, import_jquery27.default)("<link rel='attachment'>").attr("id", dep.name + "-" + key + "-attachment").attr("href", href + "/" + encodeURI(attachment));
|
||||
});
|
||||
$head.append(attach);
|
||||
}
|
||||
if (dep.head && !restyle) {
|
||||
$head.append(script);
|
||||
});
|
||||
dep.attachment.forEach(function(x) {
|
||||
var link = (0, import_jquery27.default)("<link rel='attachment'>").attr("id", dep.name + "-" + x.key + "-attachment").attr("href", encodeURI(x.href));
|
||||
$head.append(link);
|
||||
});
|
||||
if (dep.head) {
|
||||
var $newHead = (0, import_jquery27.default)("<head></head>");
|
||||
$newHead.html(dep.head);
|
||||
$head.append($newHead.children());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function addStylesheetsAndRestyle(links) {
|
||||
var $head = (0, import_jquery27.default)("head").first();
|
||||
var refreshStyle = function refreshStyle2(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 = (0, import_jquery27.default)("<style>").attr("id", id).html(xhr.responseText);
|
||||
$head.append(newStyle);
|
||||
oldStyle.remove();
|
||||
removeSheet(oldSheet);
|
||||
sendImageSizeFns.transitioned();
|
||||
};
|
||||
xhr.send();
|
||||
};
|
||||
var findSheet = function findSheet2(href) {
|
||||
if (!href)
|
||||
return null;
|
||||
for (var i = 0; i < document.styleSheets.length; i++) {
|
||||
var sheet = document.styleSheets[i];
|
||||
if (typeof sheet.href === "string" && sheet.href.indexOf(href) > -1) {
|
||||
return sheet;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
var removeSheet = function removeSheet2(sheet) {
|
||||
if (!sheet)
|
||||
return;
|
||||
sheet.disabled = true;
|
||||
if (isIE())
|
||||
sheet.cssText = "";
|
||||
if (sheet.ownerNode instanceof Element) {
|
||||
(0, import_jquery27.default)(sheet.ownerNode).remove();
|
||||
}
|
||||
};
|
||||
links.map(function(link) {
|
||||
var $link = (0, import_jquery27.default)(link);
|
||||
var oldSheet = findSheet($link.attr("href"));
|
||||
var href = $link.attr("href") + "?restyle=" + new Date().getTime();
|
||||
if (isIE()) {
|
||||
refreshStyle(href, oldSheet);
|
||||
} else {
|
||||
$link.attr("href", href);
|
||||
$link.attr("onload", function() {
|
||||
var $dummyEl = (0, import_jquery27.default)("<div>").css("transition", "0.1s all").css("position", "absolute").css("top", "-1000px").css("left", "0");
|
||||
$dummyEl.one("transitionend", function() {
|
||||
$dummyEl.remove();
|
||||
removeSheet(oldSheet);
|
||||
sendImageSizeFns.transitioned();
|
||||
});
|
||||
(0, import_jquery27.default)(document.body).append($dummyEl);
|
||||
var color = "#" + Math.floor(Math.random() * 16777215).toString(16);
|
||||
setTimeout(function() {
|
||||
return $dummyEl.css("color", color);
|
||||
}, 10);
|
||||
});
|
||||
$head.append(link);
|
||||
}
|
||||
});
|
||||
}
|
||||
function normalizeHtmlDependency(dep) {
|
||||
var _dep$src;
|
||||
var hrefPrefix = (_dep$src = dep.src) === null || _dep$src === void 0 ? void 0 : _dep$src.href;
|
||||
var result = {
|
||||
name: dep.name,
|
||||
version: dep.version,
|
||||
restyle: dep.restyle,
|
||||
meta: [],
|
||||
stylesheet: [],
|
||||
script: [],
|
||||
attachment: [],
|
||||
head: dep.head
|
||||
};
|
||||
if (dep.meta) {
|
||||
if (Array.isArray(dep.meta)) {
|
||||
result.meta = dep.meta;
|
||||
} else {
|
||||
result.meta = Object.entries(dep.meta).map(function(_ref5) {
|
||||
var _ref6 = _slicedToArray(_ref5, 2), attr = _ref6[0], val = _ref6[1];
|
||||
return {
|
||||
name: attr,
|
||||
content: val
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
result.stylesheet = asArray(dep.stylesheet).map(function(s) {
|
||||
if (typeof s === "string") {
|
||||
s = {
|
||||
href: s
|
||||
};
|
||||
}
|
||||
if (hrefPrefix) {
|
||||
s.href = hrefPrefix + "/" + s.href;
|
||||
}
|
||||
return s;
|
||||
});
|
||||
result.script = asArray(dep.script).map(function(s) {
|
||||
if (typeof s === "string") {
|
||||
s = {
|
||||
src: s
|
||||
};
|
||||
}
|
||||
if (hrefPrefix) {
|
||||
s.src = hrefPrefix + "/" + s.src;
|
||||
}
|
||||
return s;
|
||||
});
|
||||
var attachments = dep.attachment;
|
||||
if (!attachments)
|
||||
attachments = [];
|
||||
if (typeof attachments === "string")
|
||||
attachments = [attachments];
|
||||
if (Array.isArray(attachments)) {
|
||||
var tmp = attachments;
|
||||
attachments = tmp.map(function(attachment, index) {
|
||||
if (typeof attachment === "string") {
|
||||
return {
|
||||
key: (index + 1).toString(),
|
||||
href: attachment
|
||||
};
|
||||
} else {
|
||||
return attachment;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
attachments = Object.entries(attachments).map(function(_ref7) {
|
||||
var _ref8 = _slicedToArray(_ref7, 2), attr = _ref8[0], val = _ref8[1];
|
||||
return {
|
||||
key: attr,
|
||||
href: val
|
||||
};
|
||||
});
|
||||
}
|
||||
result.attachment = attachments.map(function(s) {
|
||||
if (hrefPrefix) {
|
||||
s.href = hrefPrefix + "/" + s.href;
|
||||
}
|
||||
return s;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// srcts/src/bindings/output/html.ts
|
||||
function _typeof20(obj) {
|
||||
@@ -9372,11 +9474,11 @@
|
||||
|
||||
// node_modules/core-js/modules/es.array.filter.js
|
||||
"use strict";
|
||||
var $50 = require_export();
|
||||
var $51 = require_export();
|
||||
var $filter = require_array_iteration().filter;
|
||||
var arrayMethodHasSpeciesSupport5 = require_array_method_has_species_support();
|
||||
var HAS_SPECIES_SUPPORT4 = arrayMethodHasSpeciesSupport5("filter");
|
||||
$50({ target: "Array", proto: true, forced: !HAS_SPECIES_SUPPORT4 }, {
|
||||
$51({ target: "Array", proto: true, forced: !HAS_SPECIES_SUPPORT4 }, {
|
||||
filter: function filter(callbackfn) {
|
||||
return $filter(this, callbackfn, arguments.length > 1 ? arguments[1] : void 0);
|
||||
}
|
||||
@@ -11940,21 +12042,21 @@
|
||||
|
||||
// node_modules/core-js/modules/es.array-buffer.constructor.js
|
||||
"use strict";
|
||||
var $63 = require_export();
|
||||
var global6 = require_global();
|
||||
var $64 = require_export();
|
||||
var global7 = require_global();
|
||||
var arrayBufferModule = require_array_buffer();
|
||||
var setSpecies = require_set_species();
|
||||
var ARRAY_BUFFER = "ArrayBuffer";
|
||||
var ArrayBuffer2 = arrayBufferModule[ARRAY_BUFFER];
|
||||
var NativeArrayBuffer = global6[ARRAY_BUFFER];
|
||||
$63({ global: true, forced: NativeArrayBuffer !== ArrayBuffer2 }, {
|
||||
var NativeArrayBuffer = global7[ARRAY_BUFFER];
|
||||
$64({ global: true, forced: NativeArrayBuffer !== ArrayBuffer2 }, {
|
||||
ArrayBuffer: ArrayBuffer2
|
||||
});
|
||||
setSpecies(ARRAY_BUFFER);
|
||||
|
||||
// node_modules/core-js/modules/es.array-buffer.slice.js
|
||||
"use strict";
|
||||
var $64 = require_export();
|
||||
var $65 = require_export();
|
||||
var fails10 = require_fails();
|
||||
var ArrayBufferModule = require_array_buffer();
|
||||
var anObject9 = require_an_object();
|
||||
@@ -11967,7 +12069,7 @@
|
||||
var INCORRECT_SLICE = fails10(function() {
|
||||
return !new ArrayBuffer3(2).slice(1, void 0).byteLength;
|
||||
});
|
||||
$64({ target: "ArrayBuffer", proto: true, unsafe: true, forced: INCORRECT_SLICE }, {
|
||||
$65({ target: "ArrayBuffer", proto: true, unsafe: true, forced: INCORRECT_SLICE }, {
|
||||
slice: function slice2(start, end) {
|
||||
if (nativeArrayBufferSlice !== void 0 && end === void 0) {
|
||||
return nativeArrayBufferSlice.call(anObject9(this), start);
|
||||
@@ -11987,23 +12089,23 @@
|
||||
});
|
||||
|
||||
// node_modules/core-js/modules/es.data-view.js
|
||||
var $65 = require_export();
|
||||
var $66 = require_export();
|
||||
var ArrayBufferModule2 = require_array_buffer();
|
||||
var NATIVE_ARRAY_BUFFER = require_array_buffer_native();
|
||||
$65({ global: true, forced: !NATIVE_ARRAY_BUFFER }, {
|
||||
$66({ global: true, forced: !NATIVE_ARRAY_BUFFER }, {
|
||||
DataView: ArrayBufferModule2.DataView
|
||||
});
|
||||
|
||||
// node_modules/core-js/modules/es.array.reduce.js
|
||||
"use strict";
|
||||
var $66 = require_export();
|
||||
var $67 = require_export();
|
||||
var $reduce = require_array_reduce().left;
|
||||
var arrayMethodIsStrict3 = require_array_method_is_strict();
|
||||
var CHROME_VERSION = require_engine_v8_version();
|
||||
var IS_NODE = require_engine_is_node();
|
||||
var STRICT_METHOD3 = arrayMethodIsStrict3("reduce");
|
||||
var CHROME_BUG = !IS_NODE && CHROME_VERSION > 79 && CHROME_VERSION < 83;
|
||||
$66({ target: "Array", proto: true, forced: !STRICT_METHOD3 || CHROME_BUG }, {
|
||||
$67({ target: "Array", proto: true, forced: !STRICT_METHOD3 || CHROME_BUG }, {
|
||||
reduce: function reduce(callbackfn) {
|
||||
return $reduce(this, callbackfn, arguments.length, arguments.length > 1 ? arguments[1] : void 0);
|
||||
}
|
||||
@@ -12011,34 +12113,6 @@
|
||||
|
||||
// srcts/src/shiny/shinyapp.ts
|
||||
var import_es_regexp_exec9 = __toModule(require_es_regexp_exec());
|
||||
|
||||
// node_modules/core-js/modules/es.array.for-each.js
|
||||
"use strict";
|
||||
var $67 = require_export();
|
||||
var forEach = require_array_for_each();
|
||||
$67({ target: "Array", proto: true, forced: [].forEach != forEach }, {
|
||||
forEach: forEach
|
||||
});
|
||||
|
||||
// node_modules/core-js/modules/web.dom-collections.for-each.js
|
||||
var global7 = require_global();
|
||||
var DOMIterables2 = require_dom_iterables();
|
||||
var forEach2 = require_array_for_each();
|
||||
var createNonEnumerableProperty3 = require_create_non_enumerable_property();
|
||||
for (var COLLECTION_NAME in DOMIterables2) {
|
||||
Collection = global7[COLLECTION_NAME];
|
||||
CollectionPrototype = Collection && Collection.prototype;
|
||||
if (CollectionPrototype && CollectionPrototype.forEach !== forEach2)
|
||||
try {
|
||||
createNonEnumerableProperty3(CollectionPrototype, "forEach", forEach2);
|
||||
} catch (error) {
|
||||
CollectionPrototype.forEach = forEach2;
|
||||
}
|
||||
}
|
||||
var Collection;
|
||||
var CollectionPrototype;
|
||||
|
||||
// srcts/src/shiny/shinyapp.ts
|
||||
var import_jquery41 = __toModule(require_jquery());
|
||||
function _classCallCheck36(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
@@ -13265,7 +13339,7 @@
|
||||
var windowShiny2;
|
||||
function setShiny(windowShiny_) {
|
||||
windowShiny2 = windowShiny_;
|
||||
windowShiny2.version = "1.7.0";
|
||||
windowShiny2.version = "1.7.1.9002";
|
||||
var _initInputBindings = initInputBindings(), inputBindings = _initInputBindings.inputBindings, fileInputBinding2 = _initInputBindings.fileInputBinding;
|
||||
var _initOutputBindings = initOutputBindings(), outputBindings = _initOutputBindings.outputBindings;
|
||||
setFileInputBinding(fileInputBinding2);
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
inst/www/shared/shiny.min.css
vendored
2
inst/www/shared/shiny.min.css
vendored
File diff suppressed because one or more lines are too long
4
inst/www/shared/shiny.min.js
vendored
4
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -14,6 +14,7 @@ bootstrapLib(theme = NULL)
|
||||
build of Bootstrap 3 with a customized version of Bootstrap 3 or higher.
|
||||
\item A character string pointing to an alternative Bootstrap stylesheet
|
||||
(normally a css file within the www directory, e.g. \code{www/bootstrap.css}).
|
||||
This option is here mainly for legacy reasons.
|
||||
}}
|
||||
}
|
||||
\description{
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/bootstrap.R
|
||||
\name{bootstrapPage}
|
||||
% Please edit documentation in R/bootstrap-layout.R, R/bootstrap.R
|
||||
\name{fluidPage}
|
||||
\alias{fluidPage}
|
||||
\alias{fixedPage}
|
||||
\alias{bootstrapPage}
|
||||
\alias{basicPage}
|
||||
\title{Create a Bootstrap page}
|
||||
\title{Create a Bootstrap UI page container}
|
||||
\usage{
|
||||
fluidPage(..., title = NULL, theme = NULL, lang = NULL)
|
||||
|
||||
fixedPage(..., title = NULL, theme = NULL, lang = NULL)
|
||||
|
||||
bootstrapPage(..., title = NULL, theme = NULL, lang = NULL)
|
||||
|
||||
basicPage(...)
|
||||
}
|
||||
\arguments{
|
||||
\item{...}{The contents of the document body.}
|
||||
\item{...}{UI elements (i.e., \link{tags}).}
|
||||
|
||||
\item{title}{The browser window title (defaults to the host URL of the page)}
|
||||
\item{title}{The browser window title (defaults to the host URL of the page).
|
||||
Can also be set as a side effect of the \code{\link[=titlePanel]{titlePanel()}} function.}
|
||||
|
||||
\item{theme}{One of the following:
|
||||
\itemize{
|
||||
@@ -21,30 +28,74 @@ basicPage(...)
|
||||
build of Bootstrap 3 with a customized version of Bootstrap 3 or higher.
|
||||
\item A character string pointing to an alternative Bootstrap stylesheet
|
||||
(normally a css file within the www directory, e.g. \code{www/bootstrap.css}).
|
||||
This option is here mainly for legacy reasons.
|
||||
}}
|
||||
|
||||
\item{lang}{ISO 639-1 language code for the HTML page, such as "en" or "ko".
|
||||
This will be used as the lang in the \code{<html>} tag, as in \code{<html lang="en">}.
|
||||
This will be used as the lang in the \verb{<html>} tag, as in \verb{<html lang="en">}.
|
||||
The default (NULL) results in an empty string.}
|
||||
}
|
||||
\value{
|
||||
A UI defintion that can be passed to the \link{shinyUI} function.
|
||||
A UI definition (i.e., a \link{tags} object) that can be passed to \code{\link[=shinyApp]{shinyApp()}}.
|
||||
}
|
||||
\description{
|
||||
Create a Shiny UI page that loads the CSS and JavaScript for
|
||||
\href{https://getbootstrap.com/}{Bootstrap}, and has no content in the page
|
||||
body (other than what you provide).
|
||||
Create a user interface (UI) page container based on
|
||||
\href{https://getbootstrap.com/}{Bootstrap}'s CSS and JavaScript. Most Shiny apps
|
||||
should use \code{\link[=fluidPage]{fluidPage()}} (or \code{\link[=navbarPage]{navbarPage()}}) to get a page container with a
|
||||
responsive page width, but in some cases you may want a fixed width container
|
||||
(\code{fixedPage()}) or just a bare \verb{<body>} container (\code{bootstrapPage()}).
|
||||
|
||||
Most Shiny apps make use of other Shiny UI functions for \href{https://shiny.rstudio.com/articles/layout-guide.html}{managing layout} (e.g.,
|
||||
\code{\link[=sidebarLayout]{sidebarLayout()}}, \code{\link[=fluidRow]{fluidRow()}}, etc), navigation (e.g., \code{\link[=tabPanel]{tabPanel()}}), and
|
||||
other styling (e.g., \code{\link[=wellPanel]{wellPanel()}}, \code{\link[=inputPanel]{inputPanel()}}). A good portion of these
|
||||
Shiny UI functions require Bootstrap to work properly (so most Shiny apps
|
||||
should use these functions to start their UI definitions), but more advanced
|
||||
usage (i.e., custom HTML/CSS/JS) can avoid Bootstrap entirely by using
|
||||
\code{\link[=htmlTemplate]{htmlTemplate()}} and/or HTML \link{tags}.
|
||||
}
|
||||
\details{
|
||||
This function is primarily intended for users who are proficient in HTML/CSS,
|
||||
and know how to lay out pages in Bootstrap. Most applications should use
|
||||
\code{\link[=fluidPage]{fluidPage()}} along with layout functions like
|
||||
\code{\link[=fluidRow]{fluidRow()}} and \code{\link[=sidebarLayout]{sidebarLayout()}}.
|
||||
\examples{
|
||||
|
||||
# First create some UI content.
|
||||
# See the layout guide to learn more about creating different layouts
|
||||
# https://shiny.rstudio.com/articles/layout-guide.html
|
||||
ui <- sidebarLayout(
|
||||
sidebarPanel(sliderInput("obs", "Number of observations:", 0, 1000, 500)),
|
||||
mainPanel(plotOutput("distPlot"))
|
||||
)
|
||||
server <- function(input, output) {
|
||||
output$distPlot <- renderPlot(hist(rnorm(input$obs)))
|
||||
}
|
||||
\note{
|
||||
The \code{basicPage} function is deprecated, you should use the
|
||||
\code{\link[=fluidPage]{fluidPage()}} function instead.
|
||||
|
||||
# Demonstrating difference between fluidPage(), fixedPage(), bootstrapPage()
|
||||
if (interactive()) {
|
||||
# Container width scales _fluidly_ with window size
|
||||
shinyApp(fluidPage(ui), server)
|
||||
# Container width changes with window size at fixed breakpoints
|
||||
shinyApp(fixedPage(ui), server)
|
||||
# Container width is equal to the window's width
|
||||
shinyApp(bootstrapPage(ui), server)
|
||||
}
|
||||
|
||||
# The default look is provided by Bootstrap 3, but {bslib} can be
|
||||
# used to customize the Bootstrap version and its default styling
|
||||
theme <- bslib::bs_theme(
|
||||
version = 5,
|
||||
bg = "#101010",
|
||||
fg = "#FDF7F7",
|
||||
primary = "#ED79F9",
|
||||
base_font = bslib::font_google("Prompt"),
|
||||
code_font = bslib::font_google("JetBrains Mono")
|
||||
)
|
||||
if (interactive()) {
|
||||
# Call thematic::thematic_shiny(font = "auto") to automatically
|
||||
# translate the theme/CSS to the R plot
|
||||
shinyApp(
|
||||
fluidPage(ui, theme = theme, title = "Hello Bootstrap 5"),
|
||||
server
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
\seealso{
|
||||
\code{\link[=fluidPage]{fluidPage()}}, \code{\link[=fixedPage]{fixedPage()}}
|
||||
\code{\link[=navbarPage]{navbarPage()}}, \code{\link[=fillPage]{fillPage()}}, \code{\link[=column]{column()}}, \code{\link[=tabPanel]{tabPanel()}}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,45 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/bootstrap-layout.R
|
||||
\name{column}
|
||||
\name{fluidRow}
|
||||
\alias{fluidRow}
|
||||
\alias{fixedRow}
|
||||
\alias{column}
|
||||
\title{Create a column within a UI definition}
|
||||
\title{Responsive row-column based layout}
|
||||
\usage{
|
||||
fluidRow(...)
|
||||
|
||||
fixedRow(...)
|
||||
|
||||
column(width, ..., offset = 0)
|
||||
}
|
||||
\arguments{
|
||||
\item{width}{The grid width of the column (must be between 1 and 12)}
|
||||
\item{...}{UI elements (i.e., \link{tags}). For \code{fluidRow()}, \code{...} should be a set of \code{column()}s.}
|
||||
|
||||
\item{...}{Elements to include within the column}
|
||||
\item{width}{The grid width of the column (must be between 1 and 12). When
|
||||
the device width is small (e.g., the viewer is on a mobile phone), the
|
||||
width is always 12. For more control over these responsive breakpoints, use
|
||||
Bootstrap's grid system more directly (e.g., \code{fluidRow(div(class = "col-lg-2", ...))}).}
|
||||
|
||||
\item{offset}{The number of columns to offset this column from the end of the
|
||||
previous column.}
|
||||
}
|
||||
\value{
|
||||
A column that can be included within a
|
||||
\code{\link[=fluidRow]{fluidRow()}} or \code{\link[=fixedRow]{fixedRow()}}.
|
||||
A UI element (i.e., \link{tags}).
|
||||
}
|
||||
\description{
|
||||
Create a column for use within a \code{\link[=fluidRow]{fluidRow()}} or
|
||||
\code{\link[=fixedRow]{fixedRow()}}
|
||||
Layout UI components using Bootstrap's grid layout system. Use
|
||||
\code{fluidRow()} to group elements that should appear on the same line (if the
|
||||
browser has adequate width) and \code{column()} to define how much horizontal
|
||||
space within a 12-unit wide grid each on of these elements should occupy. See
|
||||
the \href{https://shiny.rstudio.com/articles/layout-guide.html}{layout guide} for
|
||||
more context and examples.
|
||||
}
|
||||
\details{
|
||||
To work properly, these functions need \href{https://getbootstrap.com}{Bootstrap}
|
||||
included on the page. Since most Shiny apps use \code{\link[=bootstrapPage]{bootstrapPage()}}
|
||||
under-the-hood, this is usually the case, but custom page containers (i.e.,
|
||||
\code{\link[=htmlTemplate]{htmlTemplate()}}) may need to explicitly include \code{\link[=bootstrapLib]{bootstrapLib()}}
|
||||
dependencies.
|
||||
}
|
||||
\examples{
|
||||
## Only run examples in interactive R sessions
|
||||
@@ -46,21 +65,15 @@ server <- function(input, output) {
|
||||
|
||||
shinyApp(ui, server)
|
||||
|
||||
|
||||
|
||||
ui <- fluidPage(
|
||||
fluidRow(
|
||||
column(width = 4,
|
||||
"4"
|
||||
),
|
||||
column(width = 3, offset = 2,
|
||||
"3 offset 2"
|
||||
)
|
||||
column(width = 4, "4"),
|
||||
column(width = 3, offset = 2, "3 offset 2")
|
||||
)
|
||||
)
|
||||
shinyApp(ui, server = function(input, output) { })
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\code{\link[=fluidRow]{fluidRow()}}, \code{\link[=fixedRow]{fixedRow()}}.
|
||||
\code{\link[=fluidPage]{fluidPage()}}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,11 @@ shown in the document).}
|
||||
build of Bootstrap 3 with a customized version of Bootstrap 3 or higher.
|
||||
\item A character string pointing to an alternative Bootstrap stylesheet
|
||||
(normally a css file within the www directory, e.g. \code{www/bootstrap.css}).
|
||||
This option is here mainly for legacy reasons.
|
||||
}}
|
||||
|
||||
\item{lang}{ISO 639-1 language code for the HTML page, such as "en" or "ko".
|
||||
This will be used as the lang in the \code{<html>} tag, as in \code{<html lang="en">}.
|
||||
This will be used as the lang in the \verb{<html>} tag, as in \verb{<html lang="en">}.
|
||||
The default (NULL) results in an empty string.}
|
||||
}
|
||||
\description{
|
||||
@@ -101,9 +102,7 @@ fillPage(
|
||||
}
|
||||
\seealso{
|
||||
Other layout functions:
|
||||
\code{\link{fixedPage}()},
|
||||
\code{\link{flowLayout}()},
|
||||
\code{\link{fluidPage}()},
|
||||
\code{\link{navbarPage}()},
|
||||
\code{\link{sidebarLayout}()},
|
||||
\code{\link{splitLayout}()},
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/bootstrap-layout.R
|
||||
\name{fixedPage}
|
||||
\alias{fixedPage}
|
||||
\alias{fixedRow}
|
||||
\title{Create a page with a fixed layout}
|
||||
\usage{
|
||||
fixedPage(..., title = NULL, theme = NULL, lang = NULL)
|
||||
|
||||
fixedRow(...)
|
||||
}
|
||||
\arguments{
|
||||
\item{...}{Elements to include within the container}
|
||||
|
||||
\item{title}{The browser window title (defaults to the host URL of the page)}
|
||||
|
||||
\item{theme}{One of the following:
|
||||
\itemize{
|
||||
\item \code{NULL} (the default), which implies a "stock" build of Bootstrap 3.
|
||||
\item A \code{\link[bslib:bs_theme]{bslib::bs_theme()}} object. This can be used to replace a stock
|
||||
build of Bootstrap 3 with a customized version of Bootstrap 3 or higher.
|
||||
\item A character string pointing to an alternative Bootstrap stylesheet
|
||||
(normally a css file within the www directory, e.g. \code{www/bootstrap.css}).
|
||||
}}
|
||||
|
||||
\item{lang}{ISO 639-1 language code for the HTML page, such as "en" or "ko".
|
||||
This will be used as the lang in the \code{<html>} tag, as in \code{<html lang="en">}.
|
||||
The default (NULL) results in an empty string.}
|
||||
}
|
||||
\value{
|
||||
A UI defintion that can be passed to the \link{shinyUI} function.
|
||||
}
|
||||
\description{
|
||||
Functions for creating fixed page layouts. A fixed page layout consists of
|
||||
rows which in turn include columns. Rows exist for the purpose of making sure
|
||||
their elements appear on the same line (if the browser has adequate width).
|
||||
Columns exist for the purpose of defining how much horizontal space within a
|
||||
12-unit wide grid it's elements should occupy. Fixed pages limit their width
|
||||
to 940 pixels on a typical display, and 724px or 1170px on smaller and larger
|
||||
displays respectively.
|
||||
}
|
||||
\details{
|
||||
To create a fixed page use the \code{fixedPage} function and include
|
||||
instances of \code{fixedRow} and \code{\link[=column]{column()}} within it. Note that
|
||||
unlike \code{\link[=fluidPage]{fluidPage()}}, fixed pages cannot make use of higher-level
|
||||
layout functions like \code{sidebarLayout}, rather, all layout must be done
|
||||
with \code{fixedRow} and \code{column}.
|
||||
}
|
||||
\note{
|
||||
See the \href{https://shiny.rstudio.com/articles/layout-guide.html}{ Shiny Application Layout Guide} for additional details on laying out fixed
|
||||
pages.
|
||||
}
|
||||
\examples{
|
||||
## Only run examples in interactive R sessions
|
||||
if (interactive()) {
|
||||
|
||||
ui <- fixedPage(
|
||||
title = "Hello, Shiny!",
|
||||
fixedRow(
|
||||
column(width = 4,
|
||||
"4"
|
||||
),
|
||||
column(width = 3, offset = 2,
|
||||
"3 offset 2"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
shinyApp(ui, server = function(input, output) { })
|
||||
}
|
||||
|
||||
}
|
||||
\seealso{
|
||||
\code{\link[=column]{column()}}
|
||||
|
||||
Other layout functions:
|
||||
\code{\link{fillPage}()},
|
||||
\code{\link{flowLayout}()},
|
||||
\code{\link{fluidPage}()},
|
||||
\code{\link{navbarPage}()},
|
||||
\code{\link{sidebarLayout}()},
|
||||
\code{\link{splitLayout}()},
|
||||
\code{\link{verticalLayout}()}
|
||||
}
|
||||
\concept{layout functions}
|
||||
@@ -34,8 +34,6 @@ shinyApp(ui, server = function(input, output) { })
|
||||
\seealso{
|
||||
Other layout functions:
|
||||
\code{\link{fillPage}()},
|
||||
\code{\link{fixedPage}()},
|
||||
\code{\link{fluidPage}()},
|
||||
\code{\link{navbarPage}()},
|
||||
\code{\link{sidebarLayout}()},
|
||||
\code{\link{splitLayout}()},
|
||||
|
||||
119
man/fluidPage.Rd
119
man/fluidPage.Rd
@@ -1,119 +0,0 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/bootstrap-layout.R
|
||||
\name{fluidPage}
|
||||
\alias{fluidPage}
|
||||
\alias{fluidRow}
|
||||
\title{Create a page with fluid layout}
|
||||
\usage{
|
||||
fluidPage(..., title = NULL, theme = NULL, lang = NULL)
|
||||
|
||||
fluidRow(...)
|
||||
}
|
||||
\arguments{
|
||||
\item{...}{Elements to include within the page}
|
||||
|
||||
\item{title}{The browser window title (defaults to the host URL of the page).
|
||||
Can also be set as a side effect of the \code{\link[=titlePanel]{titlePanel()}} function.}
|
||||
|
||||
\item{theme}{One of the following:
|
||||
\itemize{
|
||||
\item \code{NULL} (the default), which implies a "stock" build of Bootstrap 3.
|
||||
\item A \code{\link[bslib:bs_theme]{bslib::bs_theme()}} object. This can be used to replace a stock
|
||||
build of Bootstrap 3 with a customized version of Bootstrap 3 or higher.
|
||||
\item A character string pointing to an alternative Bootstrap stylesheet
|
||||
(normally a css file within the www directory, e.g. \code{www/bootstrap.css}).
|
||||
}}
|
||||
|
||||
\item{lang}{ISO 639-1 language code for the HTML page, such as "en" or "ko".
|
||||
This will be used as the lang in the \code{<html>} tag, as in \code{<html lang="en">}.
|
||||
The default (NULL) results in an empty string.}
|
||||
}
|
||||
\value{
|
||||
A UI defintion that can be passed to the \link{shinyUI} function.
|
||||
}
|
||||
\description{
|
||||
Functions for creating fluid page layouts. A fluid page layout consists of
|
||||
rows which in turn include columns. Rows exist for the purpose of making sure
|
||||
their elements appear on the same line (if the browser has adequate width).
|
||||
Columns exist for the purpose of defining how much horizontal space within a
|
||||
12-unit wide grid it's elements should occupy. Fluid pages scale their
|
||||
components in realtime to fill all available browser width.
|
||||
}
|
||||
\details{
|
||||
To create a fluid page use the \code{fluidPage} function and include
|
||||
instances of \code{fluidRow} and \code{\link[=column]{column()}} within it. As an
|
||||
alternative to low-level row and column functions you can also use
|
||||
higher-level layout functions like \code{\link[=sidebarLayout]{sidebarLayout()}}.
|
||||
}
|
||||
\note{
|
||||
See the \href{https://shiny.rstudio.com/articles/layout-guide.html}{ Shiny-Application-Layout-Guide} for additional details on laying out fluid
|
||||
pages.
|
||||
}
|
||||
\examples{
|
||||
## Only run examples in interactive R sessions
|
||||
if (interactive()) {
|
||||
|
||||
# Example of UI with fluidPage
|
||||
ui <- fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Hello Shiny!"),
|
||||
|
||||
sidebarLayout(
|
||||
|
||||
# Sidebar with a slider input
|
||||
sidebarPanel(
|
||||
sliderInput("obs",
|
||||
"Number of observations:",
|
||||
min = 0,
|
||||
max = 1000,
|
||||
value = 500)
|
||||
),
|
||||
|
||||
# Show a plot of the generated distribution
|
||||
mainPanel(
|
||||
plotOutput("distPlot")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Server logic
|
||||
server <- function(input, output) {
|
||||
output$distPlot <- renderPlot({
|
||||
hist(rnorm(input$obs))
|
||||
})
|
||||
}
|
||||
|
||||
# Complete app with UI and server components
|
||||
shinyApp(ui, server)
|
||||
|
||||
|
||||
# UI demonstrating column layouts
|
||||
ui <- fluidPage(
|
||||
title = "Hello Shiny!",
|
||||
fluidRow(
|
||||
column(width = 4,
|
||||
"4"
|
||||
),
|
||||
column(width = 3, offset = 2,
|
||||
"3 offset 2"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
shinyApp(ui, server = function(input, output) { })
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\code{\link[=column]{column()}}
|
||||
|
||||
Other layout functions:
|
||||
\code{\link{fillPage}()},
|
||||
\code{\link{fixedPage}()},
|
||||
\code{\link{flowLayout}()},
|
||||
\code{\link{navbarPage}()},
|
||||
\code{\link{sidebarLayout}()},
|
||||
\code{\link{splitLayout}()},
|
||||
\code{\link{verticalLayout}()}
|
||||
}
|
||||
\concept{layout functions}
|
||||
@@ -71,6 +71,7 @@ layout.}
|
||||
build of Bootstrap 3 with a customized version of Bootstrap 3 or higher.
|
||||
\item A character string pointing to an alternative Bootstrap stylesheet
|
||||
(normally a css file within the www directory, e.g. \code{www/bootstrap.css}).
|
||||
This option is here mainly for legacy reasons.
|
||||
}}
|
||||
|
||||
\item{windowTitle}{the browser window title (as a character string). The
|
||||
@@ -79,7 +80,7 @@ default value, \code{NA}, means to use any character strings that appear in
|
||||
default).}
|
||||
|
||||
\item{lang}{ISO 639-1 language code for the HTML page, such as "en" or "ko".
|
||||
This will be used as the lang in the \code{<html>} tag, as in \code{<html lang="en">}.
|
||||
This will be used as the lang in the \verb{<html>} tag, as in \verb{<html lang="en">}.
|
||||
The default (NULL) results in an empty string.}
|
||||
|
||||
\item{menuName}{A name that identifies this \code{navbarMenu}. This
|
||||
@@ -124,9 +125,7 @@ navbarPage("App Title",
|
||||
|
||||
Other layout functions:
|
||||
\code{\link{fillPage}()},
|
||||
\code{\link{fixedPage}()},
|
||||
\code{\link{flowLayout}()},
|
||||
\code{\link{fluidPage}()},
|
||||
\code{\link{sidebarLayout}()},
|
||||
\code{\link{splitLayout}()},
|
||||
\code{\link{verticalLayout}()}
|
||||
|
||||
@@ -62,10 +62,10 @@ respectively.
|
||||
\examples{
|
||||
## Only run this example in interactive R sessions
|
||||
if (interactive()) {
|
||||
runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
runUrl('https://github.com/rstudio/shiny_example/archive/main.tar.gz')
|
||||
|
||||
# Can run an app from a subdirectory in the archive
|
||||
runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
runUrl("https://github.com/rstudio/shiny_example/archive/main.zip",
|
||||
subdir = "inst/shinyapp/")
|
||||
}
|
||||
## Only run this example in interactive R sessions
|
||||
|
||||
@@ -83,9 +83,7 @@ shinyApp(ui, server)
|
||||
\seealso{
|
||||
Other layout functions:
|
||||
\code{\link{fillPage}()},
|
||||
\code{\link{fixedPage}()},
|
||||
\code{\link{flowLayout}()},
|
||||
\code{\link{fluidPage}()},
|
||||
\code{\link{navbarPage}()},
|
||||
\code{\link{splitLayout}()},
|
||||
\code{\link{verticalLayout}()}
|
||||
|
||||
@@ -64,9 +64,7 @@ shinyApp(ui, server)
|
||||
\seealso{
|
||||
Other layout functions:
|
||||
\code{\link{fillPage}()},
|
||||
\code{\link{fixedPage}()},
|
||||
\code{\link{flowLayout}()},
|
||||
\code{\link{fluidPage}()},
|
||||
\code{\link{navbarPage}()},
|
||||
\code{\link{sidebarLayout}()},
|
||||
\code{\link{verticalLayout}()}
|
||||
|
||||
@@ -33,9 +33,7 @@ shinyApp(ui, server = function(input, output) { })
|
||||
\seealso{
|
||||
Other layout functions:
|
||||
\code{\link{fillPage}()},
|
||||
\code{\link{fixedPage}()},
|
||||
\code{\link{flowLayout}()},
|
||||
\code{\link{fluidPage}()},
|
||||
\code{\link{navbarPage}()},
|
||||
\code{\link{sidebarLayout}()},
|
||||
\code{\link{splitLayout}()}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"homepage": "https://shiny.rstudio.com",
|
||||
"repository": "github:rstudio/shiny",
|
||||
"name": "@types/rstudio-shiny",
|
||||
"version": "1.7.0",
|
||||
"version": "1.7.1-alpha.9002",
|
||||
"license": "GPL-3.0-only",
|
||||
"main": "",
|
||||
"browser": "",
|
||||
|
||||
@@ -196,7 +196,7 @@ Both JavaScript files will produce a sourcemap (`**.js.map`) that the browser wi
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
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. 🎉)
|
||||
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. 🎉)
|
||||
|
||||
<!-- #### Auto build and browser refresh
|
||||
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
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;
|
||||
}
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
(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()
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,87 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
/*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();
|
||||
})();
|
||||
@@ -1,8 +0,0 @@
|
||||
// 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 +0,0 @@
|
||||
})();
|
||||
@@ -1,17 +0,0 @@
|
||||
(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();
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
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();
|
||||
@@ -1,41 +0,0 @@
|
||||
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()
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -1,79 +0,0 @@
|
||||
// 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);
|
||||
@@ -1,618 +0,0 @@
|
||||
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);
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
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);
|
||||
@@ -1,65 +0,0 @@
|
||||
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();
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
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');
|
||||
@@ -1,100 +0,0 @@
|
||||
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');
|
||||
@@ -1,249 +0,0 @@
|
||||
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');
|
||||
@@ -1,147 +0,0 @@
|
||||
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');
|
||||
@@ -1,401 +0,0 @@
|
||||
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');
|
||||
@@ -1,42 +0,0 @@
|
||||
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');
|
||||
@@ -1,10 +0,0 @@
|
||||
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');
|
||||
@@ -1,95 +0,0 @@
|
||||
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');
|
||||
@@ -1,206 +0,0 @@
|
||||
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');
|
||||
@@ -1,311 +0,0 @@
|
||||
// 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');
|
||||
}
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
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');
|
||||
@@ -1,59 +0,0 @@
|
||||
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');
|
||||
@@ -1,7 +0,0 @@
|
||||
var textareaInputBinding = {};
|
||||
$.extend(textareaInputBinding, textInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('textarea');
|
||||
}
|
||||
});
|
||||
inputBindings.register(textareaInputBinding, 'shiny.textareaInput');
|
||||
@@ -1,409 +0,0 @@
|
||||
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] : ''
|
||||
};
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,154 +0,0 @@
|
||||
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">×</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">×</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
|
||||
};
|
||||
})();
|
||||
@@ -1,46 +0,0 @@
|
||||
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);
|
||||
@@ -1,29 +0,0 @@
|
||||
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);
|
||||
@@ -1,86 +0,0 @@
|
||||
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');
|
||||
@@ -1,19 +0,0 @@
|
||||
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);
|
||||
});
|
||||
@@ -1,297 +0,0 @@
|
||||
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
@@ -1,11 +0,0 @@
|
||||
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');
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
$(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();
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,362 +0,0 @@
|
||||
function escapeHTML(str) {
|
||||
var escaped = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"\"": """,
|
||||
"'": "'",
|
||||
"/": "/",
|
||||
};
|
||||
|
||||
return str.replace(/[&<>'"\/]/g, function (m) {
|
||||
return escaped[m];
|
||||
});
|
||||
}
|
||||
|
||||
function randomId() {
|
||||
return Math.floor(0x100000000 + Math.random() * 0xf00000000).toString(16);
|
||||
}
|
||||
|
||||
function strToBool(str) {
|
||||
if (!str || !str.toLowerCase) return undefined;
|
||||
|
||||
switch (str.toLowerCase()) {
|
||||
case "true":
|
||||
return true;
|
||||
case "false":
|
||||
return false;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// A wrapper for getComputedStyle that is compatible with older browsers.
|
||||
// This is significantly faster than jQuery's .css() function.
|
||||
function getStyle(el, styleProp) {
|
||||
var x;
|
||||
|
||||
if (el.currentStyle) x = el.currentStyle[styleProp];
|
||||
else if (window.getComputedStyle) {
|
||||
// getComputedStyle can return null when we're inside a hidden iframe on
|
||||
// Firefox; don't attempt to retrieve style props in this case.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=548397
|
||||
var style = document.defaultView.getComputedStyle(el, null);
|
||||
|
||||
if (style) x = style.getPropertyValue(styleProp);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// Convert a number to a string with leading zeros
|
||||
function padZeros(n, digits) {
|
||||
var str = n.toString();
|
||||
|
||||
while (str.length < digits) str = "0" + str;
|
||||
return str;
|
||||
}
|
||||
|
||||
// Round to a specified number of significant digits.
|
||||
function roundSignif(x, digits = 1) {
|
||||
if (digits < 1) throw "Significant digits must be at least 1.";
|
||||
|
||||
// This converts to a string and back to a number, which is inelegant, but
|
||||
// is less prone to FP rounding error than an alternate method which used
|
||||
// Math.round().
|
||||
return parseFloat(x.toPrecision(digits));
|
||||
}
|
||||
|
||||
// Take a string with format "YYYY-MM-DD" and return a Date object.
|
||||
// IE8 and QTWebKit don't support YYYY-MM-DD, but they support YYYY/MM/DD
|
||||
function parseDate(dateString) {
|
||||
var date = new Date(dateString);
|
||||
|
||||
if (isNaN(date)) date = new Date(dateString.replace(/-/g, "/"));
|
||||
return date;
|
||||
}
|
||||
|
||||
// Given a Date object, return a string in yyyy-mm-dd format, using the
|
||||
// UTC date. This may be a day off from the date in the local time zone.
|
||||
function formatDateUTC(date) {
|
||||
if (date instanceof Date) {
|
||||
return (
|
||||
date.getUTCFullYear() +
|
||||
"-" +
|
||||
padZeros(date.getUTCMonth() + 1, 2) +
|
||||
"-" +
|
||||
padZeros(date.getUTCDate(), 2)
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Given an element and a function(width, height), returns a function(). When
|
||||
// the output function is called, it calls the input function with the offset
|
||||
// width and height of the input element--but only if the size of the element
|
||||
// is non-zero and the size is different than the last time the output
|
||||
// function was called.
|
||||
//
|
||||
// Basically we are trying to filter out extraneous calls to func, so that
|
||||
// when the window size changes or whatever, we don't run resize logic for
|
||||
// elements that haven't actually changed size or aren't visible anyway.
|
||||
function makeResizeFilter(el, func) {
|
||||
var lastSize = {};
|
||||
|
||||
return function () {
|
||||
var size = { w: el.offsetWidth, h: el.offsetHeight };
|
||||
|
||||
if (size.w === 0 && size.h === 0) return;
|
||||
if (size.w === lastSize.w && size.h === lastSize.h) return;
|
||||
lastSize = size;
|
||||
func(size.w, size.h);
|
||||
};
|
||||
}
|
||||
|
||||
var _BlobBuilder =
|
||||
window.BlobBuilder ||
|
||||
window.WebKitBlobBuilder ||
|
||||
window.MozBlobBuilder ||
|
||||
window.MSBlobBuilder;
|
||||
|
||||
function makeBlob(parts) {
|
||||
// Browser compatibility is a mess right now. The code as written works in
|
||||
// a variety of modern browsers, but sadly gives a deprecation warning
|
||||
// message on the console in current versions (as of this writing) of
|
||||
// Chrome.
|
||||
|
||||
// Safari 6.0 (8536.25) on Mac OS X 10.8.1:
|
||||
// Has Blob constructor but it doesn't work with ArrayBufferView args
|
||||
|
||||
// Google Chrome 21.0.1180.81 on Xubuntu 12.04:
|
||||
// Has Blob constructor, accepts ArrayBufferView args, accepts ArrayBuffer
|
||||
// but with a deprecation warning message
|
||||
|
||||
// Firefox 15.0 on Xubuntu 12.04:
|
||||
// Has Blob constructor, accepts both ArrayBuffer and ArrayBufferView args
|
||||
|
||||
// Chromium 18.0.1025.168 (Developer Build 134367 Linux) on Xubuntu 12.04:
|
||||
// No Blob constructor. Has WebKitBlobBuilder.
|
||||
|
||||
try {
|
||||
return new Blob(parts);
|
||||
} catch (e) {
|
||||
var blobBuilder = new _BlobBuilder();
|
||||
|
||||
$.each(parts, function (i, part) {
|
||||
blobBuilder.append(part);
|
||||
});
|
||||
return blobBuilder.getBlob();
|
||||
}
|
||||
}
|
||||
|
||||
function pixelRatio() {
|
||||
if (window.devicePixelRatio) {
|
||||
return Math.round(window.devicePixelRatio * 100) / 100;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a string expression and returns a function that takes an argument.
|
||||
//
|
||||
// When the function is executed, it will evaluate that expression using
|
||||
// "with" on the argument value, and return the result.
|
||||
function scopeExprToFunc(expr) {
|
||||
/*jshint evil: true */
|
||||
var expr_escaped = expr
|
||||
.replace(/[\\"']/g, "\\$&")
|
||||
.replace(/\u0000/g, "\\0")
|
||||
.replace(/\n/g, "\\n")
|
||||
.replace(/\r/g, "\\r")
|
||||
// \b has a special meaning; need [\b] to match backspace char.
|
||||
.replace(/[\b]/g, "\\b");
|
||||
|
||||
try {
|
||||
var func = new Function(
|
||||
`with (this) {
|
||||
try {
|
||||
return (${expr});
|
||||
} catch (e) {
|
||||
console.error('Error evaluating expression: ${expr_escaped}');
|
||||
throw e;
|
||||
}
|
||||
}`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Error parsing expression: " + expr);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return function (scope) {
|
||||
return func.call(scope);
|
||||
};
|
||||
}
|
||||
|
||||
function asArray(value) {
|
||||
if (value === null || value === undefined) return [];
|
||||
if ($.isArray(value)) return value;
|
||||
return [value];
|
||||
}
|
||||
|
||||
// We need a stable sorting algorithm for ordering
|
||||
// bindings by priority and insertion order.
|
||||
function mergeSort(list, sortfunc) {
|
||||
function merge(sortfunc, a, b) {
|
||||
var ia = 0;
|
||||
var ib = 0;
|
||||
var sorted = [];
|
||||
|
||||
while (ia < a.length && ib < b.length) {
|
||||
if (sortfunc(a[ia], b[ib]) <= 0) {
|
||||
sorted.push(a[ia++]);
|
||||
} else {
|
||||
sorted.push(b[ib++]);
|
||||
}
|
||||
}
|
||||
while (ia < a.length) sorted.push(a[ia++]);
|
||||
while (ib < b.length) sorted.push(b[ib++]);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
// Don't mutate list argument
|
||||
list = list.slice(0);
|
||||
|
||||
for (var chunkSize = 1; chunkSize < list.length; chunkSize *= 2) {
|
||||
for (var i = 0; i < list.length; i += chunkSize * 2) {
|
||||
var listA = list.slice(i, i + chunkSize);
|
||||
var listB = list.slice(i + chunkSize, i + chunkSize * 2);
|
||||
var merged = merge(sortfunc, listA, listB);
|
||||
var args = [i, merged.length];
|
||||
|
||||
Array.prototype.push.apply(args, merged);
|
||||
Array.prototype.splice.apply(list, args);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// Escape jQuery selector metacharacters: !"#$%&'()*+,./:;<=>?@[\]^`{|}~
|
||||
var $escape = (exports.$escape = function (val) {
|
||||
return val.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
|
||||
});
|
||||
|
||||
// Maps a function over an object, preserving keys. Like the mapValues
|
||||
// function from lodash.
|
||||
function mapValues(obj, f) {
|
||||
const newObj = {};
|
||||
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key)) newObj[key] = f(obj[key], key, obj);
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
// This is does the same as Number.isNaN, but that function unfortunately does
|
||||
// not exist in any version of IE.
|
||||
function isnan(x) {
|
||||
return typeof x === "number" && isNaN(x);
|
||||
}
|
||||
|
||||
// Binary equality function used by the equal function.
|
||||
function _equal(x, y) {
|
||||
if ($.type(x) === "object" && $.type(y) === "object") {
|
||||
if (Object.keys(x).length !== Object.keys(y).length) return false;
|
||||
for (let prop in x)
|
||||
if (!y.hasOwnProperty(prop) || !_equal(x[prop], y[prop])) return false;
|
||||
return true;
|
||||
} else if ($.type(x) === "array" && $.type(y) === "array") {
|
||||
if (x.length !== y.length) return false;
|
||||
for (let i = 0; i < x.length; i++) if (!_equal(x[i], y[i])) return false;
|
||||
return true;
|
||||
} else {
|
||||
return x === y;
|
||||
}
|
||||
}
|
||||
|
||||
// Structural or "deep" equality predicate. Tests two or more arguments for
|
||||
// equality, traversing arrays and objects (as determined by $.type) as
|
||||
// necessary.
|
||||
//
|
||||
// Objects other than objects and arrays are tested for equality using ===.
|
||||
function equal(...args) {
|
||||
if (args.length < 2)
|
||||
throw new Error("equal requires at least two arguments.");
|
||||
for (let i = 0; i < args.length - 1; i++) {
|
||||
if (!_equal(args[i], args[i + 1])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compare version strings like "1.0.1", "1.4-2". `op` must be a string like
|
||||
// "==" or "<".
|
||||
exports.compareVersion = function (a, op, b) {
|
||||
function versionParts(ver) {
|
||||
return (ver + "")
|
||||
.replace(/-/, ".")
|
||||
.replace(/(\.0)+[^\.]*$/, "")
|
||||
.split(".");
|
||||
}
|
||||
|
||||
function cmpVersion(a, b) {
|
||||
a = versionParts(a);
|
||||
b = versionParts(b);
|
||||
var len = Math.min(a.length, b.length);
|
||||
var cmp;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
cmp = parseInt(a[i], 10) - parseInt(b[i], 10);
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
}
|
||||
return a.length - b.length;
|
||||
}
|
||||
|
||||
var diff = cmpVersion(a, b);
|
||||
|
||||
if (op === "==") return diff === 0;
|
||||
else if (op === ">=") return diff >= 0;
|
||||
else if (op === ">") return diff > 0;
|
||||
else if (op === "<=") return diff <= 0;
|
||||
else if (op === "<") return diff < 0;
|
||||
else throw `Unknown operator: ${op}`;
|
||||
};
|
||||
|
||||
function updateLabel(labelTxt, labelNode) {
|
||||
// Only update if label was specified in the update method
|
||||
if (typeof labelTxt === "undefined") return;
|
||||
if (labelNode.length !== 1) {
|
||||
throw new Error("labelNode must be of length 1");
|
||||
}
|
||||
|
||||
// Should the label be empty?
|
||||
var emptyLabel = $.isArray(labelTxt) && labelTxt.length === 0;
|
||||
|
||||
if (emptyLabel) {
|
||||
labelNode.addClass("shiny-label-null");
|
||||
} else {
|
||||
labelNode.text(labelTxt);
|
||||
labelNode.removeClass("shiny-label-null");
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the color property of an a tag, scoped within the element
|
||||
function getComputedLinkColor(el) {
|
||||
let a = document.createElement("a");
|
||||
|
||||
a.href = "/";
|
||||
let div = document.createElement("div");
|
||||
|
||||
div.style.setProperty("position", "absolute", "important");
|
||||
div.style.setProperty("top", "-1000px", "important");
|
||||
div.style.setProperty("left", "0", "important");
|
||||
div.style.setProperty("width", "30px", "important");
|
||||
div.style.setProperty("height", "10px", "important");
|
||||
div.appendChild(a);
|
||||
el.appendChild(div);
|
||||
let linkColor = window.getComputedStyle(a).getPropertyValue("color");
|
||||
|
||||
el.removeChild(div);
|
||||
return linkColor;
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function main(): void {
|
||||
// "_start.js"
|
||||
// √
|
||||
// "utils.js"
|
||||
// √
|
||||
// "browser.js"
|
||||
// √
|
||||
// "input_rate.js"
|
||||
// √; ./time/invoke.ts
|
||||
// √; ./time/debounce.ts
|
||||
// √; ./time/throttle.ts
|
||||
// √; ./inputPolicies/inputBatchSender.ts
|
||||
// √; ./inputPolicies/inputNoResendDecorator.ts
|
||||
// √; ./inputPolicies/inputEventDecorator.ts
|
||||
// √; ./inputPolicies/inputRateDecorator.ts
|
||||
// √; ./inputPolicies/inputDeferDecorator.ts
|
||||
// √; ./inputPolicies/inputValidateDecorator.ts
|
||||
// "shinyapp.js"
|
||||
// √; ./shiny/shinyapp.ts
|
||||
// √; ./shiny/reconnectDialog.ts
|
||||
// "notifications.js"
|
||||
// √; shiny/notifications.ts
|
||||
// "modal.js"
|
||||
// √; shiny/modal.ts
|
||||
// "file_processor.js"
|
||||
// √
|
||||
// "binding_registry.js"
|
||||
// √; ./bindings/registry.ts
|
||||
// √; ./bindings/input/index.ts
|
||||
// const inputBindings = (Shiny.inputBindings = new BindingRegistry());
|
||||
// √; ./bindings/output/index.ts
|
||||
// const outputBindings = (Shiny.outputBindings = new BindingRegistry());
|
||||
// "output_binding.js"
|
||||
// √; ./bindings/output/index.ts
|
||||
// "output_binding_text.js"
|
||||
// √; ./bindings/output/text.ts
|
||||
// "output_binding_image.js"
|
||||
// √; ./bindings/output/image.ts
|
||||
// √; ./imageutils/index.ts
|
||||
// √; ./imageutils/disableDrag.ts
|
||||
// √; ./imageutils/initPanelScales.ts
|
||||
// √; ./imageutils/initCoordmap.ts
|
||||
// √; ./imageutils/findbox.ts
|
||||
// √; ./imageutils/shiftToRange.ts
|
||||
// √; ./imageutils/createClickInfo.ts
|
||||
// √; ./imageutils/createHandlers.ts
|
||||
// √; ./imageutils/createBrush.ts
|
||||
// √; ./imageutils/resetBrush.ts
|
||||
// "output_binding_html.js"
|
||||
// √; ./bindings/output/html.ts
|
||||
// √; ./shiny/render.ts
|
||||
// "output_binding_downloadlink.js"
|
||||
// √; ./bindings/output/downloadlink.ts
|
||||
// "output_binding_datatable.js"
|
||||
// √; ./bindings/output/datatable.ts
|
||||
// "output_binding_adapter.js"
|
||||
// √; ./bindings/output_adapter.ts
|
||||
// "input_binding.js"
|
||||
// √; ./bindings/input/index.ts
|
||||
// "input_binding_text.js"
|
||||
// √; ./bindings/input/text.ts
|
||||
// "input_binding_textarea.js"
|
||||
// √; ./bindings/input/textarea.ts
|
||||
// "input_binding_password.js"
|
||||
// √; ./bindings/input/password.ts
|
||||
// "input_binding_number.js"
|
||||
// √; ./bindings/input/number.ts
|
||||
// "input_binding_checkbox.js"
|
||||
// √; ./bindings/input/checkbox.ts
|
||||
// "input_binding_slider.js"
|
||||
// √; ./bindings/input/slider.ts
|
||||
// "input_binding_date.js"
|
||||
// √; ./bindings/input/date.ts
|
||||
// "input_binding_daterange.js"
|
||||
// √; ./bindings/input/daterange.ts
|
||||
// "input_binding_select.js"
|
||||
// "input_binding_radio.js"
|
||||
// √; ./bindings/input/radio.ts
|
||||
// "input_binding_checkboxgroup.js"
|
||||
// √; ./bindings/input/checkboxgroup.ts
|
||||
// "input_binding_actionbutton.js"
|
||||
// √; ./bindings/input/actionbutton.ts
|
||||
// "input_binding_tabinput.js"
|
||||
// √; ./bindings/input/tabinput.ts
|
||||
// "input_binding_fileinput.js"
|
||||
// √; ./bindings/input/fileinput.ts
|
||||
// √; ./shiny/init.ts
|
||||
// "reactlog.js"
|
||||
// √; ./shiny/reactlog.ts
|
||||
// "_end.js"
|
||||
// √
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -13,7 +13,7 @@ class InputBinding {
|
||||
}
|
||||
|
||||
getId(el: HTMLElement): string {
|
||||
return el["data-input-id"] || el.id;
|
||||
return el.getAttribute("data-input-id") || el.id;
|
||||
}
|
||||
|
||||
// Gives the input a type in case the server needs to know it
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user