mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 16:08:19 -05:00
Compare commits
1 Commits
ui-docs-re
...
rituals_ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c71c369a0 |
12
.github/shiny-workflows/routine.sh
vendored
12
.github/shiny-workflows/routine.sh
vendored
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
. ./tools/documentation/checkDocsCurrent.sh
|
||||
|
||||
echo "Updating package.json version to match DESCRIPTION Version"
|
||||
Rscript ./tools/updatePackageJsonVersion.R
|
||||
if [ -n "$(git status --porcelain package.json)" ]
|
||||
then
|
||||
yarn build
|
||||
git add ./inst package.json && git commit -m 'Sync package version (GitHub Actions)' || echo "No package version to commit"
|
||||
else
|
||||
echo "No package version difference detected; package.json is current."
|
||||
fi
|
||||
159
.github/workflows/R-CMD-check.yaml
vendored
159
.github/workflows/R-CMD-check.yaml
vendored
@@ -1,23 +1,150 @@
|
||||
# Workflow derived from https://github.com/rstudio/shiny-workflows
|
||||
#
|
||||
# NOTE: This Shiny team GHA workflow is overkill for most R packages.
|
||||
# For most R packages it is better to use https://github.com/r-lib/actions
|
||||
name: R-CMD-check
|
||||
on:
|
||||
push:
|
||||
branches: [main, rc-**]
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 5 * * 1' # every monday
|
||||
branches:
|
||||
- master
|
||||
|
||||
name: Package checks
|
||||
|
||||
jobs:
|
||||
website:
|
||||
uses: rstudio/shiny-workflows/.github/workflows/website.yaml@v1
|
||||
routine:
|
||||
uses: rstudio/shiny-workflows/.github/workflows/routine.yaml@v1
|
||||
with:
|
||||
node-version: "14.x"
|
||||
|
||||
R-CMD-check:
|
||||
uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
|
||||
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {os: macOS-latest, r: 'devel'}
|
||||
- {os: macOS-latest, r: '4.0'}
|
||||
- {os: windows-latest, r: '4.0'}
|
||||
- {os: ubuntu-16.04, r: '4.0', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
|
||||
- {os: ubuntu-16.04, r: '3.6', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
|
||||
- {os: ubuntu-16.04, r: '3.5', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
|
||||
- {os: ubuntu-16.04, r: '3.4', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
|
||||
- {os: ubuntu-16.04, r: '3.3', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"}
|
||||
|
||||
env:
|
||||
_R_CHECK_FORCE_SUGGESTS_: false
|
||||
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
|
||||
RSPM: ${{ matrix.config.rspm }}
|
||||
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/checkout/issues/135
|
||||
- name: Set git to use LF
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
git config --system core.autocrlf false
|
||||
git config --system core.eol lf
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: r-lib/actions/setup-r@master
|
||||
id: install-r
|
||||
with:
|
||||
r-version: ${{ matrix.config.r }}
|
||||
|
||||
- uses: r-lib/actions/setup-pandoc@master
|
||||
|
||||
- name: Install pak and query dependencies
|
||||
shell: Rscript {0}
|
||||
run: |
|
||||
install.packages("pak", repos = "https://r-lib.github.io/p/pak/dev/")
|
||||
saveRDS(pak::pkg_deps_tree("local::.", dependencies = TRUE), ".github/r-depends.rds")
|
||||
|
||||
- name: Cache R packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.R_LIBS_USER }}
|
||||
key: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-${{ hashFiles('.github/r-depends.rds') }}
|
||||
restore-keys: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-
|
||||
|
||||
- name: Install system dependencies
|
||||
if: runner.os == 'Linux'
|
||||
shell: Rscript {0}
|
||||
run: |
|
||||
pak::local_system_requirements(execute = TRUE)
|
||||
|
||||
# https://stackoverflow.com/a/66568545/591574
|
||||
#> fatal error: 'X11/Intrinsic.h' file not found
|
||||
- name: Install Cairo macOS R devel dependency
|
||||
if: runner.os == 'macOS' && matrix.config.r == 'devel'
|
||||
run: |
|
||||
brew install libxt
|
||||
|
||||
# xquartz and cairo are needed for Cairo package.
|
||||
# harfbuzz and fribidi are needed for textshaping package.
|
||||
- name: Mac systemdeps
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew install --cask xquartz
|
||||
brew install cairo
|
||||
brew install harfbuzz fribidi
|
||||
|
||||
# Use a shorter temp directory for pak installations, due to filename
|
||||
# length issues on Windows. https://github.com/r-lib/pak/issues/252
|
||||
- name: Windows temp dir
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
New-Item -Path "C:\" -Name "tmp" -ItemType Directory
|
||||
echo "TMPDIR=c:\tmp" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pak::local_install_dev_deps(upgrade = TRUE)
|
||||
pak::pkg_install("rcmdcheck")
|
||||
shell: Rscript {0}
|
||||
|
||||
- name: Find PhantomJS path
|
||||
id: phantomjs
|
||||
run: |
|
||||
echo "::set-output name=path::$(Rscript -e 'cat(shinytest:::phantom_paths()[[1]])')"
|
||||
- name: Cache PhantomJS
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.phantomjs.outputs.path }}
|
||||
key: ${{ matrix.config.os }}-phantomjs
|
||||
restore-keys: ${{ matrix.config.os }}-phantomjs
|
||||
- name: Install PhantomJS
|
||||
run: >
|
||||
Rscript
|
||||
-e "if (!shinytest::dependenciesInstalled()) shinytest::installDependencies()"
|
||||
|
||||
- name: Session info
|
||||
run: |
|
||||
options(width = 100)
|
||||
pkgs <- installed.packages()[, "Package"]
|
||||
sessioninfo::session_info(pkgs, include_base = TRUE)
|
||||
shell: Rscript {0}
|
||||
|
||||
- name: Check
|
||||
env:
|
||||
_R_CHECK_CRAN_INCOMING_: false
|
||||
_R_CHECK_FORCE_SUGGESTS_: ${{ matrix.config.r != 'devel' }}
|
||||
run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check")
|
||||
shell: Rscript {0}
|
||||
|
||||
- name: Show testthat output
|
||||
if: always()
|
||||
run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true
|
||||
shell: bash
|
||||
|
||||
- name: Upload check results
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.config.os }}-r${{ matrix.config.r }}-results
|
||||
path: check
|
||||
|
||||
- name: Fix path for Windows caching
|
||||
if: runner.os == 'Windows'
|
||||
# This is needed because if you use the default tar at this stage,
|
||||
# C:/Rtools/bin/tar.exe, it will say that it can't find gzip.exe. So
|
||||
# we'll just set the path so that the original tar that would be
|
||||
# found, will be found.
|
||||
run: echo "C:/Program Files/Git/usr/bin" >> $GITHUB_PATH
|
||||
|
||||
172
.github/workflows/rituals.yaml
vendored
Normal file
172
.github/workflows/rituals.yaml
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- ghactions
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
name: Rituals
|
||||
|
||||
jobs:
|
||||
rituals:
|
||||
name: Rituals
|
||||
# if: false
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- { os: ubuntu-16.04, r: '4.0', node: "14.x", rspm: "https://packagemanager.rstudio.com/all/__linux__/xenial/latest"}
|
||||
|
||||
env:
|
||||
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
|
||||
RSPM: ${{ matrix.config.rspm }}
|
||||
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- uses: r-lib/actions/pr-fetch@master
|
||||
name: Git Pull (PR)
|
||||
if: github.event_name == 'pull_request'
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: r-lib/actions/setup-r@master
|
||||
id: install-r
|
||||
with:
|
||||
r-version: ${{ matrix.config.r }}
|
||||
|
||||
- uses: r-lib/actions/setup-pandoc@master
|
||||
|
||||
- name: Git Config
|
||||
run: |
|
||||
git config user.name "${GITHUB_ACTOR}"
|
||||
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
|
||||
|
||||
- name: Install pak and query dependencies
|
||||
shell: Rscript {0}
|
||||
run: |
|
||||
install.packages("pak", repos = "https://r-lib.github.io/p/pak/dev/")
|
||||
saveRDS(pak::pkg_deps_tree("local::.", dependencies = TRUE), ".github/r-depends.rds")
|
||||
|
||||
- name: Cache R packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.R_LIBS_USER }}
|
||||
key: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-${{ hashFiles('.github/r-depends.rds') }}
|
||||
restore-keys: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-
|
||||
|
||||
- name: Install system dependencies
|
||||
# if: runner.os == 'Linux'
|
||||
shell: Rscript {0}
|
||||
run: |
|
||||
pak::local_system_requirements(execute = TRUE)
|
||||
|
||||
- name: Install dependencies
|
||||
shell: Rscript {0}
|
||||
run: |
|
||||
pak::local_install_dev_deps(upgrade = TRUE)
|
||||
|
||||
- name: Session info
|
||||
shell: Rscript {0}
|
||||
run: |
|
||||
options(width = 100)
|
||||
pak::pkg_install("sessioninfo")
|
||||
pkgs <- installed.packages()[, "Package"]
|
||||
sessioninfo::session_info(pkgs, include_base = TRUE)
|
||||
|
||||
- name: Url redirects
|
||||
# only perform if in an RC branch (`rc-vX.Y.Z`)
|
||||
if: ${{ github.event_name == 'push' && contains(github.ref, '/rc-v') }}
|
||||
run: |
|
||||
Rscript -e 'pak::pkg_install("r-lib/urlchecker"); urlchecker::url_update()'
|
||||
# throw an error if man files were updated
|
||||
if [ -n "$(git status --porcelain man)" ]
|
||||
then
|
||||
git status --porcelain
|
||||
>&2 echo "Updated links found in files above"
|
||||
>&2 echo 'Run `urlchecker::url_update()` to fix links locally'
|
||||
exit 1
|
||||
fi
|
||||
# Add locally changed urls
|
||||
git add .
|
||||
git commit -m 'Update links (GitHub Actions)' || echo "No link changes to commit"
|
||||
|
||||
- name: Document
|
||||
run: |
|
||||
Rscript -e 'pak::pkg_install("devtools")'
|
||||
Rscript -e 'devtools::document()'
|
||||
git add man/\* NAMESPACE
|
||||
git commit -m 'Document (GitHub Actions)' || echo "No documentation changes to commit"
|
||||
|
||||
- name: Check documentation
|
||||
run: |
|
||||
./tools/documentation/checkDocsCurrent.sh
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.config.node }}
|
||||
# https://github.com/actions/cache/blame/ccf96194800dbb7b7094edcd5a7cf3ec3c270f10/examples.md#L185-L200
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: yarn cache
|
||||
uses: actions/cache@v2
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ matrix.config.os }}-${{ matrix.config.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ matrix.config.os }}-${{ matrix.config.node }}-yarn-
|
||||
|
||||
- name: Sync DESCRIPTION and package.json versions
|
||||
run: |
|
||||
Rscript -e 'pak::pkg_install("jsonlite")'
|
||||
Rscript -e 'pkg <- jsonlite::read_json("package.json", simplifyVector = TRUE)' \
|
||||
-e 'version <- as.list(read.dcf("DESCRIPTION")[1,])$Version' \
|
||||
-e 'pkg$version <- gsub("^(\\d+).(\\d+).(\\d+).(.+)$", "\\1.\\2.\\3-alpha.\\4", version)' \
|
||||
-e 'pkg$files <- as.list(pkg$files)' \
|
||||
-e 'jsonlite::write_json(pkg, path = "package.json", pretty = TRUE, auto_unbox = TRUE)'
|
||||
git add package.json && git commit -m 'sync package version (GitHub Actions)' || echo "No version changes to commit"
|
||||
- name: Build JS
|
||||
run: |
|
||||
tree srcts
|
||||
rm -r srcts/types
|
||||
yarn install --immutable && yarn build
|
||||
git add ./srcts/src && git commit -m 'yarn lint (GitHub Actions)' || echo "No yarn lint changes to commit"
|
||||
git add ./srcts/types && git commit -m 'yarn tsc (GitHub Actions)' || echo "No type definition changes to commit"
|
||||
git add ./inst && git commit -m 'yarn build (GitHub Actions)' || echo "No yarn build changes to commit"
|
||||
if [ -n "$(git status --porcelain)" ]
|
||||
then
|
||||
git status --porcelain
|
||||
>&2 echo "The above files changed when we built the JavaScript assets."
|
||||
exit 1
|
||||
else
|
||||
echo "No difference detected; TypeScript build is current."
|
||||
fi
|
||||
|
||||
- name: Git Push (PR)
|
||||
uses: r-lib/actions/pr-push@master
|
||||
if: github.event_name == 'pull_request'
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Verify no un-pushed commits (MASTER)
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
# Can't push to a protected branch
|
||||
if [ -n "`git cherry origin/master`"]; then
|
||||
echo "Un-pushed commits:"
|
||||
git cherry -v origin/master
|
||||
echo "\nCan not push to a protected branch. Exiting"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Execute after pushing, as no updated files will be produced
|
||||
- name: Test TypeScript code
|
||||
run: |
|
||||
yarn test
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,7 +12,7 @@ README.html
|
||||
tools/yarn-error.log
|
||||
|
||||
# TypeScript / yarn
|
||||
/node_modules/
|
||||
node_modules/
|
||||
.cache
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
|
||||
3
.prettierrc.yml
Normal file
3
.prettierrc.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
trailingComma: "es5"
|
||||
arrowParens: always
|
||||
endOfLine: lf
|
||||
16
.vscode/settings.json
vendored
16
.vscode/settings.json
vendored
@@ -1,18 +1,10 @@
|
||||
{
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"prettier.prettierPath": "./node_modules/prettier",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"[r]": {
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
},
|
||||
"eslint.nodePath": ".yarn/sdks",
|
||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.js",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
|
||||
13
DESCRIPTION
13
DESCRIPTION
@@ -1,7 +1,7 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 1.7.1.9002
|
||||
Version: 1.6.0.9022
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com", comment = c(ORCID = "0000-0002-1576-2126")),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
|
||||
@@ -79,7 +79,7 @@ Imports:
|
||||
jsonlite (>= 0.9.16),
|
||||
xtable,
|
||||
fontawesome (>= 0.2.1),
|
||||
htmltools (>= 0.5.2),
|
||||
htmltools (>= 0.5.1.9003),
|
||||
R6 (>= 2.0),
|
||||
sourcetools,
|
||||
later (>= 1.0.0),
|
||||
@@ -91,7 +91,7 @@ Imports:
|
||||
withr,
|
||||
commonmark (>= 1.7),
|
||||
glue (>= 1.3.2),
|
||||
bslib (>= 0.3.0),
|
||||
bslib (>= 0.2.5.9002),
|
||||
cachem,
|
||||
ellipsis,
|
||||
lifecycle (>= 0.2.0)
|
||||
@@ -112,6 +112,10 @@ Suggests:
|
||||
ragg,
|
||||
showtext,
|
||||
sass
|
||||
Remotes:
|
||||
r-lib/rlang,
|
||||
rstudio/bslib,
|
||||
rstudio/htmltools
|
||||
URL: https://shiny.rstudio.com/
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Collate:
|
||||
@@ -189,7 +193,6 @@ Collate:
|
||||
'shinywrappers.R'
|
||||
'showcase.R'
|
||||
'snapshot.R'
|
||||
'staticimports.R'
|
||||
'tar.R'
|
||||
'test-export.R'
|
||||
'test-server.R'
|
||||
@@ -202,7 +205,7 @@ Collate:
|
||||
'version_selectize.R'
|
||||
'version_strftime.R'
|
||||
'viewer.R'
|
||||
RoxygenNote: 7.1.2
|
||||
RoxygenNote: 7.1.1
|
||||
Encoding: UTF-8
|
||||
Roxygen: list(markdown = TRUE)
|
||||
RdMacros: lifecycle
|
||||
|
||||
43
NEWS.md
43
NEWS.md
@@ -1,37 +1,8 @@
|
||||
shiny 1.7.1.9001
|
||||
shiny 1.6.0.9000
|
||||
================
|
||||
|
||||
## 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
|
||||
===========
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* Closed #3516: Fix regression in repeated calls to `appendTab()` when `navbarMenu()` is already present within a `tabsetPanel()`/`navbarPage()`. (#3518)
|
||||
|
||||
* Re-arranged conditions for testthat 1.0.0 compatibility. (#3512)
|
||||
|
||||
|
||||
shiny 1.7.0
|
||||
===========
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The `format` and `locale` arguments to `sliderInput()` have been removed. They have been deprecated since 0.10.2.2 (released on 2014-12-08).
|
||||
@@ -47,7 +18,7 @@ shiny 1.7.0
|
||||
|
||||
* All uses of `list(...)` have been replaced with `rlang::list2(...)`. This means that you can use trailing `,` without error and use rlang's `!!!` operator to "splice" a list of argument values into `...`. We think this'll be particularly useful for passing a list of `tabPanel()` to their consumers (i.e., `tabsetPanel()`, `navbarPage()`, etc). For example, `tabs <- list(tabPanel("A", "a"), tabPanel("B", "b")); navbarPage(!!!tabs)`. (#3315 and #3328)
|
||||
|
||||
* `installExprFunction()` and `exprToFunction()` are now able to handle quosures when `quoted = TRUE`. So `render`-functions which call these functions (such as with `htmlwidgets`) can now understand quosures. Users can also use `rlang::inject()` to unquote a quosure for evaluation. This also means that `render` function no longer need `env` and `quoted` parameters; that information can be embedded into a quosure which is then passed to the `render` function. Better documentation was added for how to create `render` functions. (#3472)
|
||||
* `installExprFunction()` and `exprToFunction()` are now able to handle quosures, so `render`-functions which call these functions can now understand quosures, when they are called using `rlang::inject()`. This also means that `render` function no longer need `env` and `quoted` parameters; that information can be embedded into a quosure which is then passed to the `render` function. Additionally, the `getQuosure()` function was added, which makes it easier for developers to create `render` functions which understand quosures. Better documentation was added for how to create `render` functions. (#3462, #3466)
|
||||
|
||||
* `icon(lib="fontawesome")` is now powered by the `{fontawesome}` package, which will make it easier to use the latest FA icons in the future (by updating the `{fontawesome}` package). (#3302)
|
||||
|
||||
@@ -59,7 +30,7 @@ shiny 1.7.0
|
||||
|
||||
### Other improvements
|
||||
|
||||
* Shiny's core JavaScript code was converted to TypeScript. For the latest development information, please see the [README.md in `./srcts`](https://github.com/rstudio/shiny/tree/v1.7.0/srcts). (#3296)
|
||||
* Shiny's core JavaScript code was converted to TypeScript. For the latest development information, please see the [README.md in `./srcts`](https://github.com/rstudio/shiny/tree/master/srcts). (#3296)
|
||||
|
||||
* Switched from `digest::digest()` to `rlang::hash()` for hashing. (#3264)
|
||||
|
||||
@@ -73,12 +44,10 @@ shiny 1.7.0
|
||||
|
||||
* Fixed [#2666](https://github.com/rstudio/shiny/issues/2666) and [#2670](https://github.com/rstudio/shiny/issues/2670): `nearPoints()` and `brushedPoints()` weren't properly account for missing values (#2666 was introduced in v1.4.0). ([#2668](https://github.com/rstudio/shiny/pull/2668))
|
||||
|
||||
* Closed #3374: `quoToFunction()` now works correctly with nested quosures; and as a result, quasi-quotation with rendering function (e.g., `renderPrint()`, `renderPlot()`, etc) now works as expected with nested quosures. (#3373)
|
||||
* Closed #3374: `quoToFunction()` now works correctly with nested quosures; and as a result, quasi-quotation with rendering function (e.g., `renderPrint()`, `renderPlot()`, etc) now works as expected with nested quosures. (#3373)
|
||||
|
||||
* Exported `register_devmode_option()`. This method was described in the documentation for `devmode()` but was never exported. See `?devmode()` for more details on how to register Shiny Developer options using `register_devmode_option()`. (#3364)
|
||||
|
||||
* Closed #3484: In the RStudio IDE on Mac 11.5, selected checkboxes and radio buttons were not visible. (#3485)
|
||||
|
||||
### Library updates
|
||||
|
||||
* Closed #3286: Updated to Font-Awesome 5.15.2. (#3288)
|
||||
@@ -92,7 +61,7 @@ This release focuses on improvements in three main areas:
|
||||
|
||||
1. Better theming (and Bootstrap 4) support:
|
||||
* The `theme` argument of `fluidPage()`, `navbarPage()`, and `bootstrapPage()` all now understand `bslib::bs_theme()` objects, which can be used to opt-into Bootstrap 4, use any Bootswatch theme, and/or implement custom themes without writing any CSS.
|
||||
* The `session` object now includes `$setCurrentTheme()` and `$getCurrentTheme()` methods to dynamically update (or obtain) the page's `theme` after initial load, which is useful for things such as [adding a dark mode switch to an app](https://rstudio.github.io/bslib/articles/bslib.html#dynamic) or some other "real-time" theming tool like `bslib::bs_themer()`.
|
||||
* The `session` object now includes `$setCurrentTheme()` and `$getCurrentTheme()` methods to dynamically update (or obtain) the page's `theme` after initial load, which is useful for things such as [adding a dark mode switch to an app](https://rstudio.github.io/bslib/articles/theming.html#dynamic-shiny) or some other "real-time" theming tool like `bslib::bs_themer()`.
|
||||
* For more details, see [`{bslib}`'s website](https://rstudio.github.io/bslib/)
|
||||
|
||||
2. Caching of `reactive()` and `render*()` (e.g. `renderText()`, `renderTable()`, etc) expressions.
|
||||
@@ -214,7 +183,7 @@ shiny 1.5.0
|
||||
|
||||
* The new `moduleServer` function provides a simpler interface for creating and using modules. (#2773)
|
||||
|
||||
* Resolved #2732: `markdown()` is a new function for writing Markdown with Github extensions directly in Shiny UIs. Markdown rendering is performed by the [commonmark](https://github.com/r-lib/commonmark) package. (#2737)
|
||||
* Resolved #2732: `markdown()` is a new function for writing Markdown with Github extensions directly in Shiny UIs. Markdown rendering is performed by the [commonmark](https://github.com/jeroen/commonmark) package. (#2737)
|
||||
|
||||
* The `getCurrentOutputInfo()` function can now return the background color (`bg`), foreground color (`fg`), `accent` (i.e., hyperlink) color, and `font` information of the output's HTML container. This information is reported by `plotOutput()`, `imageOutput()`, and any other output bindings containing a class of `.shiny-report-theme`. This feature allows developers to style an output's contents based on the container's CSS styling. (#2740)
|
||||
|
||||
|
||||
@@ -125,14 +125,14 @@ shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
|
||||
}
|
||||
|
||||
if ("shinytest" %in% examples) {
|
||||
if (!is_installed("shinytest", "1.4.0"))
|
||||
if (!is_available("shinytest", "1.4.0"))
|
||||
{
|
||||
message(
|
||||
"The tests/shinytest directory needs shinytest 1.4.0 or later to work properly."
|
||||
)
|
||||
if (is_installed("shinytest")) {
|
||||
if (is_available("shinytest")) {
|
||||
message("You currently have shinytest ",
|
||||
get_package_version("shinytest"), " installed.")
|
||||
utils::packageVersion("shinytest"), " installed.")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -152,7 +152,7 @@ shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
|
||||
|
||||
# Helper to resolve paths relative to our template
|
||||
template_path <- function(...) {
|
||||
system_file("app_template", ..., package = "shiny")
|
||||
system.file("app_template", ..., package = "shiny")
|
||||
}
|
||||
|
||||
# Resolve path relative to destination
|
||||
|
||||
@@ -255,7 +255,7 @@ utils::globalVariables(".GenericCallEnv", add = TRUE)
|
||||
#' the cache.
|
||||
#'
|
||||
#' You may need to provide a `cacheHint` to [createRenderFunction()] (or
|
||||
#' `htmlwidgets::shinyRenderWidget()`, if you've authored an htmlwidget) in
|
||||
#' [htmlwidgets::shinyRenderWidget()], if you've authored an htmlwidget) in
|
||||
#' order for `bindCache()` to correctly compute a cache key.
|
||||
#'
|
||||
#' The potential problem is a cache collision. Consider the following:
|
||||
|
||||
@@ -1,4 +1,87 @@
|
||||
#' @rdname bootstrapPage
|
||||
|
||||
#' 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
|
||||
#' @export
|
||||
fluidPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
|
||||
bootstrapPage(div(class = "container-fluid", ...),
|
||||
@@ -8,13 +91,62 @@ fluidPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
|
||||
}
|
||||
|
||||
|
||||
#' @rdname column
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidRow <- function(...) {
|
||||
div(class = "row", ...)
|
||||
}
|
||||
|
||||
#' @rdname bootstrapPage
|
||||
#' 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
|
||||
#' @export
|
||||
fixedPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
|
||||
bootstrapPage(div(class = "container", ...),
|
||||
@@ -23,39 +155,28 @@ fixedPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
|
||||
lang = lang)
|
||||
}
|
||||
|
||||
#' @rdname column
|
||||
#' @rdname fixedPage
|
||||
#' @export
|
||||
fixedRow <- function(...) {
|
||||
div(class = "row", ...)
|
||||
}
|
||||
|
||||
#' Responsive row-column based layout
|
||||
|
||||
#' Create a column within a UI definition
|
||||
#'
|
||||
#' 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.
|
||||
#' Create a column for use within a [fluidRow()] or
|
||||
#' [fixedRow()]
|
||||
#'
|
||||
#' 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 width The grid width of the column (must be between 1 and 12)
|
||||
#' @param ... Elements to include within the column
|
||||
#' @param offset The number of columns to offset this column from the end of the
|
||||
#' previous column.
|
||||
#'
|
||||
#' @return A UI element (i.e., [tags]).
|
||||
#' @return A column that can be included within a
|
||||
#' [fluidRow()] or [fixedRow()].
|
||||
#'
|
||||
#' @seealso [fluidPage()]
|
||||
#'
|
||||
#' @seealso [fluidRow()], [fixedRow()].
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -81,10 +202,16 @@ 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,84 +1,36 @@
|
||||
#' @include utils.R
|
||||
NULL
|
||||
|
||||
#' Create a Bootstrap UI page container
|
||||
#' Create a Bootstrap page
|
||||
#'
|
||||
#' @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()`).
|
||||
#' 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).
|
||||
#'
|
||||
#' 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].
|
||||
#' 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()].
|
||||
#'
|
||||
#' @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 ... The contents of the document body.
|
||||
#' @param title The browser window title (defaults to the host URL of the page)
|
||||
#' @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 `<html>` tag, as in `<html lang="en">`.
|
||||
#' 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.
|
||||
#'
|
||||
#' @return A UI definition (i.e., a [tags] object) that can be passed to [shinyApp()].
|
||||
#' @return A UI defintion that can be passed to the [shinyUI] function.
|
||||
#'
|
||||
#' @seealso [navbarPage()], [fillPage()], [column()], [tabPanel()]
|
||||
#' @note The `basicPage` function is deprecated, you should use the
|
||||
#' [fluidPage()] function instead.
|
||||
#'
|
||||
#' @seealso [fluidPage()], [fixedPage()]
|
||||
#' @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(
|
||||
@@ -186,7 +138,8 @@ bs_theme_deps <- function(theme) {
|
||||
}
|
||||
|
||||
is_bs_theme <- function(x) {
|
||||
bslib::is_bs_theme(x)
|
||||
is_available("bslib", "0.2.0.9000") &&
|
||||
bslib::is_bs_theme(x)
|
||||
}
|
||||
|
||||
#' Obtain Shiny's Bootstrap Sass theme
|
||||
@@ -262,10 +215,11 @@ registerThemeDependency <- function(func) {
|
||||
|
||||
bootstrapDependency <- function(theme) {
|
||||
htmlDependency(
|
||||
"bootstrap",
|
||||
bootstrapVersion,
|
||||
src = "www/shared/bootstrap",
|
||||
package = "shiny",
|
||||
"bootstrap", bootstrapVersion,
|
||||
c(
|
||||
href = "shared/bootstrap",
|
||||
file = system.file("www/shared/bootstrap", package = "shiny")
|
||||
),
|
||||
script = c(
|
||||
"js/bootstrap.min.js",
|
||||
# Safely adding accessibility plugin for screen readers and keyboard users; no break for sighted aspects (see https://github.com/paypal/bootstrap-accessibility-plugin)
|
||||
@@ -282,12 +236,14 @@ 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
|
||||
@@ -1153,17 +1109,11 @@ tableOutput <- function(outputId) {
|
||||
|
||||
dataTableDependency <- list(
|
||||
htmlDependency(
|
||||
"datatables",
|
||||
"1.10.5",
|
||||
src = "www/shared/datatables",
|
||||
package = "shiny",
|
||||
"datatables", "1.10.5", c(href = "shared/datatables"),
|
||||
script = "js/jquery.dataTables.min.js"
|
||||
),
|
||||
htmlDependency(
|
||||
"datatables-bootstrap",
|
||||
"1.10.5",
|
||||
src = "www/shared/datatables",
|
||||
package = "shiny",
|
||||
"datatables-bootstrap", "1.10.5", c(href = "shared/datatables"),
|
||||
stylesheet = c("css/dataTables.bootstrap.css", "css/dataTables.extra.css"),
|
||||
script = "js/dataTables.bootstrap.js"
|
||||
)
|
||||
@@ -1203,7 +1153,7 @@ dataTableOutput <- function(outputId) {
|
||||
htmlOutput <- function(outputId, inline = FALSE,
|
||||
container = if (inline) span else div, ...)
|
||||
{
|
||||
if (any_unnamed(list(...))) {
|
||||
if (anyUnnamed(list(...))) {
|
||||
warning("Unnamed elements in ... will be replaced with dynamic UI.")
|
||||
}
|
||||
container(id = outputId, class="shiny-html-output", ...)
|
||||
|
||||
@@ -228,9 +228,7 @@ withLogErrors <- function(expr,
|
||||
if (promises::is.promise(result)) {
|
||||
result <- promises::catch(result, function(cond) {
|
||||
# Don't print shiny.silent.error (i.e. validation errors)
|
||||
if (cnd_inherits(cond, "shiny.silent.error")) {
|
||||
return()
|
||||
}
|
||||
if (inherits(cond, "shiny.silent.error")) return()
|
||||
if (isTRUE(getOption("show.error.messages"))) {
|
||||
printError(cond, full = full, offset = offset)
|
||||
}
|
||||
@@ -241,7 +239,7 @@ withLogErrors <- function(expr,
|
||||
},
|
||||
error = function(cond) {
|
||||
# Don't print shiny.silent.error (i.e. validation errors)
|
||||
if (cnd_inherits(cond, "shiny.silent.error")) return()
|
||||
if (inherits(cond, "shiny.silent.error")) return()
|
||||
if (isTRUE(getOption("show.error.messages"))) {
|
||||
printError(cond, full = full, offset = offset)
|
||||
}
|
||||
|
||||
55
R/globals.R
55
R/globals.R
@@ -1,6 +1,55 @@
|
||||
# A scope where we can put mutable global state
|
||||
.globals <- new.env(parent = emptyenv())
|
||||
|
||||
register_s3_method <- function(pkg, generic, class, fun = NULL) {
|
||||
stopifnot(is.character(pkg), length(pkg) == 1)
|
||||
stopifnot(is.character(generic), length(generic) == 1)
|
||||
stopifnot(is.character(class), length(class) == 1)
|
||||
|
||||
if (is.null(fun)) {
|
||||
fun <- get(paste0(generic, ".", class), envir = parent.frame())
|
||||
} else {
|
||||
stopifnot(is.function(fun))
|
||||
}
|
||||
|
||||
if (pkg %in% loadedNamespaces()) {
|
||||
registerS3method(generic, class, fun, envir = asNamespace(pkg))
|
||||
}
|
||||
|
||||
# Always register hook in case pkg is loaded at some
|
||||
# point the future (or, potentially, but less commonly,
|
||||
# unloaded & reloaded)
|
||||
setHook(
|
||||
packageEvent(pkg, "onLoad"),
|
||||
function(...) {
|
||||
registerS3method(generic, class, fun, envir = asNamespace(pkg))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
register_upgrade_message <- function(pkg, version) {
|
||||
|
||||
msg <- sprintf(
|
||||
"This version of Shiny is designed to work with '%s' >= %s.
|
||||
Please upgrade via install.packages('%s').",
|
||||
pkg, version, pkg
|
||||
)
|
||||
|
||||
if (pkg %in% loadedNamespaces() && !is_available(pkg, version)) {
|
||||
packageStartupMessage(msg)
|
||||
}
|
||||
|
||||
# Always register hook in case pkg is loaded at some
|
||||
# point the future (or, potentially, but less commonly,
|
||||
# unloaded & reloaded)
|
||||
setHook(
|
||||
packageEvent(pkg, "onLoad"),
|
||||
function(...) {
|
||||
if (!is_available(pkg, version)) packageStartupMessage(msg)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
.onLoad <- function(libname, pkgname) {
|
||||
# R's lazy-loading package scheme causes the private seed to be cached in the
|
||||
# package itself, making our PRNG completely deterministic. This line resets
|
||||
@@ -13,9 +62,9 @@
|
||||
|
||||
# Make sure these methods are available to knitr if shiny is loaded but not
|
||||
# attached.
|
||||
s3_register("knitr::knit_print", "reactive")
|
||||
s3_register("knitr::knit_print", "shiny.appobj")
|
||||
s3_register("knitr::knit_print", "shiny.render.function")
|
||||
register_s3_method("knitr", "knit_print", "reactive")
|
||||
register_s3_method("knitr", "knit_print", "shiny.appobj")
|
||||
register_s3_method("knitr", "knit_print", "shiny.render.function")
|
||||
|
||||
# Shiny 1.4.0 bumps jQuery 1.x to 3.x, which caused a problem
|
||||
# with static-rendering of htmlwidgets, and htmlwidgets 1.5
|
||||
|
||||
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_installed(package, version)) {
|
||||
if (is_available(package, version)) {
|
||||
return()
|
||||
}
|
||||
|
||||
@@ -115,28 +115,22 @@ check_reactlog <- function() {
|
||||
}
|
||||
# read reactlog version from description file
|
||||
# prevents version mismatch in code and description file
|
||||
reactlog_version <- local({
|
||||
version <- NULL
|
||||
function() {
|
||||
if (!is.null(version)) return(version)
|
||||
reactlog_version <- function() {
|
||||
desc <- read.dcf(system.file("DESCRIPTION", package = "shiny", mustWork = TRUE))
|
||||
suggests <- desc[1,"Suggests"][[1]]
|
||||
suggests_pkgs <- strsplit(suggests, "\n")[[1]]
|
||||
|
||||
desc <- read.dcf(system_file("DESCRIPTION", package = "shiny"))
|
||||
suggests <- desc[1,"Suggests"][[1]]
|
||||
suggests_pkgs <- strsplit(suggests, "\n")[[1]]
|
||||
|
||||
reactlog_info <- suggests_pkgs[grepl("reactlog", suggests_pkgs)]
|
||||
if (length(reactlog_info) == 0) {
|
||||
stop("reactlog can not be found in shiny DESCRIPTION file")
|
||||
}
|
||||
|
||||
reactlog_info <- sub("^[^\\(]*\\(", "", reactlog_info)
|
||||
reactlog_info <- sub("\\)[^\\)]*$", "", reactlog_info)
|
||||
reactlog_info <- sub("^[>= ]*", "", reactlog_info)
|
||||
|
||||
version <<- package_version(reactlog_info)
|
||||
version
|
||||
reactlog_info <- suggests_pkgs[grepl("reactlog", suggests_pkgs)]
|
||||
if (length(reactlog_info) == 0) {
|
||||
stop("reactlog can not be found in shiny DESCRIPTION file")
|
||||
}
|
||||
})
|
||||
|
||||
reactlog_info <- sub("^[^\\(]*\\(", "", reactlog_info)
|
||||
reactlog_info <- sub("\\)[^\\)]*$", "", reactlog_info)
|
||||
reactlog_info <- sub("^[>= ]*", "", reactlog_info)
|
||||
|
||||
package_version(reactlog_info)
|
||||
}
|
||||
|
||||
|
||||
RLog <- R6Class(
|
||||
|
||||
@@ -4,12 +4,12 @@ startPNG <- function(filename, width, height, res, ...) {
|
||||
# to use ragg (say, instead of showtext, for custom font rendering).
|
||||
# In the next shiny release, this option will likely be superseded in
|
||||
# favor of a fully customizable graphics device option
|
||||
if ((getOption('shiny.useragg') %||% FALSE) && is_installed("ragg")) {
|
||||
if ((getOption('shiny.useragg') %||% FALSE) && is_available("ragg")) {
|
||||
pngfun <- ragg::agg_png
|
||||
} else if (capabilities("aqua")) {
|
||||
# i.e., png(type = 'quartz')
|
||||
pngfun <- grDevices::png
|
||||
} else if ((getOption('shiny.usecairo') %||% TRUE) && is_installed("Cairo")) {
|
||||
} else if ((getOption('shiny.usecairo') %||% TRUE) && is_available("Cairo")) {
|
||||
pngfun <- Cairo::CairoPNG
|
||||
} else {
|
||||
# i.e., png(type = 'cairo')
|
||||
|
||||
@@ -138,8 +138,7 @@ datePickerDependency <- function(theme) {
|
||||
htmlDependency(
|
||||
name = "bootstrap-datepicker-js",
|
||||
version = version_bs_date_picker,
|
||||
src = "www/shared/datepicker",
|
||||
package = "shiny",
|
||||
src = c(href = "shared/datepicker"),
|
||||
script = if (getOption("shiny.minified", TRUE)) "js/bootstrap-datepicker.min.js"
|
||||
else "js/bootstrap-datepicker.js",
|
||||
# Need to enable noConflict mode. See #1346.
|
||||
@@ -158,19 +157,18 @@ datePickerCSS <- function(theme) {
|
||||
return(htmlDependency(
|
||||
name = "bootstrap-datepicker-css",
|
||||
version = version_bs_date_picker,
|
||||
src = "www/shared/datepicker",
|
||||
package = "shiny",
|
||||
src = c(href = "shared/datepicker"),
|
||||
stylesheet = "css/bootstrap-datepicker3.min.css"
|
||||
))
|
||||
}
|
||||
|
||||
scss_file <- system_file(package = "shiny", "www/shared/datepicker/scss/build3.scss")
|
||||
scss_file <- system.file(package = "shiny", "www/shared/datepicker/scss/build3.scss")
|
||||
|
||||
bslib::bs_dependency(
|
||||
input = sass::sass_file(scss_file),
|
||||
theme = theme,
|
||||
name = "bootstrap-datepicker",
|
||||
version = version_bs_date_picker,
|
||||
cache_key_extra = get_package_version("shiny")
|
||||
cache_key_extra = shinyPackageVersion()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -213,7 +213,14 @@ selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
|
||||
deps <- list(selectizeDependency())
|
||||
|
||||
if ('drag_drop' %in% options$plugins) {
|
||||
deps[[length(deps) + 1]] <- jqueryuiDependency()
|
||||
deps <- c(
|
||||
deps,
|
||||
list(htmlDependency(
|
||||
'jqueryui', '1.12.1',
|
||||
c(href = 'shared/jqueryui'),
|
||||
script = 'jquery-ui.min.js'
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
# Insert script on same level as <select> tag
|
||||
@@ -240,7 +247,7 @@ selectizeDependencyFunc <- function(theme) {
|
||||
return(selectizeStaticDependency(version_selectize))
|
||||
}
|
||||
|
||||
selectizeDir <- system_file(package = "shiny", "www/shared/selectize/")
|
||||
selectizeDir <- system.file(package = "shiny", "www/shared/selectize/")
|
||||
bs_version <- bslib::theme_version(theme)
|
||||
stylesheet <- file.path(
|
||||
selectizeDir, "scss", paste0("selectize.bootstrap", bs_version, ".scss")
|
||||
@@ -260,17 +267,15 @@ selectizeDependencyFunc <- function(theme) {
|
||||
theme = theme,
|
||||
name = "selectize",
|
||||
version = version_selectize,
|
||||
cache_key_extra = get_package_version("shiny"),
|
||||
cache_key_extra = shinyPackageVersion(),
|
||||
.dep_args = list(script = script)
|
||||
)
|
||||
}
|
||||
|
||||
selectizeStaticDependency <- function(version) {
|
||||
htmlDependency(
|
||||
"selectize",
|
||||
version,
|
||||
src = "www/shared/selectize",
|
||||
package = "shiny",
|
||||
"selectize", version,
|
||||
src = c(href = "shared/selectize"),
|
||||
stylesheet = "css/selectize.bootstrap3.css",
|
||||
script = c(
|
||||
"js/selectize.min.js",
|
||||
|
||||
@@ -205,17 +205,13 @@ ionRangeSliderDependency <- function() {
|
||||
list(
|
||||
# ion.rangeSlider also needs normalize.css, which is already included in Bootstrap.
|
||||
htmlDependency(
|
||||
"ionrangeslider-javascript",
|
||||
version_ion_range_slider,
|
||||
src = "www/shared/ionrangeslider",
|
||||
package = "shiny",
|
||||
"ionrangeslider-javascript", version_ion_range_slider,
|
||||
src = c(href = "shared/ionrangeslider"),
|
||||
script = "js/ion.rangeSlider.min.js"
|
||||
),
|
||||
htmlDependency(
|
||||
"strftime",
|
||||
version_strftime,
|
||||
src = "www/shared/strftime",
|
||||
package = "shiny",
|
||||
"strftime", version_strftime,
|
||||
src = c(href = "shared/strftime"),
|
||||
script = "strftime-min.js"
|
||||
),
|
||||
bslib::bs_dependency_defer(ionRangeSliderDependencyCSS)
|
||||
@@ -227,8 +223,7 @@ ionRangeSliderDependencyCSS <- function(theme) {
|
||||
return(htmlDependency(
|
||||
"ionrangeslider-css",
|
||||
version_ion_range_slider,
|
||||
src = "www/shared/ionrangeslider",
|
||||
package = "shiny",
|
||||
src = c(href = "shared/ionrangeslider"),
|
||||
stylesheet = "css/ion.rangeSlider.css"
|
||||
))
|
||||
}
|
||||
@@ -237,13 +232,13 @@ ionRangeSliderDependencyCSS <- function(theme) {
|
||||
input = list(
|
||||
list(accent = "$component-active-bg"),
|
||||
sass::sass_file(
|
||||
system_file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
|
||||
system.file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
|
||||
)
|
||||
),
|
||||
theme = theme,
|
||||
name = "ionRangeSlider",
|
||||
version = version_ion_range_slider,
|
||||
cache_key_extra = get_package_version("shiny")
|
||||
cache_key_extra = shinyPackageVersion()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ normalizeChoicesArgs <- function(choices, choiceNames, choiceValues,
|
||||
if (length(choiceNames) != length(choiceValues)) {
|
||||
stop("`choiceNames` and `choiceValues` must have the same length.")
|
||||
}
|
||||
if (any_named(choiceNames) || any_named(choiceValues)) {
|
||||
if (anyNamed(choiceNames) || anyNamed(choiceValues)) {
|
||||
stop("`choiceNames` and `choiceValues` must not be named.")
|
||||
}
|
||||
} else {
|
||||
|
||||
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,14 +99,3 @@ fixedPanel <- function(...,
|
||||
width=width, height=height, draggable=draggable, cursor=match.arg(cursor),
|
||||
fixed=TRUE)
|
||||
}
|
||||
|
||||
|
||||
jqueryuiDependency <- function() {
|
||||
htmlDependency(
|
||||
'jqueryui',
|
||||
'1.12.1',
|
||||
src = 'www/shared/jqueryui',
|
||||
package = 'shiny',
|
||||
script = 'jquery-ui.min.js'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ knit_print.shiny.appobj <- function(x, ...) {
|
||||
#' @param inline Whether the object is printed inline.
|
||||
knit_print.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
x <- htmltools::as.tags(x, inline = inline)
|
||||
output <- knitr::knit_print(tagList(x), ..., inline = inline)
|
||||
output <- knitr::knit_print(tagList(x))
|
||||
attr(output, "knit_cacheable") <- FALSE
|
||||
attr(output, "knit_meta") <- append(attr(output, "knit_meta"),
|
||||
shiny_rmd_warning())
|
||||
@@ -77,5 +77,5 @@ knit_print.reactive <- function(x, ..., inline = FALSE) {
|
||||
renderFunc <- if (inline) renderText else renderPrint
|
||||
knitr::knit_print(renderFunc({
|
||||
x()
|
||||
}), ..., inline = inline)
|
||||
}), inline = inline)
|
||||
}
|
||||
|
||||
@@ -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/main/tests/testthat/common.R
|
||||
# https://github.com/rstudio/promises/blob/master/tests/testthat/common.R
|
||||
# Block until all pending later tasks have executed
|
||||
wait_for_it <- function() {
|
||||
while (!later::loop_empty()) {
|
||||
|
||||
@@ -1203,7 +1203,7 @@ Observer <- R6Class(
|
||||
# validation = function(e) NULL,
|
||||
# shiny.output.cancel = function(e) NULL
|
||||
|
||||
if (cnd_inherits(e, "shiny.silent.error")) {
|
||||
if (inherits(e, "shiny.silent.error")) {
|
||||
return()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
####
|
||||
# Generated by `./tools/documentation/updateReexports.R`: do not edit by hand
|
||||
# Please call `source('tools/documentation/updateReexports.R')` from the root folder to update`
|
||||
# Generated by `./tools/updateReexports.R`: do not edit by hand
|
||||
# Please call `source('tools/updateReexports.R') from the root folder to update`
|
||||
####
|
||||
|
||||
|
||||
@@ -90,20 +90,17 @@ htmltools::em
|
||||
#' @export
|
||||
htmltools::hr
|
||||
|
||||
|
||||
# htmltools tag.Rd -------------------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools tag
|
||||
#' @export
|
||||
htmltools::tag
|
||||
|
||||
|
||||
# htmltools tagList.Rd ---------------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools tagList
|
||||
#' @export
|
||||
htmltools::tagList
|
||||
|
||||
|
||||
# htmltools tagAppendAttributes.Rd ---------------------------------------------
|
||||
|
||||
#' @importFrom htmltools tagAppendAttributes
|
||||
#' @export
|
||||
htmltools::tagAppendAttributes
|
||||
@@ -116,9 +113,6 @@ htmltools::tagHasAttribute
|
||||
#' @export
|
||||
htmltools::tagGetAttribute
|
||||
|
||||
|
||||
# htmltools tagAppendChild.Rd --------------------------------------------------
|
||||
|
||||
#' @importFrom htmltools tagAppendChild
|
||||
#' @export
|
||||
htmltools::tagAppendChild
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
#' # At the top of app.R, this set the application-scoped cache to be a disk
|
||||
#' # cache that can be shared among multiple concurrent R processes, and is
|
||||
#' # deleted when the system reboots.
|
||||
#' shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache")))
|
||||
#' shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache"))
|
||||
#'
|
||||
#' # At the top of app.R, this set the application-scoped cache to be a disk
|
||||
#' # cache that can be shared among multiple concurrent R processes, and
|
||||
|
||||
@@ -612,7 +612,7 @@ getGgplotCoordmap <- function(p, width, height, res) {
|
||||
find_panel_info <- function(b) {
|
||||
# Structure of ggplot objects changed after 2.1.0. After 2.2.1, there was a
|
||||
# an API for extracting the necessary information.
|
||||
ggplot_ver <- get_package_version("ggplot2")
|
||||
ggplot_ver <- utils::packageVersion("ggplot2")
|
||||
|
||||
if (ggplot_ver > "2.2.1") {
|
||||
find_panel_info_api(b)
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' runUrl('https://github.com/rstudio/shiny_example/archive/main.tar.gz')
|
||||
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the archive
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/main.zip",
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
#' subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, destdir = NULL, ...) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -41,9 +41,9 @@ inputHandlers <- Map$new()
|
||||
#' })
|
||||
#'
|
||||
#' ## On the Javascript side, the associated input binding must have a corresponding getType method:
|
||||
#' # getType: function(el) {
|
||||
#' # return "mypackage.validint";
|
||||
#' # }
|
||||
#' getType: function(el) {
|
||||
#' return "mypackage.validint";
|
||||
#' }
|
||||
#'
|
||||
#' }
|
||||
#' @seealso [removeInputHandler()] [applyInputHandlers()]
|
||||
@@ -181,7 +181,7 @@ registerInputHandler("shiny.datetime", function(val, ...){
|
||||
|
||||
registerInputHandler("shiny.action", function(val, shinysession, name) {
|
||||
# mark up the action button value with a special class so we can recognize it later
|
||||
class(val) <- c("shinyActionButtonValue", class(val))
|
||||
class(val) <- c(class(val), "shinyActionButtonValue")
|
||||
val
|
||||
})
|
||||
|
||||
|
||||
@@ -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 (any_unnamed(msg)) {
|
||||
if (anyUnnamed(msg)) {
|
||||
stop("All arguments to sendMessage must be named.")
|
||||
}
|
||||
private$write(toJSON(msg))
|
||||
@@ -825,7 +825,7 @@ ShinySession <- R6Class(
|
||||
dots <- eval(substitute(alist(...)))
|
||||
}
|
||||
|
||||
if (any_unnamed(dots))
|
||||
if (anyUnnamed(dots))
|
||||
stop("exportTestValues: all arguments must be named.")
|
||||
|
||||
names(dots) <- ns(names(dots))
|
||||
@@ -913,7 +913,7 @@ ShinySession <- R6Class(
|
||||
|
||||
# Copy `values` from scopeState to state, adding namespace
|
||||
if (length(scopeState$values) != 0) {
|
||||
if (any_unnamed(scopeState$values)) {
|
||||
if (anyUnnamed(scopeState$values)) {
|
||||
stop("All scope values in must be named.")
|
||||
}
|
||||
|
||||
@@ -1114,12 +1114,7 @@ ShinySession <- R6Class(
|
||||
structure(list(), class = "try-error", condition = cond)
|
||||
} else if (inherits(cond, "shiny.output.cancel")) {
|
||||
structure(list(), class = "cancel-output")
|
||||
} else if (cnd_inherits(cond, "shiny.silent.error")) {
|
||||
# The error condition might have been chained by
|
||||
# foreign code, e.g. dplyr. Find the original error.
|
||||
while (!inherits(cond, "shiny.silent.error")) {
|
||||
cond <- cond$parent
|
||||
}
|
||||
} else if (inherits(cond, "shiny.silent.error")) {
|
||||
# Don't let shiny.silent.error go through the normal stop
|
||||
# path of try, because we don't want it to print. But we
|
||||
# do want to try to return the same looking result so that
|
||||
@@ -1706,7 +1701,7 @@ ShinySession <- R6Class(
|
||||
dots <- eval(substitute(alist(...)))
|
||||
}
|
||||
|
||||
if (any_unnamed(dots))
|
||||
if (anyUnnamed(dots))
|
||||
stop("exportTestValues: all arguments must be named.")
|
||||
|
||||
# Create a named list where each item is a list with an expression and
|
||||
|
||||
@@ -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,14 +55,8 @@ renderPage <- function(ui, showcase=0, testMode=FALSE) {
|
||||
if (testMode) {
|
||||
# Add code injection listener if in test mode
|
||||
shiny_deps[[length(shiny_deps) + 1]] <-
|
||||
htmlDependency(
|
||||
"shiny-testmode",
|
||||
get_package_version("shiny"),
|
||||
src = "www/shared",
|
||||
package = "shiny",
|
||||
script = "shiny-testmode.js",
|
||||
all_files = FALSE
|
||||
)
|
||||
htmlDependency("shiny-testmode", shinyPackageVersion(),
|
||||
c(href="shared"), script = "shiny-testmode.js")
|
||||
}
|
||||
|
||||
html <- renderDocument(ui, shiny_deps, processDep = createWebDependency)
|
||||
@@ -74,19 +68,23 @@ jqueryDependency <- function() {
|
||||
if (version == 3) {
|
||||
return(htmlDependency(
|
||||
"jquery", version_jquery,
|
||||
src = "www/shared",
|
||||
src = c(
|
||||
href = "shared",
|
||||
file = "www/shared"
|
||||
),
|
||||
package = "shiny",
|
||||
script = "jquery.min.js",
|
||||
all_files = FALSE
|
||||
script = "jquery.min.js"
|
||||
))
|
||||
}
|
||||
if (version == 1) {
|
||||
return(htmlDependency(
|
||||
"jquery", "1.12.4",
|
||||
src = "www/shared/legacy",
|
||||
src = c(
|
||||
href = "shared/legacy",
|
||||
file = "www/shared/legacy"
|
||||
),
|
||||
package = "shiny",
|
||||
script = "jquery.min.js",
|
||||
all_files = FALSE
|
||||
script = "jquery.min.js"
|
||||
))
|
||||
}
|
||||
stop("Unsupported version of jQuery: ", version)
|
||||
@@ -97,9 +95,8 @@ shinyDependencies <- function() {
|
||||
bslib::bs_dependency_defer(shinyDependencyCSS),
|
||||
htmlDependency(
|
||||
name = "shiny-javascript",
|
||||
version = get_package_version("shiny"),
|
||||
src = "www/shared",
|
||||
package = "shiny",
|
||||
version = shinyPackageVersion(),
|
||||
src = c(href = "shared"),
|
||||
script =
|
||||
if (isTRUE(
|
||||
get_devmode_option(
|
||||
@@ -109,27 +106,24 @@ shinyDependencies <- function() {
|
||||
))
|
||||
"shiny.min.js"
|
||||
else
|
||||
"shiny.js",
|
||||
all_files = FALSE
|
||||
"shiny.js"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
shinyDependencyCSS <- function(theme) {
|
||||
version <- get_package_version("shiny")
|
||||
version <- shinyPackageVersion()
|
||||
|
||||
if (!is_bs_theme(theme)) {
|
||||
return(htmlDependency(
|
||||
name = "shiny-css",
|
||||
version = version,
|
||||
src = "www/shared",
|
||||
package = "shiny",
|
||||
stylesheet = "shiny.min.css",
|
||||
all_files = FALSE
|
||||
src = c(href = "shared"),
|
||||
stylesheet = "shiny.min.css"
|
||||
))
|
||||
}
|
||||
|
||||
scss_home <- system_file("www/shared/shiny_scss", package = "shiny")
|
||||
scss_home <- system.file("www/shared/shiny_scss", package = "shiny")
|
||||
scss_files <- file.path(scss_home, c("bootstrap.scss", "shiny.scss"))
|
||||
scss_files <- lapply(scss_files, sass::sass_file)
|
||||
|
||||
|
||||
@@ -822,9 +822,7 @@ downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()
|
||||
#' Table output with the JavaScript DataTables library
|
||||
#'
|
||||
#' @description
|
||||
#' `r lifecycle::badge("superseded")` Please use
|
||||
#' \href{https://rstudio.github.io/DT/shiny.html}{\code{DT::renderDataTable()}}.
|
||||
#' (Shiny 0.11.1)
|
||||
#' `r lifecycle::badge("superseded")` Please use [`DT::renderDataTable()`]. (Shiny 0.11.1)
|
||||
#'
|
||||
#' Makes a reactive version of the given function that returns a data frame (or
|
||||
#' matrix), which will be rendered with the [DataTables](https://datatables.net)
|
||||
@@ -953,7 +951,7 @@ renderDataTable <- function(expr, options = NULL, searchDelay = 500,
|
||||
DT10Names <- function() {
|
||||
rbind(
|
||||
utils::read.table(
|
||||
system_file('www/shared/datatables/upgrade1.10.txt', package = 'shiny'),
|
||||
system.file('www/shared/datatables/upgrade1.10.txt', package = 'shiny'),
|
||||
stringsAsFactors = FALSE
|
||||
),
|
||||
c('aoColumns', 'Removed') # looks like an omission on the upgrade guide
|
||||
|
||||
42
R/showcase.R
42
R/showcase.R
@@ -32,40 +32,26 @@ licenseLink <- function(licenseName) {
|
||||
showcaseHead <- function() {
|
||||
|
||||
deps <- list(
|
||||
jqueryuiDependency(),
|
||||
htmlDependency(
|
||||
"showdown",
|
||||
"0.3.1",
|
||||
src = "www/shared/showdown/compressed",
|
||||
package="shiny",
|
||||
script = "showdown.js"
|
||||
),
|
||||
htmlDependency(
|
||||
"highlight.js",
|
||||
"6.2",
|
||||
src = "www/shared/highlight",
|
||||
package="shiny",
|
||||
script = "highlight.pack.js",
|
||||
stylesheet = "rstudio.css"
|
||||
),
|
||||
htmlDependency(
|
||||
"showcase",
|
||||
"0.1.0",
|
||||
src = "www/shared",
|
||||
package = "shiny",
|
||||
script = "shiny-showcase.js",
|
||||
stylesheet = "shiny-showcase.css",
|
||||
all_files = FALSE
|
||||
)
|
||||
htmlDependency("jqueryui", "1.12.1", c(href="shared/jqueryui"),
|
||||
script = "jquery-ui.min.js"),
|
||||
htmlDependency("showdown", "0.3.1", c(href="shared/showdown/compressed"),
|
||||
script = "showdown.js"),
|
||||
htmlDependency("highlight.js", "6.2", c(href="shared/highlight"),
|
||||
script = "highlight.pack.js")
|
||||
)
|
||||
|
||||
mdfile <- file.path.ci(getwd(), 'Readme.md')
|
||||
html <- tagList(
|
||||
html <- with(tags, tagList(
|
||||
script(src="shared/shiny-showcase.js"),
|
||||
link(rel="stylesheet", type="text/css",
|
||||
href="shared/highlight/rstudio.css"),
|
||||
link(rel="stylesheet", type="text/css",
|
||||
href="shared/shiny-showcase.css"),
|
||||
if (file.exists(mdfile))
|
||||
tags$script(type="text/markdown", id="showcase-markdown-content",
|
||||
script(type="text/markdown", id="showcase-markdown-content",
|
||||
paste(readUTF8(mdfile), collapse="\n"))
|
||||
else ""
|
||||
)
|
||||
))
|
||||
|
||||
return(attachDependencies(html, deps))
|
||||
}
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
# Generated by staticimports; do not edit by hand.
|
||||
# ======================================================================
|
||||
# Imported from pkg:staticimports
|
||||
# ======================================================================
|
||||
|
||||
# Given a vector, return TRUE if any elements are named, FALSE otherwise.
|
||||
# For zero-length vectors, always return FALSE.
|
||||
any_named <- function(x) {
|
||||
if (length(x) == 0) return(FALSE)
|
||||
nms <- names(x)
|
||||
!is.null(nms) && any(nzchar(nms))
|
||||
}
|
||||
|
||||
# Given a vector, return TRUE if any elements are unnamed, FALSE otherwise.
|
||||
# For zero-length vectors, always return FALSE.
|
||||
any_unnamed <- function(x) {
|
||||
if (length(x) == 0) return(FALSE)
|
||||
nms <- names(x)
|
||||
is.null(nms) || !all(nzchar(nms))
|
||||
}
|
||||
|
||||
# Borrowed from pkgload:::dev_meta, with some modifications.
|
||||
# Returns TRUE if `pkg` was loaded with `devtools::load_all()`.
|
||||
devtools_loaded <- function(pkg) {
|
||||
ns <- .getNamespace(pkg)
|
||||
if (is.null(ns) || is.null(ns$.__DEVTOOLS__)) {
|
||||
return(FALSE)
|
||||
}
|
||||
TRUE
|
||||
}
|
||||
|
||||
get_package_version <- function(pkg) {
|
||||
# `utils::packageVersion()` can be slow, so first try the fast path of
|
||||
# checking if the package is already loaded.
|
||||
ns <- .getNamespace(pkg)
|
||||
if (is.null(ns)) {
|
||||
utils::packageVersion(pkg)
|
||||
} else {
|
||||
as.package_version(ns$.__NAMESPACE__.$spec[["version"]])
|
||||
}
|
||||
}
|
||||
|
||||
is_installed <- function(pkg, version = NULL) {
|
||||
installed <- isNamespaceLoaded(pkg) || nzchar(system_file_cached(package = pkg))
|
||||
if (is.null(version)) {
|
||||
return(installed)
|
||||
}
|
||||
installed && isTRUE(get_package_version(pkg) >= version)
|
||||
}
|
||||
|
||||
register_upgrade_message <- function(pkg, version, error = FALSE) {
|
||||
|
||||
msg <- sprintf(
|
||||
"This version of '%s' is designed to work with '%s' >= %s.
|
||||
Please upgrade via install.packages('%s').",
|
||||
environmentName(environment(register_upgrade_message)),
|
||||
pkg, version, pkg
|
||||
)
|
||||
|
||||
cond <- if (error) stop else packageStartupMessage
|
||||
|
||||
if (pkg %in% loadedNamespaces() && !is_installed(pkg, version)) {
|
||||
cond(msg)
|
||||
}
|
||||
|
||||
# Always register hook in case pkg is loaded at some
|
||||
# point the future (or, potentially, but less commonly,
|
||||
# unloaded & reloaded)
|
||||
setHook(
|
||||
packageEvent(pkg, "onLoad"),
|
||||
function(...) {
|
||||
if (!is_installed(pkg, version)) cond(msg)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# Simplified version rlang:::s3_register() that just uses
|
||||
# warning() instead of rlang::warn() when registration fails
|
||||
# https://github.com/r-lib/rlang/blob/main/R/compat-s3-register.R
|
||||
s3_register <- function(generic, class, method = NULL) {
|
||||
stopifnot(is.character(generic), length(generic) == 1)
|
||||
stopifnot(is.character(class), length(class) == 1)
|
||||
|
||||
pieces <- strsplit(generic, "::")[[1]]
|
||||
stopifnot(length(pieces) == 2)
|
||||
package <- pieces[[1]]
|
||||
generic <- pieces[[2]]
|
||||
|
||||
caller <- parent.frame()
|
||||
|
||||
get_method_env <- function() {
|
||||
top <- topenv(caller)
|
||||
if (isNamespace(top)) {
|
||||
asNamespace(environmentName(top))
|
||||
} else {
|
||||
caller
|
||||
}
|
||||
}
|
||||
get_method <- function(method, env) {
|
||||
if (is.null(method)) {
|
||||
get(paste0(generic, ".", class), envir = get_method_env())
|
||||
} else {
|
||||
method
|
||||
}
|
||||
}
|
||||
|
||||
register <- function(...) {
|
||||
envir <- asNamespace(package)
|
||||
|
||||
# Refresh the method each time, it might have been updated by
|
||||
# `devtools::load_all()`
|
||||
method_fn <- get_method(method)
|
||||
stopifnot(is.function(method_fn))
|
||||
|
||||
# Only register if generic can be accessed
|
||||
if (exists(generic, envir)) {
|
||||
registerS3method(generic, class, method_fn, envir = envir)
|
||||
} else {
|
||||
warning(
|
||||
"Can't find generic `", generic, "` in package ", package,
|
||||
" register S3 method. Do you need to update ", package,
|
||||
" to the latest version?", call. = FALSE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
# Always register hook in case package is later unloaded & reloaded
|
||||
setHook(packageEvent(package, "onLoad"), function(...) {
|
||||
register()
|
||||
})
|
||||
|
||||
# Avoid registration failures during loading (pkgload or regular).
|
||||
# Check that environment is locked because the registering package
|
||||
# might be a dependency of the package that exports the generic. In
|
||||
# that case, the exports (and the generic) might not be populated
|
||||
# yet (#1225).
|
||||
if (isNamespaceLoaded(package) && environmentIsLocked(asNamespace(package))) {
|
||||
register()
|
||||
}
|
||||
|
||||
invisible()
|
||||
}
|
||||
|
||||
# Borrowed from pkgload::shim_system.file, with some modifications. This behaves
|
||||
# like `system.file()`, except that (1) for packages loaded with
|
||||
# `devtools::load_all()`, it will return the path to files in the package's
|
||||
# inst/ directory, and (2) for other packages, the directory lookup is cached.
|
||||
# Also, to keep the implementation simple, it doesn't support specification of
|
||||
# lib.loc or mustWork.
|
||||
system_file <- function(..., package = "base") {
|
||||
if (!devtools_loaded(package)) {
|
||||
return(system_file_cached(..., package = package))
|
||||
}
|
||||
|
||||
if (!is.null(names(list(...)))) {
|
||||
stop("All arguments other than `package` must be unnamed.")
|
||||
}
|
||||
|
||||
# If package was loaded with devtools (the package loaded with load_all),
|
||||
# also search for files under inst/, and don't cache the results (it seems
|
||||
# more likely that the package path will change during the development
|
||||
# process)
|
||||
pkg_path <- find.package(package)
|
||||
|
||||
# First look in inst/
|
||||
files_inst <- file.path(pkg_path, "inst", ...)
|
||||
present_inst <- file.exists(files_inst)
|
||||
|
||||
# For any files that weren't present in inst/, look in the base path
|
||||
files_top <- file.path(pkg_path, ...)
|
||||
present_top <- file.exists(files_top)
|
||||
|
||||
# Merge them together. Here are the different possible conditions, and the
|
||||
# desired result. NULL means to drop that element from the result.
|
||||
#
|
||||
# files_inst: /inst/A /inst/B /inst/C /inst/D
|
||||
# present_inst: T T F F
|
||||
# files_top: /A /B /C /D
|
||||
# present_top: T F T F
|
||||
# result: /inst/A /inst/B /C NULL
|
||||
#
|
||||
files <- files_top
|
||||
files[present_inst] <- files_inst[present_inst]
|
||||
# Drop cases where not present in either location
|
||||
files <- files[present_inst | present_top]
|
||||
if (length(files) == 0) {
|
||||
return("")
|
||||
}
|
||||
# Make sure backslashes are replaced with slashes on Windows
|
||||
normalizePath(files, winslash = "/")
|
||||
}
|
||||
|
||||
# A wrapper for `system.file()`, which caches the results, because
|
||||
# `system.file()` can be slow. Note that because of caching, if
|
||||
# `system_file_cached()` is called on a package that isn't installed, then the
|
||||
# package is installed, and then `system_file_cached()` is called again, it will
|
||||
# still return "".
|
||||
system_file_cached <- local({
|
||||
pkg_dir_cache <- character()
|
||||
|
||||
function(..., package = "base") {
|
||||
if (!is.null(names(list(...)))) {
|
||||
stop("All arguments other than `package` must be unnamed.")
|
||||
}
|
||||
|
||||
not_cached <- is.na(match(package, names(pkg_dir_cache)))
|
||||
if (not_cached) {
|
||||
pkg_dir <- system.file(package = package)
|
||||
pkg_dir_cache[[package]] <<- pkg_dir
|
||||
} else {
|
||||
pkg_dir <- pkg_dir_cache[[package]]
|
||||
}
|
||||
|
||||
file.path(pkg_dir, ...)
|
||||
}
|
||||
})
|
||||
73
R/utils.R
73
R/utils.R
@@ -2,11 +2,6 @@
|
||||
#' @include map.R
|
||||
NULL
|
||||
|
||||
# @staticimports pkg:staticimports
|
||||
# is_installed get_package_version system_file
|
||||
# s3_register register_upgrade_message
|
||||
# any_named any_unnamed
|
||||
|
||||
#' Make a random number generator repeatable
|
||||
#'
|
||||
#' Given a function that generates random data, returns a wrapped version of
|
||||
@@ -131,6 +126,34 @@ dropNullsOrEmpty <- function(x) {
|
||||
x[!vapply(x, nullOrEmpty, FUN.VALUE=logical(1))]
|
||||
}
|
||||
|
||||
# Given a vector/list, return TRUE if any elements are named, FALSE otherwise.
|
||||
anyNamed <- function(x) {
|
||||
# Zero-length vector
|
||||
if (length(x) == 0) return(FALSE)
|
||||
|
||||
nms <- names(x)
|
||||
|
||||
# List with no name attribute
|
||||
if (is.null(nms)) return(FALSE)
|
||||
|
||||
# List with name attribute; check for any ""
|
||||
any(nzchar(nms))
|
||||
}
|
||||
|
||||
# Given a vector/list, return TRUE if any elements are unnamed, FALSE otherwise.
|
||||
anyUnnamed <- function(x) {
|
||||
# Zero-length vector
|
||||
if (length(x) == 0) return(FALSE)
|
||||
|
||||
nms <- names(x)
|
||||
|
||||
# List with no name attribute
|
||||
if (is.null(nms)) return(TRUE)
|
||||
|
||||
# List with name attribute; check for any ""
|
||||
any(!nzchar(nms))
|
||||
}
|
||||
|
||||
|
||||
# Given a vector/list, returns a named vector/list (the labels will be blank).
|
||||
asNamed <- function(x) {
|
||||
@@ -150,7 +173,7 @@ empty_named_list <- function() {
|
||||
# name as elements in a, the element in a is dropped. Also, if there are any
|
||||
# duplicated names in a or b, only the last one with that name is kept.
|
||||
mergeVectors <- function(a, b) {
|
||||
if (any_unnamed(a) || any_unnamed(b)) {
|
||||
if (anyUnnamed(a) || anyUnnamed(b)) {
|
||||
stop("Vectors must be either NULL or have names for all elements")
|
||||
}
|
||||
|
||||
@@ -163,7 +186,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 (any_unnamed(x))
|
||||
if (anyUnnamed(x))
|
||||
stop("All items must be named")
|
||||
|
||||
# Special case for empty vectors/lists, and NULL
|
||||
@@ -472,7 +495,7 @@ shinyCallingHandlers <- function(expr) {
|
||||
withCallingHandlers(captureStackTraces(expr),
|
||||
error = function(e) {
|
||||
# Don't intercept shiny.silent.error (i.e. validation errors)
|
||||
if (cnd_inherits(e, "shiny.silent.error"))
|
||||
if (inherits(e, "shiny.silent.error"))
|
||||
return()
|
||||
|
||||
handle <- getOption('shiny.error')
|
||||
@@ -1693,20 +1716,24 @@ findEnclosingApp <- function(path = ".") {
|
||||
}
|
||||
}
|
||||
|
||||
# Until `rlang::cnd_inherits()` is on CRAN
|
||||
cnd_inherits <- function(cnd, class) {
|
||||
cnd_some(cnd, ~ inherits(.x, class))
|
||||
}
|
||||
cnd_some <- function(.cnd, .p, ...) {
|
||||
.p <- rlang::as_function(.p)
|
||||
|
||||
while (rlang::is_condition(.cnd)) {
|
||||
if (.p(.cnd, ...)) {
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
.cnd <- .cnd$parent
|
||||
# Check if a package is installed, and if version is specified,
|
||||
# that we have at least that version
|
||||
is_available <- function(package, version = NULL) {
|
||||
installed <- nzchar(system.file(package = package))
|
||||
if (is.null(version)) {
|
||||
return(installed)
|
||||
}
|
||||
|
||||
FALSE
|
||||
installed && isTRUE(utils::packageVersion(package) >= version)
|
||||
}
|
||||
|
||||
|
||||
# cached version of utils::packageVersion("shiny")
|
||||
shinyPackageVersion <- local({
|
||||
version <- NULL
|
||||
function() {
|
||||
if (is.null(version)) {
|
||||
version <<- utils::packageVersion("shiny")
|
||||
}
|
||||
version
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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/main/.github/CONTRIBUTING.md) file for detailed guidelines of how to contribute.
|
||||
We welcome contributions to the **shiny** package. Please see our [CONTRIBUTING.md](https://github.com/rstudio/shiny/blob/master/.github/CONTRIBUTING.md) file for detailed guidelines of how to contribute.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.7.1.9002 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.6.0.9022 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
(function(){var t="ws:";window.location.protocol==="https:"&&(t="wss:");var o=window.location.pathname;/\/$/.test(o)||(o+="/");o+="autoreload/";var n=new WebSocket(t+"//"+window.location.host+o);n.onmessage=function(a){a.data==="autoreload"&&window.location.reload()};})();
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjdHMvZXh0cmFzL3NoaW55LWF1dG9yZWxvYWQudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qIGVzbGludC1kaXNhYmxlIHVuaWNvcm4vZmlsZW5hbWUtY2FzZSAqL1xudmFyIHByb3RvY29sID0gXCJ3czpcIjtcbmlmICh3aW5kb3cubG9jYXRpb24ucHJvdG9jb2wgPT09IFwiaHR0cHM6XCIpIHByb3RvY29sID0gXCJ3c3M6XCI7XG52YXIgZGVmYXVsdFBhdGggPSB3aW5kb3cubG9jYXRpb24ucGF0aG5hbWU7XG5pZiAoIS9cXC8kLy50ZXN0KGRlZmF1bHRQYXRoKSkgZGVmYXVsdFBhdGggKz0gXCIvXCI7XG5kZWZhdWx0UGF0aCArPSBcImF1dG9yZWxvYWQvXCI7XG52YXIgd3MgPSBuZXcgV2ViU29ja2V0KHByb3RvY29sICsgXCIvL1wiICsgd2luZG93LmxvY2F0aW9uLmhvc3QgKyBkZWZhdWx0UGF0aCk7XG5cbndzLm9ubWVzc2FnZSA9IGZ1bmN0aW9uIChldmVudCkge1xuICBpZiAoZXZlbnQuZGF0YSA9PT0gXCJhdXRvcmVsb2FkXCIpIHtcbiAgICB3aW5kb3cubG9jYXRpb24ucmVsb2FkKCk7XG4gIH1cbn07XG5cbmV4cG9ydCB7fTsiXSwKICAibWFwcGluZ3MiOiAiO1lBQ0EsR0FBSSxHQUFXLE1BQ2YsQUFBSSxPQUFPLFNBQVMsV0FBYSxVQUFVLEdBQVcsUUFDdEQsR0FBSSxHQUFjLE9BQU8sU0FBUyxTQUNsQyxBQUFLLE1BQU0sS0FBSyxJQUFjLElBQWUsS0FDN0MsR0FBZSxjQUNmLEdBQUksR0FBSyxHQUFJLFdBQVUsRUFBVyxLQUFPLE9BQU8sU0FBUyxLQUFPLEdBRWhFLEVBQUcsVUFBWSxTQUFVLEVBQU8sQ0FDOUIsQUFBSSxFQUFNLE9BQVMsY0FDakIsT0FBTyxTQUFTIiwKICAibmFtZXMiOiBbXQp9Cg==
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.7.1.9002 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.6.0.9022 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,"Courier New",monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:normal}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav{margin-bottom:0}#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.7.1.9002 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.6.0.9022 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
(function(){var a=eval;window.addEventListener("message",function(i){var e=i.data;e.code&&a(e.code)});})();
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjdHMvc3JjL3V0aWxzL2V2YWwudHMiLCAiLi4vLi4vLi4vc3JjdHMvZXh0cmFzL3NoaW55LXRlc3Rtb2RlLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvL2VzYnVpbGQuZ2l0aHViLmlvL2NvbnRlbnQtdHlwZXMvI2RpcmVjdC1ldmFsXG4vL3RsL2RyO1xuLy8gKiBEaXJlY3QgdXNhZ2Ugb2YgYGV2YWwoXCJ4XCIpYCBpcyBiYWQgd2l0aCBidW5kbGVkIGNvZGUuXG4vLyAqIEluc3RlYWQsIHVzZSBpbmRpcmVjdCBjYWxscyB0byBgZXZhbGAgc3VjaCBhcyBgaW5kaXJlY3RFdmFsKFwieFwiKWBcbi8vICAgKiBFdmVuIGp1c3QgcmVuYW1pbmcgdGhlIGZ1bmN0aW9uIHdvcmtzIHdlbGwgZW5vdWdoLlxuLy8gPiBUaGlzIGlzIGtub3duIGFzIFwiaW5kaXJlY3QgZXZhbFwiIGJlY2F1c2UgZXZhbCBpcyBub3QgYmVpbmcgY2FsbGVkIGRpcmVjdGx5LCBhbmQgc28gZG9lcyBub3QgdHJpZ2dlciB0aGUgZ3JhbW1hdGljYWwgc3BlY2lhbCBjYXNlIGZvciBkaXJlY3QgZXZhbCBpbiB0aGUgSmF2YVNjcmlwdCBWTS4gWW91IGNhbiBjYWxsIGluZGlyZWN0IGV2YWwgdXNpbmcgYW55IHN5bnRheCBhdCBhbGwgZXhjZXB0IGZvciBhbiBleHByZXNzaW9uIG9mIHRoZSBleGFjdCBmb3JtIGV2YWwoJ3gnKS4gRm9yIGV4YW1wbGUsIHZhciBldmFsMiA9IGV2YWw7IGV2YWwyKCd4JykgYW5kIFtldmFsXVswXSgneCcpIGFuZCB3aW5kb3cuZXZhbCgneCcpIGFyZSBhbGwgaW5kaXJlY3QgZXZhbCBjYWxscy5cbi8vID4gV2hlbiB5b3UgdXNlIGluZGlyZWN0IGV2YWwsIHRoZSBjb2RlIGlzIGV2YWx1YXRlZCBpbiB0aGUgZ2xvYmFsIHNjb3BlIGluc3RlYWQgb2YgaW4gdGhlIGlubGluZSBzY29wZSBvZiB0aGUgY2FsbGVyLlxudmFyIGluZGlyZWN0RXZhbCA9IGV2YWw7XG5leHBvcnQgeyBpbmRpcmVjdEV2YWwgfTsiLCAiLyogZXNsaW50LWRpc2FibGUgdW5pY29ybi9maWxlbmFtZS1jYXNlICovXG5pbXBvcnQgeyBpbmRpcmVjdEV2YWwgfSBmcm9tIFwiLi4vc3JjL3V0aWxzL2V2YWxcIjsgLy8gTGlzdGVuIGZvciBtZXNzYWdlcyBmcm9tIHBhcmVudCBmcmFtZS4gVGhpcyBmaWxlIGlzIG9ubHkgYWRkZWQgd2hlbiB0aGVcbi8vIHNoaW55LnRlc3Rtb2RlIG9wdGlvbiBpcyBUUlVFLlxuXG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcIm1lc3NhZ2VcIiwgZnVuY3Rpb24gKGUpIHtcbiAgdmFyIG1lc3NhZ2UgPSBlLmRhdGE7XG4gIGlmIChtZXNzYWdlLmNvZGUpIGluZGlyZWN0RXZhbChtZXNzYWdlLmNvZGUpO1xufSk7Il0sCiAgIm1hcHBpbmdzIjogIjtZQU9BLEdBQUksR0FBZSxLQ0huQixPQUFPLGlCQUFpQixVQUFXLFNBQVUsRUFBRyxDQUM5QyxHQUFJLEdBQVUsRUFBRSxLQUNoQixBQUFJLEVBQVEsTUFBTSxFQUFhLEVBQVEiLAogICJuYW1lcyI6IFtdCn0K
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*! shiny 1.7.1.9002 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.6.0.9022 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
(function() {
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
@@ -2131,19 +2131,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;
|
||||
}
|
||||
});
|
||||
|
||||
// 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) {
|
||||
@@ -2782,6 +2769,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;
|
||||
}
|
||||
});
|
||||
|
||||
// srcts/src/initialize/disableForm.ts
|
||||
var import_jquery = __toModule(require_jquery());
|
||||
function disableFormSubmission() {
|
||||
@@ -2877,9 +2877,6 @@
|
||||
} else {
|
||||
setIsQt(false);
|
||||
}
|
||||
if (/\bQt/.test(userAgent) && /\bMacintosh/.test(userAgent)) {
|
||||
(0, import_jquery3.default)(document.documentElement).addClass("qtmac");
|
||||
}
|
||||
if (/\bQt\/5/.test(userAgent) && /Linux/.test(userAgent)) {
|
||||
(0, import_jquery3.default)(document.documentElement).addClass("qt5");
|
||||
}
|
||||
@@ -3762,7 +3759,7 @@
|
||||
}, {
|
||||
key: "getId",
|
||||
value: function getId(el) {
|
||||
return el.getAttribute("data-input-id") || el.id;
|
||||
return el["data-input-id"] || el.id;
|
||||
}
|
||||
}, {
|
||||
key: "getType",
|
||||
@@ -8040,7 +8037,7 @@
|
||||
}, {
|
||||
key: "getId",
|
||||
value: function getId(el) {
|
||||
return el.getAttribute("data-input-id") || el.id;
|
||||
return el["data-input-id"] || el.id;
|
||||
}
|
||||
}, {
|
||||
key: "onValueChange",
|
||||
@@ -8825,53 +8822,29 @@
|
||||
var import_es_array_iterator20 = __toModule(require_es_array_iterator());
|
||||
var import_jquery28 = __toModule(require_jquery());
|
||||
|
||||
// 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;
|
||||
// srcts/src/shiny/render.ts
|
||||
var import_es_regexp_exec6 = __toModule(require_es_regexp_exec());
|
||||
|
||||
// node_modules/core-js/modules/es.object.entries.js
|
||||
var $46 = require_export();
|
||||
var $45 = require_export();
|
||||
var $entries = require_object_to_array().entries;
|
||||
$46({ target: "Object", stat: true }, {
|
||||
$45({ 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 $47 = require_export();
|
||||
var $46 = require_export();
|
||||
var from = require_array_from();
|
||||
var checkCorrectnessOfIteration = require_check_correctness_of_iteration();
|
||||
var INCORRECT_ITERATION = !checkCorrectnessOfIteration(function(iterable) {
|
||||
Array.from(iterable);
|
||||
});
|
||||
$47({ target: "Array", stat: true, forced: INCORRECT_ITERATION }, {
|
||||
$46({ target: "Array", stat: true, forced: INCORRECT_ITERATION }, {
|
||||
from: from
|
||||
});
|
||||
|
||||
@@ -9088,7 +9061,9 @@
|
||||
}
|
||||
function renderDependencies(dependencies) {
|
||||
if (dependencies) {
|
||||
dependencies.forEach(renderDependency);
|
||||
import_jquery27.default.each(dependencies, function(i, dep) {
|
||||
renderDependency(dep);
|
||||
});
|
||||
}
|
||||
}
|
||||
function renderContent(el, content) {
|
||||
@@ -9096,7 +9071,7 @@
|
||||
if (where === "replace") {
|
||||
shinyUnbindAll(el);
|
||||
}
|
||||
var html = "";
|
||||
var html;
|
||||
var dependencies = [];
|
||||
if (content === null) {
|
||||
html = "";
|
||||
@@ -9145,208 +9120,128 @@
|
||||
}
|
||||
return htmlDependencies[names[idx]] === dep.version;
|
||||
}
|
||||
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))
|
||||
function renderDependency(dep) {
|
||||
var restyle = needsRestyle(dep);
|
||||
if (hasOwnProperty(htmlDependencies, dep.name) && !restyle)
|
||||
return false;
|
||||
registerDependency(dep.name, dep.version);
|
||||
var href = dep.src.href;
|
||||
var $head = (0, import_jquery27.default)("head").first();
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
script.setAttribute(attr, val ? val : "");
|
||||
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(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) {
|
||||
$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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
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
|
||||
};
|
||||
}
|
||||
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;
|
||||
});
|
||||
$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) {
|
||||
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) {
|
||||
@@ -9474,11 +9369,11 @@
|
||||
|
||||
// node_modules/core-js/modules/es.array.filter.js
|
||||
"use strict";
|
||||
var $51 = require_export();
|
||||
var $50 = require_export();
|
||||
var $filter = require_array_iteration().filter;
|
||||
var arrayMethodHasSpeciesSupport5 = require_array_method_has_species_support();
|
||||
var HAS_SPECIES_SUPPORT4 = arrayMethodHasSpeciesSupport5("filter");
|
||||
$51({ target: "Array", proto: true, forced: !HAS_SPECIES_SUPPORT4 }, {
|
||||
$50({ target: "Array", proto: true, forced: !HAS_SPECIES_SUPPORT4 }, {
|
||||
filter: function filter(callbackfn) {
|
||||
return $filter(this, callbackfn, arguments.length > 1 ? arguments[1] : void 0);
|
||||
}
|
||||
@@ -12042,21 +11937,21 @@
|
||||
|
||||
// node_modules/core-js/modules/es.array-buffer.constructor.js
|
||||
"use strict";
|
||||
var $64 = require_export();
|
||||
var global7 = require_global();
|
||||
var $63 = require_export();
|
||||
var global6 = require_global();
|
||||
var arrayBufferModule = require_array_buffer();
|
||||
var setSpecies = require_set_species();
|
||||
var ARRAY_BUFFER = "ArrayBuffer";
|
||||
var ArrayBuffer2 = arrayBufferModule[ARRAY_BUFFER];
|
||||
var NativeArrayBuffer = global7[ARRAY_BUFFER];
|
||||
$64({ global: true, forced: NativeArrayBuffer !== ArrayBuffer2 }, {
|
||||
var NativeArrayBuffer = global6[ARRAY_BUFFER];
|
||||
$63({ global: true, forced: NativeArrayBuffer !== ArrayBuffer2 }, {
|
||||
ArrayBuffer: ArrayBuffer2
|
||||
});
|
||||
setSpecies(ARRAY_BUFFER);
|
||||
|
||||
// node_modules/core-js/modules/es.array-buffer.slice.js
|
||||
"use strict";
|
||||
var $65 = require_export();
|
||||
var $64 = require_export();
|
||||
var fails10 = require_fails();
|
||||
var ArrayBufferModule = require_array_buffer();
|
||||
var anObject9 = require_an_object();
|
||||
@@ -12069,7 +11964,7 @@
|
||||
var INCORRECT_SLICE = fails10(function() {
|
||||
return !new ArrayBuffer3(2).slice(1, void 0).byteLength;
|
||||
});
|
||||
$65({ target: "ArrayBuffer", proto: true, unsafe: true, forced: INCORRECT_SLICE }, {
|
||||
$64({ 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);
|
||||
@@ -12089,23 +11984,23 @@
|
||||
});
|
||||
|
||||
// node_modules/core-js/modules/es.data-view.js
|
||||
var $66 = require_export();
|
||||
var $65 = require_export();
|
||||
var ArrayBufferModule2 = require_array_buffer();
|
||||
var NATIVE_ARRAY_BUFFER = require_array_buffer_native();
|
||||
$66({ global: true, forced: !NATIVE_ARRAY_BUFFER }, {
|
||||
$65({ global: true, forced: !NATIVE_ARRAY_BUFFER }, {
|
||||
DataView: ArrayBufferModule2.DataView
|
||||
});
|
||||
|
||||
// node_modules/core-js/modules/es.array.reduce.js
|
||||
"use strict";
|
||||
var $67 = require_export();
|
||||
var $66 = 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;
|
||||
$67({ target: "Array", proto: true, forced: !STRICT_METHOD3 || CHROME_BUG }, {
|
||||
$66({ 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);
|
||||
}
|
||||
@@ -12113,6 +12008,34 @@
|
||||
|
||||
// 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)) {
|
||||
@@ -12892,8 +12815,8 @@
|
||||
}
|
||||
function getTabIndex($tabset2, tabsetId2) {
|
||||
var existingTabIds = [0];
|
||||
$tabset2.find("> li").each(function() {
|
||||
var $tab = (0, import_jquery41.default)(this).find("> a[data-toggle='tab']");
|
||||
$tabset2.find("a[data-toggle='tab']").each(function() {
|
||||
var $tab = (0, import_jquery41.default)(this);
|
||||
if ($tab.length > 0) {
|
||||
var href = $tab.attr("href").replace(/.*(?=#[^\s]+$)/, "");
|
||||
var _index = href.replace("#tab-" + tabsetId2 + "-", "");
|
||||
@@ -13322,6 +13245,7 @@
|
||||
(0, import_jquery42.default)(document).one("shiny:connected", function() {
|
||||
initDeferredIframes();
|
||||
});
|
||||
window.console.log("Shiny version: ", windowShiny3.version);
|
||||
}
|
||||
function initDeferredIframes() {
|
||||
if (!window.Shiny || !window.Shiny.shinyapp || !window.Shiny.shinyapp.isConnected()) {
|
||||
@@ -13339,7 +13263,7 @@
|
||||
var windowShiny2;
|
||||
function setShiny(windowShiny_) {
|
||||
windowShiny2 = windowShiny_;
|
||||
windowShiny2.version = "1.7.1.9002";
|
||||
windowShiny2.version = "1.6.0.9022";
|
||||
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
4
inst/www/shared/shiny.min.css
vendored
4
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
@@ -295,15 +295,6 @@ pre.shiny-text-output {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
/* Workaround for radio buttons and checkboxes not showing on Qt on Mac.
|
||||
This occurs in the RStudio IDE on macOS 11.5.
|
||||
https://github.com/rstudio/shiny/issues/3484
|
||||
*/
|
||||
.qtmac input[type="radio"],
|
||||
.qtmac input[type="checkbox"] {
|
||||
zoom: 1.0000001;
|
||||
}
|
||||
|
||||
/* consistency with bootstrap.css for selectize.js */
|
||||
.selectize-control {
|
||||
margin-bottom: 10px;
|
||||
|
||||
@@ -40,7 +40,7 @@ sense, namely:
|
||||
|
||||
In the example here, the \code{bindCache()} key consists of \code{input$x} and
|
||||
\code{input$y} combined, and the value is \code{input$x * input$y}. In this simple
|
||||
example, for any given key, there is only one possible returned value.\if{html}{\out{<div class="sourceCode NA">}}\preformatted{r <- reactive(\{ input$x * input$y \}) \%>\%
|
||||
example, for any given key, there is only one possible returned value.\if{html}{\out{<div class="NA">}}\preformatted{r <- reactive(\{ input$x * input$y \}) \%>\%
|
||||
bindCache(input$x, input$y)
|
||||
}\if{html}{\out{</div>}}
|
||||
|
||||
@@ -63,7 +63,7 @@ to do some sort of reduction on the data that still captures information
|
||||
about whether a value can be retrieved from the cache. For example, if you
|
||||
have a large data set with timestamps, it might make sense to extract the
|
||||
most recent timestamp and return that. Then, instead of hashing the entire
|
||||
data object, the cached reactive only needs to hash the timestamp.\if{html}{\out{<div class="sourceCode NA">}}\preformatted{r <- reactive(\{ compute(bigdata()) \} \%>\%
|
||||
data object, the cached reactive only needs to hash the timestamp.\if{html}{\out{<div class="NA">}}\preformatted{r <- reactive(\{ compute(bigdata()) \} \%>\%
|
||||
bindCache(\{ extract_most_recent_time(bigdata()) \})
|
||||
}\if{html}{\out{</div>}}
|
||||
|
||||
@@ -105,7 +105,7 @@ cache key is not too expensive.
|
||||
Remember that the key is \emph{reactive}, so it is not re-executed every single
|
||||
time that someone accesses the cached reactive. It is only re-executed if
|
||||
it has been invalidated by one of the reactives it depends on. For
|
||||
example, suppose we have this cached reactive:\if{html}{\out{<div class="sourceCode NA">}}\preformatted{r <- reactive(\{ input$x * input$y \}) \%>\%
|
||||
example, suppose we have this cached reactive:\if{html}{\out{<div class="NA">}}\preformatted{r <- reactive(\{ input$x * input$y \}) \%>\%
|
||||
bindCache(input$x, input$y)
|
||||
}\if{html}{\out{</div>}}
|
||||
|
||||
@@ -244,7 +244,7 @@ cache collisions, dealing with internal state that may be set by the,
|
||||
the cache.
|
||||
|
||||
You may need to provide a \code{cacheHint} to \code{\link[=createRenderFunction]{createRenderFunction()}} (or
|
||||
\code{htmlwidgets::shinyRenderWidget()}, if you've authored an htmlwidget) in
|
||||
\code{\link[htmlwidgets:htmlwidgets-shiny]{htmlwidgets::shinyRenderWidget()}}, if you've authored an htmlwidget) in
|
||||
order for \code{bindCache()} to correctly compute a cache key.
|
||||
|
||||
The potential problem is a cache collision. Consider the following:\preformatted{output$x1 <- renderText(\{ input$x \}) \%>\% bindCache(input$x)
|
||||
@@ -258,7 +258,7 @@ this, a \emph{cache hint} is automatically added when \code{\link[=renderText]{r
|
||||
\code{\link[=createRenderFunction]{createRenderFunction()}}. The cache hint is used as part of the actual
|
||||
cache key, in addition to the one passed to \code{bindCache()} by the user. The
|
||||
cache hint can be viewed by calling the internal Shiny function
|
||||
\code{extractCacheHint()}:\if{html}{\out{<div class="sourceCode NA">}}\preformatted{r <- renderText(\{ input$x \})
|
||||
\code{extractCacheHint()}:\if{html}{\out{<div class="NA">}}\preformatted{r <- renderText(\{ input$x \})
|
||||
shiny:::extractCacheHint(r)
|
||||
}\if{html}{\out{</div>}}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ user sets both \code{x} and \code{y}, and then clicks on an \link{actionButton}
|
||||
\code{go}.
|
||||
|
||||
To use both caching and events, the object should first be passed to
|
||||
\code{bindCache()}, then \code{bindEvent()}. For example:\if{html}{\out{<div class="sourceCode NA">}}\preformatted{r <- reactive(\{
|
||||
\code{bindCache()}, then \code{bindEvent()}. For example:\if{html}{\out{<div class="NA">}}\preformatted{r <- reactive(\{
|
||||
Sys.sleep(2) # Pretend this is an expensive computation
|
||||
input$x * input$y
|
||||
\}) \%>\%
|
||||
|
||||
@@ -14,7 +14,6 @@ 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,25 +1,18 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/bootstrap-layout.R, R/bootstrap.R
|
||||
\name{fluidPage}
|
||||
\alias{fluidPage}
|
||||
\alias{fixedPage}
|
||||
% Please edit documentation in R/bootstrap.R
|
||||
\name{bootstrapPage}
|
||||
\alias{bootstrapPage}
|
||||
\alias{basicPage}
|
||||
\title{Create a Bootstrap UI page container}
|
||||
\title{Create a Bootstrap page}
|
||||
\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{...}{UI elements (i.e., \link{tags}).}
|
||||
\item{...}{The contents of the document body.}
|
||||
|
||||
\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{title}{The browser window title (defaults to the host URL of the page)}
|
||||
|
||||
\item{theme}{One of the following:
|
||||
\itemize{
|
||||
@@ -28,74 +21,30 @@ Can also be set as a side effect of the \code{\link[=titlePanel]{titlePanel()}}
|
||||
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 \verb{<html>} tag, as in \verb{<html lang="en">}.
|
||||
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 definition (i.e., a \link{tags} object) that can be passed to \code{\link[=shinyApp]{shinyApp()}}.
|
||||
A UI defintion that can be passed to the \link{shinyUI} function.
|
||||
}
|
||||
\description{
|
||||
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}.
|
||||
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).
|
||||
}
|
||||
\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)))
|
||||
\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()}}.
|
||||
}
|
||||
|
||||
# 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
|
||||
)
|
||||
}
|
||||
|
||||
\note{
|
||||
The \code{basicPage} function is deprecated, you should use the
|
||||
\code{\link[=fluidPage]{fluidPage()}} function instead.
|
||||
}
|
||||
\seealso{
|
||||
\code{\link[=navbarPage]{navbarPage()}}, \code{\link[=fillPage]{fillPage()}}, \code{\link[=column]{column()}}, \code{\link[=tabPanel]{tabPanel()}}
|
||||
\code{\link[=fluidPage]{fluidPage()}}, \code{\link[=fixedPage]{fixedPage()}}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,26 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/bootstrap-layout.R
|
||||
\name{fluidRow}
|
||||
\alias{fluidRow}
|
||||
\alias{fixedRow}
|
||||
\name{column}
|
||||
\alias{column}
|
||||
\title{Responsive row-column based layout}
|
||||
\title{Create a column within a UI definition}
|
||||
\usage{
|
||||
fluidRow(...)
|
||||
|
||||
fixedRow(...)
|
||||
|
||||
column(width, ..., offset = 0)
|
||||
}
|
||||
\arguments{
|
||||
\item{...}{UI elements (i.e., \link{tags}). For \code{fluidRow()}, \code{...} should be a set of \code{column()}s.}
|
||||
\item{width}{The grid width of the column (must be between 1 and 12)}
|
||||
|
||||
\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{...}{Elements to include within the column}
|
||||
|
||||
\item{offset}{The number of columns to offset this column from the end of the
|
||||
previous column.}
|
||||
}
|
||||
\value{
|
||||
A UI element (i.e., \link{tags}).
|
||||
A column that can be included within a
|
||||
\code{\link[=fluidRow]{fluidRow()}} or \code{\link[=fixedRow]{fixedRow()}}.
|
||||
}
|
||||
\description{
|
||||
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.
|
||||
Create a column for use within a \code{\link[=fluidRow]{fluidRow()}} or
|
||||
\code{\link[=fixedRow]{fixedRow()}}
|
||||
}
|
||||
\examples{
|
||||
## Only run examples in interactive R sessions
|
||||
@@ -65,15 +46,21 @@ 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[=fluidPage]{fluidPage()}}
|
||||
\code{\link[=fluidRow]{fluidRow()}}, \code{\link[=fixedRow]{fixedRow()}}.
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ displays a message once every 8 hrs (by default)
|
||||
value and Developer message. This registration method allows package
|
||||
authors to write one message in a single location.
|
||||
|
||||
For example, the following Shiny Developer Mode options are registered:\if{html}{\out{<div class="sourceCode r">}}\preformatted{# Reload the Shiny app when a sourced R file changes
|
||||
For example, the following Shiny Developer Mode options are registered:\if{html}{\out{<div class="r">}}\preformatted{# Reload the Shiny app when a sourced R file changes
|
||||
register_devmode_option(
|
||||
"shiny.autoreload",
|
||||
"Turning on shiny autoreload. To disable, call `options(shiny.autoreload = FALSE)`",
|
||||
@@ -126,7 +126,7 @@ register_devmode_option(
|
||||
Other known, non-Shiny Developer Mode options:
|
||||
\itemize{
|
||||
\item Sass:
|
||||
}\if{html}{\out{<div class="sourceCode r">}}\preformatted{# Display the full stack trace when errors occur during Shiny app execution
|
||||
}\if{html}{\out{<div class="r">}}\preformatted{# Display the full stack trace when errors occur during Shiny app execution
|
||||
register_devmode_option(
|
||||
"sass.cache",
|
||||
"Turning off sass cache. To use default caching, call `options(sass.cache = TRUE)`",
|
||||
@@ -165,7 +165,7 @@ re-implementing these two functions:
|
||||
|
||||
This function should return \code{TRUE} if \code{getOption("shiny.devmode")} is set.
|
||||
In addition, we strongly recommend that it also checks to make sure
|
||||
\code{testthat} is not testing.\if{html}{\out{<div class="sourceCode r">}}\preformatted{in_devmode <- function() \{
|
||||
\code{testthat} is not testing.\if{html}{\out{<div class="r">}}\preformatted{in_devmode <- function() \{
|
||||
isTRUE(getOption("shiny.devmode", FALSE)) &&
|
||||
!identical(Sys.getenv("TESTTHAT"), "true")
|
||||
\}
|
||||
@@ -201,7 +201,7 @@ recommend displaying a message (\code{devmode_message}) to \code{stderr()} once
|
||||
hours using \code{\link[rlang:abort]{rlang::inform()}}. This will keep the author up to date as to
|
||||
which Dev Mode options are being altered. To allow developers a chance to
|
||||
disable Dev Mode messages, the message should be skipped if
|
||||
\code{getOption("shiny.devmode.verbose", TRUE)} is not \code{TRUE}.\if{html}{\out{<div class="sourceCode r">}}\preformatted{get_devmode_option <- function(name, default = NULL, devmode_default, devmode_message) \{
|
||||
\code{getOption("shiny.devmode.verbose", TRUE)} is not \code{TRUE}.\if{html}{\out{<div class="r">}}\preformatted{get_devmode_option <- function(name, default = NULL, devmode_default, devmode_message) \{
|
||||
if (!in_devmode()) \{
|
||||
# Dev Mode disabled, act like `getOption()`
|
||||
return(getOption(name, default = default))
|
||||
|
||||
@@ -37,11 +37,10 @@ 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 \verb{<html>} tag, as in \verb{<html lang="en">}.
|
||||
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.}
|
||||
}
|
||||
\description{
|
||||
@@ -102,7 +101,9 @@ fillPage(
|
||||
}
|
||||
\seealso{
|
||||
Other layout functions:
|
||||
\code{\link{fixedPage}()},
|
||||
\code{\link{flowLayout}()},
|
||||
\code{\link{fluidPage}()},
|
||||
\code{\link{navbarPage}()},
|
||||
\code{\link{sidebarLayout}()},
|
||||
\code{\link{splitLayout}()},
|
||||
|
||||
85
man/fixedPage.Rd
Normal file
85
man/fixedPage.Rd
Normal file
@@ -0,0 +1,85 @@
|
||||
% 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,6 +34,8 @@ 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
Normal file
119
man/fluidPage.Rd
Normal file
@@ -0,0 +1,119 @@
|
||||
% 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,7 +71,6 @@ 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
|
||||
@@ -80,7 +79,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 \verb{<html>} tag, as in \verb{<html lang="en">}.
|
||||
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.}
|
||||
|
||||
\item{menuName}{A name that identifies this \code{navbarMenu}. This
|
||||
@@ -125,7 +124,9 @@ 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}()}
|
||||
|
||||
@@ -51,9 +51,9 @@ registerInputHandler("mypackage.validint", function(x, shinysession, name) {
|
||||
})
|
||||
|
||||
## On the Javascript side, the associated input binding must have a corresponding getType method:
|
||||
# getType: function(el) {
|
||||
# return "mypackage.validint";
|
||||
# }
|
||||
getType: function(el) {
|
||||
return "mypackage.validint";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ shinyOptions(cache = cachem::cache_mem(max_size = 20e6, max_age = 3600))
|
||||
# At the top of app.R, this set the application-scoped cache to be a disk
|
||||
# cache that can be shared among multiple concurrent R processes, and is
|
||||
# deleted when the system reboots.
|
||||
shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache")))
|
||||
shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache"))
|
||||
|
||||
# At the top of app.R, this set the application-scoped cache to be a disk
|
||||
# cache that can be shared among multiple concurrent R processes, and
|
||||
|
||||
@@ -63,9 +63,7 @@ call to \code{dataTableOutput()} when \code{renderDataTable()} is used
|
||||
in an interactive R Markdown document.}
|
||||
}
|
||||
\description{
|
||||
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} Please use
|
||||
\href{https://rstudio.github.io/DT/shiny.html}{\code{DT::renderDataTable()}}.
|
||||
(Shiny 0.11.1)
|
||||
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} Please use \code{\link[DT:renderDataTable]{DT::renderDataTable()}}. (Shiny 0.11.1)
|
||||
|
||||
Makes a reactive version of the given function that returns a data frame (or
|
||||
matrix), which will be rendered with the \href{https://datatables.net}{DataTables}
|
||||
|
||||
@@ -62,10 +62,10 @@ respectively.
|
||||
\examples{
|
||||
## Only run this example in interactive R sessions
|
||||
if (interactive()) {
|
||||
runUrl('https://github.com/rstudio/shiny_example/archive/main.tar.gz')
|
||||
runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
|
||||
# Can run an app from a subdirectory in the archive
|
||||
runUrl("https://github.com/rstudio/shiny_example/archive/main.zip",
|
||||
runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
subdir = "inst/shinyapp/")
|
||||
}
|
||||
## Only run this example in interactive R sessions
|
||||
|
||||
@@ -83,7 +83,9 @@ 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,7 +64,9 @@ 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}()}
|
||||
|
||||
@@ -76,10 +76,10 @@ value when it is a single choice input and the empty string is not in the
|
||||
The resulting server \code{input} value will be returned as:
|
||||
\itemize{
|
||||
\item A symbol if \code{multiple = FALSE}. The \code{input} value should be
|
||||
used with rlang's \code{\link[rlang:nse-force]{rlang::!!()}}. For example,
|
||||
used with rlang's \code{\link[rlang:bang-bang]{rlang::!!()}}. For example,
|
||||
\code{ggplot2::aes(!!input$variable)}.
|
||||
\item A list of symbols if \code{multiple = TRUE}. The \code{input} value
|
||||
should be used with rlang's \code{\link[rlang:nse-force]{rlang::!!!()}} to expand
|
||||
should be used with rlang's \code{\link[rlang:bang-bang]{rlang::!!!()}} to expand
|
||||
the symbol list as individual arguments. For example,
|
||||
\code{dplyr::select(mtcars, !!!input$variabls)} which is
|
||||
equivalent to \code{dplyr::select(mtcars, !!input$variabls[[1]], !!input$variabls[[2]], ..., !!input$variabls[[length(input$variabls)]])}.
|
||||
|
||||
@@ -33,7 +33,9 @@ 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.1-alpha.9002",
|
||||
"version": "1.6.0-alpha.9022",
|
||||
"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 `main` branch or push to a Pull Request to the `main` branch, a GitHub Action will be run to make sure the bundled JavaScript code is up to date. If the source code does not compile to the exact same file, it will be committed an pushed back to the outdated branch. (This makes it so the full build tools are not necessary for small tweaks and comments. 🎉)
|
||||
On push to the `master` branch or push to a Pull Request to the `master` branch, a GitHub Action will be run to make sure the bundled JavaScript code is up to date. If the source code does not compile to the exact same file, it will be committed an pushed back to the outdated branch. (This makes it so the full build tools are not necessary for small tweaks and comments. 🎉)
|
||||
|
||||
<!-- #### Auto build and browser refresh
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ async function build(
|
||||
incremental: incremental,
|
||||
watch: watch,
|
||||
target: "es5",
|
||||
preserveSymlinks: true,
|
||||
...opts,
|
||||
}).then((x) => {
|
||||
onRebuild();
|
||||
|
||||
283
srcts/old/Gruntfile.js
Normal file
283
srcts/old/Gruntfile.js
Normal file
@@ -0,0 +1,283 @@
|
||||
module.exports = function(grunt) {
|
||||
|
||||
var instdir = '../inst/';
|
||||
var js_srcdir = '../srcjs/'
|
||||
|
||||
// Make sure all plattforms to use `\n` as eol char: https://stackoverflow.com/questions/29817511/grunt-issue-with-line-endings
|
||||
grunt.util.linefeed = "\n";
|
||||
|
||||
gruntConfig = {
|
||||
pkg: pkgInfo(),
|
||||
|
||||
clean: {
|
||||
options: { force: true },
|
||||
src: [
|
||||
instdir + "www/shared/shiny.js",
|
||||
instdir + "www/shared/shiny.js.map",
|
||||
instdir + "www/shared/shiny.min.js",
|
||||
instdir + "www/shared/shiny.min.js.map",
|
||||
"./temp_concat/shiny.js",
|
||||
"./temp_concat/shiny.js.map",
|
||||
instdir + "www/shared/datepicker/js/bootstrap-datepicker.min.js",
|
||||
instdir + "www/shared/ionrangeslider/js/ion.rangeSlider.min.js"
|
||||
]
|
||||
},
|
||||
|
||||
concat: {
|
||||
options: {
|
||||
process: function(src, filepath) {
|
||||
return (
|
||||
"//---------------------------------------------------------------------\n" +
|
||||
"// Source file: " +
|
||||
filepath +
|
||||
"\n\n" +
|
||||
src
|
||||
);
|
||||
},
|
||||
sourceMap: true
|
||||
},
|
||||
shiny: {
|
||||
src: [
|
||||
js_srcdir + "_start.js",
|
||||
js_srcdir + "utils.js",
|
||||
js_srcdir + "browser.js",
|
||||
js_srcdir + "input_rate.js",
|
||||
js_srcdir + "shinyapp.js",
|
||||
js_srcdir + "notifications.js",
|
||||
js_srcdir + "modal.js",
|
||||
js_srcdir + "file_processor.js",
|
||||
js_srcdir + "binding_registry.js",
|
||||
js_srcdir + "output_binding.js",
|
||||
js_srcdir + "output_binding_text.js",
|
||||
js_srcdir + "output_binding_image.js",
|
||||
js_srcdir + "output_binding_html.js",
|
||||
js_srcdir + "output_binding_downloadlink.js",
|
||||
js_srcdir + "output_binding_datatable.js",
|
||||
js_srcdir + "output_binding_adapter.js",
|
||||
js_srcdir + "input_binding.js",
|
||||
js_srcdir + "input_binding_text.js",
|
||||
js_srcdir + "input_binding_textarea.js",
|
||||
js_srcdir + "input_binding_password.js",
|
||||
js_srcdir + "input_binding_number.js",
|
||||
js_srcdir + "input_binding_checkbox.js",
|
||||
js_srcdir + "input_binding_slider.js",
|
||||
js_srcdir + "input_binding_date.js",
|
||||
js_srcdir + "input_binding_daterange.js",
|
||||
js_srcdir + "input_binding_select.js",
|
||||
js_srcdir + "input_binding_radio.js",
|
||||
js_srcdir + "input_binding_checkboxgroup.js",
|
||||
js_srcdir + "input_binding_actionbutton.js",
|
||||
js_srcdir + "input_binding_tabinput.js",
|
||||
js_srcdir + "input_binding_fileinput.js",
|
||||
js_srcdir + "init_shiny.js",
|
||||
js_srcdir + "reactlog.js",
|
||||
js_srcdir + "_end.js"
|
||||
],
|
||||
// The temp_concat/ directory would have gone under /srcjs/, but the
|
||||
// Babel Grunt plugin has trouble finding presets if it operates on a
|
||||
// file that's not under the current directory. So we'll put it under
|
||||
// ./
|
||||
dest: "./temp_concat/shiny.js",
|
||||
nonull: true
|
||||
}
|
||||
},
|
||||
|
||||
"string-replace": {
|
||||
version: {
|
||||
files: {
|
||||
"./temp_concat/shiny.js": "./temp_concat/shiny.js"
|
||||
},
|
||||
options: {
|
||||
replacements: [
|
||||
{
|
||||
pattern: /{{\s*VERSION\s*}}/g,
|
||||
replacement: pkgInfo().version
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
babel: {
|
||||
options: {
|
||||
sourceMap: true,
|
||||
compact: false,
|
||||
presets: ["@babel/preset-env"]
|
||||
},
|
||||
shiny: {
|
||||
src: "./temp_concat/shiny.js",
|
||||
dest: instdir + "/www/shared/shiny.js"
|
||||
}
|
||||
},
|
||||
|
||||
eslint: {
|
||||
options: {
|
||||
parser: "babel-eslint",
|
||||
format: require("eslint-stylish-mapped"),
|
||||
extends: "eslint:recommended",
|
||||
rules: {
|
||||
"consistent-return": 1,
|
||||
"dot-location": [1, "property"],
|
||||
eqeqeq: 1,
|
||||
// "no-shadow": 1,
|
||||
"no-implicit-globals": 1,
|
||||
"no-restricted-globals": [
|
||||
"error",
|
||||
"name",
|
||||
"length",
|
||||
"top",
|
||||
"location",
|
||||
"parent",
|
||||
"status"
|
||||
],
|
||||
"no-global-assign": 1,
|
||||
"no-undef": 1,
|
||||
"no-unused-vars": [1, { args: "none" }],
|
||||
"guard-for-in": 1,
|
||||
// "no-use-before-define": [1, {"functions": false}],
|
||||
semi: [1, "always"]
|
||||
},
|
||||
envs: ["es6", "browser", "jquery"],
|
||||
globals: ["strftime"]
|
||||
},
|
||||
shiny: ["./temp_concat/shiny.js"]
|
||||
},
|
||||
|
||||
uglify: {
|
||||
shiny: {
|
||||
options: {
|
||||
banner:
|
||||
"/*! <%= pkg.name %> <%= pkg.version %> | " +
|
||||
'(c) 2012-<%= grunt.template.today("yyyy") %> RStudio, Inc. | ' +
|
||||
"License: <%= pkg.license %> */\n",
|
||||
sourceMap: {
|
||||
includeSources: true
|
||||
},
|
||||
// Base the .min.js sourcemap off of the .js sourcemap created by concat
|
||||
sourceMapIn: instdir + "www/shared/shiny.js.map"
|
||||
},
|
||||
src: instdir + "www/shared/shiny.js",
|
||||
dest: instdir + "www/shared/shiny.min.js"
|
||||
},
|
||||
datepicker: {
|
||||
src: [
|
||||
instdir + "www/shared/datepicker/js/bootstrap-datepicker.js",
|
||||
instdir + "www/shared/datepicker/js/locales/bootstrap-datepicker.*.js"
|
||||
],
|
||||
dest: instdir + "www/shared/datepicker/js/bootstrap-datepicker.min.js"
|
||||
},
|
||||
ionrangeslider: {
|
||||
src: instdir + "www/shared/ionrangeslider/js/ion.rangeSlider.js",
|
||||
dest: instdir + "www/shared/ionrangeslider/js/ion.rangeSlider.min.js"
|
||||
},
|
||||
selectize: {
|
||||
src: instdir + "www/shared/selectize/accessibility/js/selectize-plugin-a11y.js",
|
||||
dest: instdir + "www/shared/selectize/accessibility/js/selectize-plugin-a11y.min.js"
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
shiny: {
|
||||
files: ["<%= concat.shiny.src %>", "../DESCRIPTION"],
|
||||
tasks: ["default"]
|
||||
},
|
||||
datepicker: {
|
||||
files: "<%= uglify.datepicker.src %>",
|
||||
tasks: ["uglify:datepicker"]
|
||||
}
|
||||
},
|
||||
|
||||
newer: {
|
||||
options: {
|
||||
override: function(detail, include) {
|
||||
// If DESCRIPTION is updated, we'll also need to re-minify shiny.js
|
||||
// because the min.js file embeds the version number.
|
||||
if (detail.task === "uglify" && detail.target === "shiny") {
|
||||
include(isNewer("../DESCRIPTION", detail.time));
|
||||
} else {
|
||||
include(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-string-replace');
|
||||
grunt.loadNpmTasks('grunt-babel');
|
||||
grunt.loadNpmTasks('grunt-eslint');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-newer');
|
||||
|
||||
// Need this here so that babel reads in the source map file after it's
|
||||
// generated. Without this task, it would read in the source map when Grunt
|
||||
// runs, which is wrong, if the source map doesn't exist, or is change later.
|
||||
grunt.task.registerTask("configureBabel", "configures babel options", function() {
|
||||
gruntConfig.babel.options.inputSourceMap = grunt.file.readJSON('./temp_concat/shiny.js.map');
|
||||
});
|
||||
|
||||
grunt.task.registerTask(
|
||||
"validateStringReplace",
|
||||
"tests to make sure the version value was replaced",
|
||||
function() {
|
||||
var shinyContent = require('fs').readFileSync('./temp_concat/shiny.js', 'utf8');
|
||||
if (/{{\s*VERSION\s*}}/.test(shinyContent)) {
|
||||
grunt.fail.fatal("{{ VERSION }} was not replaced in compiled shiny.js file!")
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
grunt.initConfig(gruntConfig);
|
||||
|
||||
grunt.registerTask('default', [
|
||||
'concat',
|
||||
'string-replace',
|
||||
'validateStringReplace',
|
||||
'eslint',
|
||||
'configureBabel',
|
||||
'babel',
|
||||
'uglify'
|
||||
]);
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Utility functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Return an object which merges information from package.json and the
|
||||
// DESCRIPTION file.
|
||||
function pkgInfo() {
|
||||
var pkg = grunt.file.readJSON('package.json');
|
||||
|
||||
pkg.name = descKeyValue('Package');
|
||||
pkg.version = descKeyValue('Version');
|
||||
pkg.license = descKeyValue('License');
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
// From the DESCRIPTION file, get the value of a key. This presently only
|
||||
// works if the value is on one line, the same line as the key.
|
||||
function descKeyValue(key) {
|
||||
var lines = require('fs').readFileSync('../DESCRIPTION', 'utf8').split(/\r?\n/);
|
||||
|
||||
var pattern = new RegExp('^' + key + ':');
|
||||
var txt = lines.filter(function(line) {
|
||||
return pattern.test(line);
|
||||
});
|
||||
|
||||
txt = txt[0];
|
||||
|
||||
pattern = new RegExp(key + ': *');
|
||||
txt = txt.replace(pattern, '');
|
||||
|
||||
return txt;
|
||||
}
|
||||
|
||||
// Return true if file's mtime is newer than mtime; false otherwise.
|
||||
function isNewer(file, mtime) {
|
||||
return require('fs').statSync(file).mtime > mtime;
|
||||
}
|
||||
};
|
||||
17
srcts/old/extras/shiny-autoreload.js
Normal file
17
srcts/old/extras/shiny-autoreload.js
Normal file
@@ -0,0 +1,17 @@
|
||||
(function() {
|
||||
var protocol = 'ws:';
|
||||
if (window.location.protocol === 'https:')
|
||||
protocol = 'wss:';
|
||||
|
||||
var defaultPath = window.location.pathname;
|
||||
if (!/\/$/.test(defaultPath))
|
||||
defaultPath += '/';
|
||||
defaultPath += 'autoreload/';
|
||||
|
||||
var ws = new WebSocket(protocol + '//' + window.location.host + defaultPath);
|
||||
ws.onmessage = function(event) {
|
||||
if (event.data === "autoreload") {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
})();
|
||||
87
srcts/old/extras/shiny-showcase.css
Normal file
87
srcts/old/extras/shiny-showcase.css
Normal file
@@ -0,0 +1,87 @@
|
||||
#showcase-well {
|
||||
border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
}
|
||||
|
||||
.shiny-code {
|
||||
background-color: white;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.shiny-code code {
|
||||
font-family: Menlo, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
.shiny-code-container {
|
||||
margin-top: 20px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.shiny-code-container h3 {
|
||||
display: inline;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.showcase-header {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.showcase-code-link {
|
||||
text-align: right;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#showcase-app-container {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#showcase-code-tabs pre {
|
||||
border: none;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
#showcase-code-tabs .nav,
|
||||
#showcase-code-tabs ul {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#showcase-app-code {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#showcase-code-tabs {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#showcase-code-tabs .tab-content {
|
||||
border-style: solid;
|
||||
border-color: #e5e5e5;
|
||||
border-width: 0px 1px 1px 1px;
|
||||
overflow:auto;
|
||||
-webkit-border-bottom-right-radius: 4px;
|
||||
-webkit-border-bottom-left-radius: 4px;
|
||||
-moz-border-radius-bottomright: 4px;
|
||||
-moz-border-radius-bottomleft: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
#showcase-code-position-toggle {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#showcase-sxs-code {
|
||||
padding-top: 20px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.showcase-code-license {
|
||||
display: block;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#showcase-code-content pre {
|
||||
background-color: white;
|
||||
}
|
||||
272
srcts/old/extras/shiny-showcase.js
Normal file
272
srcts/old/extras/shiny-showcase.js
Normal file
@@ -0,0 +1,272 @@
|
||||
/*jshint browser:true, jquery:true, strict:false, curly:false, indent:2*/
|
||||
|
||||
(function() {
|
||||
var animateMs = 400;
|
||||
|
||||
// Given a DOM node and a column (count of characters), walk recursively
|
||||
// through the node's siblings counting characters until the given number
|
||||
// of characters have been found.
|
||||
//
|
||||
// If the given count is bigger than the number of characters contained by
|
||||
// the node and its siblings, returns a null node and the number of
|
||||
// characters found.
|
||||
function findTextColPoint(node, col) {
|
||||
var cols = 0;
|
||||
if (node.nodeType === 3) {
|
||||
var nchar = node.nodeValue.replace(/\n/g, "").length;
|
||||
if (nchar >= col) {
|
||||
return { element: node, offset: col };
|
||||
} else {
|
||||
cols += nchar;
|
||||
}
|
||||
} else if (node.nodeType === 1 && node.firstChild) {
|
||||
var ret = findTextColPoint(node.firstChild, col);
|
||||
if (ret.element !== null) {
|
||||
return ret;
|
||||
} else {
|
||||
cols += ret.offset;
|
||||
}
|
||||
}
|
||||
if (node.nextSibling)
|
||||
return findTextColPoint(node.nextSibling, col - cols);
|
||||
else
|
||||
return { element: null, offset: cols };
|
||||
}
|
||||
|
||||
// Returns an object indicating the element containing the given line and
|
||||
// column of text, and the offset into that element where the text was found.
|
||||
//
|
||||
// If the given line and column are not found, returns a null element and
|
||||
// the number of lines found.
|
||||
function findTextPoint(el, line, col) {
|
||||
var newlines = 0;
|
||||
for (var childId = 0; childId < el.childNodes.length; childId++) {
|
||||
var child = el.childNodes[childId];
|
||||
// If this is a text node, count the number of newlines it contains.
|
||||
if (child.nodeType === 3) { // TEXT_NODE
|
||||
var newlinere = /\n/g;
|
||||
var match;
|
||||
while ((match = newlinere.exec(child.nodeValue)) !== null) {
|
||||
newlines++;
|
||||
// Found the desired line, now find the column.
|
||||
if (newlines === line) {
|
||||
return findTextColPoint(child, match.index + col + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If this is not a text node, descend recursively to see how many
|
||||
// lines it contains.
|
||||
else if (child.nodeType === 1) { // ELEMENT_NODE
|
||||
var ret = findTextPoint(child, line - newlines, col);
|
||||
if (ret.element !== null)
|
||||
return ret;
|
||||
else
|
||||
newlines += ret.offset;
|
||||
}
|
||||
}
|
||||
return { element: null, offset: newlines };
|
||||
}
|
||||
|
||||
// Draw a highlight effect for the given source ref. srcref is assumed to be
|
||||
// an integer array of length 6, following the standard R format for source
|
||||
// refs.
|
||||
function highlightSrcref (srcref, srcfile) {
|
||||
// Check to see if the browser supports text ranges (IE8 doesn't)
|
||||
if (!document.createRange)
|
||||
return;
|
||||
|
||||
// Check to see if we already have a marker for this source ref
|
||||
var el = document.getElementById("srcref_" + srcref);
|
||||
if (!el) {
|
||||
// We don't have a marker, create one
|
||||
el = document.createElement("span");
|
||||
el.id = "srcref_" + srcref;
|
||||
var ref = srcref;
|
||||
var code = document.getElementById(srcfile.replace(/\./g, "_") + "_code");
|
||||
// if there is no code file (might be a shiny file), quit early
|
||||
if (!code) return;
|
||||
var start = findTextPoint(code, ref[0], ref[4]);
|
||||
var end = findTextPoint(code, ref[2], ref[5]);
|
||||
|
||||
// If the insertion point can't be found, bail out now
|
||||
if (start.element === null || end.element === null)
|
||||
return;
|
||||
|
||||
var range = document.createRange();
|
||||
// If the text points are inside different <SPAN>s, we may not be able to
|
||||
// surround them without breaking apart the elements to keep the DOM tree
|
||||
// intact. Just move the selection points to encompass the contents of
|
||||
// the SPANs.
|
||||
if (start.element.parentNode.nodeName === "SPAN" &&
|
||||
start.element !== end.element) {
|
||||
range.setStartBefore(start.element.parentNode);
|
||||
} else {
|
||||
range.setStart(start.element, start.offset);
|
||||
}
|
||||
if (end.element.parentNode.nodeName === "SPAN" &&
|
||||
start.element !== end.element) {
|
||||
range.setEndAfter(end.element.parentNode);
|
||||
} else {
|
||||
range.setEnd(end.element, end.offset);
|
||||
}
|
||||
range.surroundContents(el);
|
||||
}
|
||||
// End any previous highlight before starting this one
|
||||
jQuery(el)
|
||||
.stop(true, true)
|
||||
.effect("highlight", null, 1600);
|
||||
}
|
||||
|
||||
// If this is the main Shiny window, wire up our custom message handler.
|
||||
if (window.Shiny) {
|
||||
Shiny.addCustomMessageHandler('showcase-src', function(message) {
|
||||
if (message.srcref && message.srcfile) {
|
||||
highlightSrcref(message.srcref, message.srcfile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var isCodeAbove = false;
|
||||
var setCodePosition = function(above, animate) {
|
||||
var animateCodeMs = animate ? animateMs : 1;
|
||||
|
||||
// set the source and targets for the tab move
|
||||
var newHostElement = above ?
|
||||
document.getElementById("showcase-sxs-code") :
|
||||
document.getElementById("showcase-code-inline");
|
||||
var currentHostElement = above ?
|
||||
document.getElementById("showcase-code-inline") :
|
||||
document.getElementById("showcase-sxs-code");
|
||||
|
||||
var metadataElement = document.getElementById("showcase-app-metadata");
|
||||
if (metadataElement === null) {
|
||||
// if there's no app metadata, show and hide the entire well container
|
||||
// when the code changes position
|
||||
var wellElement = $("#showcase-well");
|
||||
if (above) {
|
||||
wellElement.fadeOut(animateCodeMs);
|
||||
} else {
|
||||
wellElement.fadeIn(animateCodeMs);
|
||||
}
|
||||
}
|
||||
|
||||
// hide the new element before doing anything to it
|
||||
$(newHostElement).hide();
|
||||
$(currentHostElement).fadeOut(animateCodeMs, function() {
|
||||
var tabs = document.getElementById("showcase-code-tabs");
|
||||
currentHostElement.removeChild(tabs);
|
||||
newHostElement.appendChild(tabs);
|
||||
|
||||
// remove or set the height of the code
|
||||
if (above) {
|
||||
setCodeHeightFromDocHeight();
|
||||
} else {
|
||||
document.getElementById("showcase-code-content").removeAttribute("style");
|
||||
}
|
||||
|
||||
$(newHostElement).fadeIn(animateCodeMs);
|
||||
if (!above) {
|
||||
// remove the applied width and zoom on the app container, and
|
||||
// scroll smoothly down to the code's new home
|
||||
document.getElementById("showcase-app-container").removeAttribute("style");
|
||||
if (animate)
|
||||
$(document.body).animate({ scrollTop: $(newHostElement).offset().top });
|
||||
}
|
||||
// if there's a readme, move it either alongside the code or beneath
|
||||
// the app
|
||||
var readme = document.getElementById("readme-md");
|
||||
if (readme !== null) {
|
||||
readme.parentElement.removeChild(readme);
|
||||
if (above) {
|
||||
currentHostElement.appendChild(readme);
|
||||
$(currentHostElement).fadeIn(animateCodeMs);
|
||||
}
|
||||
else
|
||||
document.getElementById("showcase-app-metadata").appendChild(readme);
|
||||
}
|
||||
|
||||
// change the text on the toggle button to reflect the new state
|
||||
document.getElementById("showcase-code-position-toggle").innerHTML = above ?
|
||||
'<i class="fa fa-level-down"></i> show below' :
|
||||
'<i class="fa fa-level-up"></i> show with app';
|
||||
});
|
||||
if (above) {
|
||||
$(document.body).animate({ scrollTop: 0 }, animateCodeMs);
|
||||
}
|
||||
isCodeAbove = above;
|
||||
setAppCodeSxsWidths(above && animate);
|
||||
$(window).trigger("resize");
|
||||
};
|
||||
|
||||
var setAppCodeSxsWidths = function(animate) {
|
||||
var appTargetWidth = 960;
|
||||
var appWidth = appTargetWidth;
|
||||
var zoom = 1.0;
|
||||
var totalWidth = document.getElementById("showcase-app-code").offsetWidth;
|
||||
if (totalWidth / 2 > appTargetWidth) {
|
||||
// If the app can use only half the available space and still meet its
|
||||
// target, take half the available space.
|
||||
appWidth = totalWidth / 2;
|
||||
} else if (totalWidth * 0.66 > appTargetWidth) {
|
||||
// If the app can meet its target by taking up more space (up to 66%
|
||||
// of its container), take up more space.
|
||||
appWidth = 960;
|
||||
} else {
|
||||
// The space is too narrow for the app and code to live side-by-side
|
||||
// in a friendly way. Keep the app at 2/3 of the space but scale it.
|
||||
appWidth = totalWidth * 0.66;
|
||||
zoom = appWidth/appTargetWidth;
|
||||
}
|
||||
var app = document.getElementById("showcase-app-container");
|
||||
$(app).animate({
|
||||
width: appWidth + "px",
|
||||
zoom: (zoom*100) + "%"
|
||||
}, animate ? animateMs : 0);
|
||||
};
|
||||
|
||||
var toggleCodePosition = function() {
|
||||
setCodePosition(!isCodeAbove, true);
|
||||
};
|
||||
|
||||
// if the browser is sized to wider than 1350px, show the code next to the
|
||||
// app by default
|
||||
var setInitialCodePosition = function() {
|
||||
if (document.body.offsetWidth > 1350) {
|
||||
setCodePosition(true, false);
|
||||
}
|
||||
};
|
||||
|
||||
// make the code scrollable to about the height of the browser, less space
|
||||
// for the tabs
|
||||
var setCodeHeightFromDocHeight = function() {
|
||||
document.getElementById("showcase-code-content").style.height =
|
||||
$(window).height() + "px";
|
||||
};
|
||||
|
||||
// if there's a block of markdown content, render it to HTML
|
||||
var renderMarkdown = function() {
|
||||
var mdContent = document.getElementById("showcase-markdown-content");
|
||||
if (mdContent !== null) {
|
||||
// IE8 puts the content of <script> tags into innerHTML but
|
||||
// not innerText
|
||||
var content = mdContent.innerText || mdContent.innerHTML;
|
||||
document.getElementById("readme-md").innerHTML =
|
||||
(new Showdown.converter()).makeHtml(content)
|
||||
}
|
||||
}
|
||||
|
||||
$(window).resize(function() {
|
||||
if (isCodeAbove) {
|
||||
setAppCodeSxsWidths(false);
|
||||
setCodeHeightFromDocHeight();
|
||||
}
|
||||
});
|
||||
|
||||
window.toggleCodePosition = toggleCodePosition;
|
||||
|
||||
$(window).on("load", setInitialCodePosition);
|
||||
$(window).on("load", renderMarkdown);
|
||||
|
||||
if (window.hljs)
|
||||
hljs.initHighlightingOnLoad();
|
||||
})();
|
||||
8
srcts/old/extras/shiny-testmode.js
Normal file
8
srcts/old/extras/shiny-testmode.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Listen for messages from parent frame. This file is only added when the
|
||||
// shiny.testmode option is TRUE.
|
||||
window.addEventListener("message", function(e) {
|
||||
var message = e.data;
|
||||
|
||||
if (message.code)
|
||||
eval(message.code);
|
||||
});
|
||||
1
srcts/old/js/_end.js
Normal file
1
srcts/old/js/_end.js
Normal file
@@ -0,0 +1 @@
|
||||
})();
|
||||
17
srcts/old/js/_start.js
Normal file
17
srcts/old/js/_start.js
Normal file
@@ -0,0 +1,17 @@
|
||||
(function() {
|
||||
var $ = jQuery;
|
||||
|
||||
var exports = window.Shiny = window.Shiny || {};
|
||||
|
||||
exports.version = "{{ VERSION }}"; // Version number inserted by Grunt
|
||||
|
||||
var origPushState = window.history.pushState;
|
||||
window.history.pushState = function() {
|
||||
var result = origPushState.apply(this, arguments);
|
||||
$(document).trigger("pushstate");
|
||||
return result;
|
||||
};
|
||||
|
||||
$(document).on('submit', 'form:not([action])', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
37
srcts/old/js/binding_registry.js
Normal file
37
srcts/old/js/binding_registry.js
Normal file
@@ -0,0 +1,37 @@
|
||||
var BindingRegistry = function() {
|
||||
this.bindings = [];
|
||||
this.bindingNames = {};
|
||||
};
|
||||
(function() {
|
||||
this.register = function(binding, bindingName, priority) {
|
||||
var bindingObj = {binding: binding, priority: priority || 0};
|
||||
this.bindings.unshift(bindingObj);
|
||||
if (bindingName) {
|
||||
this.bindingNames[bindingName] = bindingObj;
|
||||
binding.name = bindingName;
|
||||
}
|
||||
};
|
||||
this.setPriority = function(bindingName, priority) {
|
||||
var bindingObj = this.bindingNames[bindingName];
|
||||
if (!bindingObj)
|
||||
throw "Tried to set priority on unknown binding " + bindingName;
|
||||
bindingObj.priority = priority || 0;
|
||||
};
|
||||
this.getPriority = function(bindingName) {
|
||||
var bindingObj = this.bindingNames[bindingName];
|
||||
if (!bindingObj)
|
||||
return false;
|
||||
return bindingObj.priority;
|
||||
};
|
||||
this.getBindings = function() {
|
||||
// Sort the bindings. The ones with higher priority are consulted
|
||||
// first; ties are broken by most-recently-registered.
|
||||
return mergeSort(this.bindings, function(a, b) {
|
||||
return b.priority - a.priority;
|
||||
});
|
||||
};
|
||||
}).call(BindingRegistry.prototype);
|
||||
|
||||
|
||||
var inputBindings = exports.inputBindings = new BindingRegistry();
|
||||
var outputBindings = exports.outputBindings = new BindingRegistry();
|
||||
41
srcts/old/js/browser.js
Normal file
41
srcts/old/js/browser.js
Normal file
@@ -0,0 +1,41 @@
|
||||
var browser = (function() {
|
||||
|
||||
var isQt = false;
|
||||
// For easy handling of Qt quirks using CSS
|
||||
if (/\bQt\//.test(window.navigator.userAgent)) {
|
||||
$(document.documentElement).addClass('qt');
|
||||
isQt = true;
|
||||
}
|
||||
|
||||
// Enable special treatment for Qt 5 quirks on Linux
|
||||
if (/\bQt\/5/.test(window.navigator.userAgent) &&
|
||||
/Linux/.test(window.navigator.userAgent)) {
|
||||
$(document.documentElement).addClass('qt5');
|
||||
}
|
||||
|
||||
// Detect IE and older (pre-Chromium) Edge
|
||||
var ua = window.navigator.userAgent;
|
||||
var isIE = /MSIE|Trident|Edge/.test(ua);
|
||||
|
||||
function getIEVersion() {
|
||||
var msie = ua.indexOf('MSIE ');
|
||||
if (isIE && msie > 0) {
|
||||
// IE 10 or older => return version number
|
||||
return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
|
||||
}
|
||||
var trident = ua.indexOf('Trident/');
|
||||
if (trident > 0) {
|
||||
// IE 11 => return version number
|
||||
var rv = ua.indexOf('rv:');
|
||||
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return {
|
||||
isQt: isQt,
|
||||
isIE: isIE,
|
||||
IEVersion: getIEVersion()
|
||||
};
|
||||
|
||||
})();
|
||||
79
srcts/old/js/file_processor.js
Normal file
79
srcts/old/js/file_processor.js
Normal file
@@ -0,0 +1,79 @@
|
||||
// Generic driver class for doing chunk-wise asynchronous processing of a
|
||||
// FileList object. Subclass/clone it and override the `on*` functions to
|
||||
// make it do something useful.
|
||||
var FileProcessor = function(files) {
|
||||
this.files = files;
|
||||
this.fileIndex = -1;
|
||||
// Currently need to use small chunk size because R-Websockets can't
|
||||
// handle continuation frames
|
||||
this.aborted = false;
|
||||
this.completed = false;
|
||||
|
||||
// TODO: Register error/abort callbacks
|
||||
|
||||
this.$run();
|
||||
};
|
||||
(function() {
|
||||
// Begin callbacks. Subclassers/cloners may override any or all of these.
|
||||
this.onBegin = function(files, cont) {
|
||||
setTimeout(cont, 0);
|
||||
};
|
||||
this.onFile = function(file, cont) {
|
||||
setTimeout(cont, 0);
|
||||
};
|
||||
this.onComplete = function() {
|
||||
};
|
||||
this.onAbort = function() {
|
||||
};
|
||||
// End callbacks
|
||||
|
||||
// Aborts processing, unless it's already completed
|
||||
this.abort = function() {
|
||||
if (this.completed || this.aborted)
|
||||
return;
|
||||
|
||||
this.aborted = true;
|
||||
this.onAbort();
|
||||
};
|
||||
|
||||
// Returns a bound function that will call this.$run one time.
|
||||
this.$getRun = function() {
|
||||
var self = this;
|
||||
var called = false;
|
||||
return function() {
|
||||
if (called)
|
||||
return;
|
||||
called = true;
|
||||
self.$run();
|
||||
};
|
||||
};
|
||||
|
||||
// This function will be called multiple times to advance the process.
|
||||
// It relies on the state of the object's fields to know what to do next.
|
||||
this.$run = function() {
|
||||
|
||||
if (this.aborted || this.completed)
|
||||
return;
|
||||
|
||||
if (this.fileIndex < 0) {
|
||||
// Haven't started yet--begin
|
||||
this.fileIndex = 0;
|
||||
this.onBegin(this.files, this.$getRun());
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.fileIndex === this.files.length) {
|
||||
// Just ended
|
||||
this.completed = true;
|
||||
this.onComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got here, then we have a file to process, or we are
|
||||
// in the middle of processing a file, or have just finished
|
||||
// processing a file.
|
||||
|
||||
var file = this.files[this.fileIndex++];
|
||||
this.onFile(file, this.$getRun());
|
||||
};
|
||||
}).call(FileProcessor.prototype);
|
||||
618
srcts/old/js/init_shiny.js
Normal file
618
srcts/old/js/init_shiny.js
Normal file
@@ -0,0 +1,618 @@
|
||||
function initShiny() {
|
||||
|
||||
var shinyapp = exports.shinyapp = new ShinyApp();
|
||||
|
||||
function bindOutputs(scope = document) {
|
||||
scope = $(scope);
|
||||
|
||||
var bindings = outputBindings.getBindings();
|
||||
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
var binding = bindings[i].binding;
|
||||
var matches = binding.find(scope) || [];
|
||||
for (var j = 0; j < matches.length; j++) {
|
||||
var el = matches[j];
|
||||
var id = binding.getId(el);
|
||||
|
||||
// Check if ID is falsy
|
||||
if (!id)
|
||||
continue;
|
||||
|
||||
// In some uncommon cases, elements that are later in the
|
||||
// matches array can be removed from the document by earlier
|
||||
// iterations. See https://github.com/rstudio/shiny/issues/1399
|
||||
if (!$.contains(document, el))
|
||||
continue;
|
||||
|
||||
var $el = $(el);
|
||||
if ($el.hasClass('shiny-bound-output')) {
|
||||
// Already bound; can happen with nested uiOutput (bindAll
|
||||
// gets called on two ancestors)
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this element reports its CSS styles to getCurrentOutputInfo()
|
||||
// then it should have a MutationObserver() to resend CSS if its
|
||||
// style/class attributes change. This observer should already exist
|
||||
// for _static_ UI, but not yet for _dynamic_ UI
|
||||
maybeAddThemeObserver(el);
|
||||
|
||||
var bindingAdapter = new OutputBindingAdapter(el, binding);
|
||||
shinyapp.bindOutput(id, bindingAdapter);
|
||||
$el.data('shiny-output-binding', bindingAdapter);
|
||||
$el.addClass('shiny-bound-output');
|
||||
if (!$el.attr("aria-live")) $el.attr("aria-live", "polite");
|
||||
$el.trigger({
|
||||
type: 'shiny:bound',
|
||||
binding: binding,
|
||||
bindingType: 'output'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Send later in case DOM layout isn't final yet.
|
||||
setTimeout(sendImageSize, 0);
|
||||
setTimeout(sendOutputHiddenState, 0);
|
||||
}
|
||||
|
||||
function unbindOutputs(scope = document, includeSelf = false) {
|
||||
var outputs = $(scope).find('.shiny-bound-output');
|
||||
|
||||
if (includeSelf && $(scope).hasClass('shiny-bound-output')) {
|
||||
outputs.push(scope);
|
||||
}
|
||||
|
||||
for (var i = 0; i < outputs.length; i++) {
|
||||
var $el = $(outputs[i]);
|
||||
var bindingAdapter = $el.data('shiny-output-binding');
|
||||
if (!bindingAdapter)
|
||||
continue;
|
||||
var id = bindingAdapter.binding.getId(outputs[i]);
|
||||
shinyapp.unbindOutput(id, bindingAdapter);
|
||||
$el.removeClass('shiny-bound-output');
|
||||
$el.removeData('shiny-output-binding');
|
||||
$el.trigger({
|
||||
type: 'shiny:unbound',
|
||||
binding: bindingAdapter.binding,
|
||||
bindingType: 'output'
|
||||
});
|
||||
}
|
||||
|
||||
// Send later in case DOM layout isn't final yet.
|
||||
setTimeout(sendImageSize, 0);
|
||||
setTimeout(sendOutputHiddenState, 0);
|
||||
}
|
||||
|
||||
var inputBatchSender = new InputBatchSender(shinyapp);
|
||||
var inputsNoResend = new InputNoResendDecorator(inputBatchSender);
|
||||
var inputsEvent = new InputEventDecorator(inputsNoResend);
|
||||
var inputsRate = new InputRateDecorator(inputsEvent);
|
||||
var inputsDefer = new InputDeferDecorator(inputsEvent);
|
||||
|
||||
var inputs;
|
||||
if ($('input[type="submit"], button[type="submit"]').length > 0) {
|
||||
// If there is a submit button on the page, use defer decorator
|
||||
inputs = inputsDefer;
|
||||
|
||||
$('input[type="submit"], button[type="submit"]').each(function() {
|
||||
$(this).click(function(event) {
|
||||
event.preventDefault();
|
||||
inputsDefer.submit();
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
// By default, use rate decorator
|
||||
inputs = inputsRate;
|
||||
}
|
||||
|
||||
inputs = new InputValidateDecorator(inputs);
|
||||
|
||||
exports.setInputValue = exports.onInputChange = function(name, value, opts) {
|
||||
opts = addDefaultInputOpts(opts);
|
||||
inputs.setInput(name, value, opts);
|
||||
};
|
||||
|
||||
// By default, Shiny deduplicates input value changes; that is, if
|
||||
// `setInputValue` is called with the same value as the input already
|
||||
// has, the call is ignored (unless opts.priority = "event"). Calling
|
||||
// `forgetLastInputValue` tells Shiny that the very next call to
|
||||
// `setInputValue` for this input id shouldn't be ignored, even if it
|
||||
// is a dupe of the existing value.
|
||||
exports.forgetLastInputValue = function(name) {
|
||||
inputsNoResend.forget(name);
|
||||
};
|
||||
|
||||
var boundInputs = {};
|
||||
|
||||
function valueChangeCallback(binding, el, allowDeferred) {
|
||||
var id = binding.getId(el);
|
||||
if (id) {
|
||||
var value = binding.getValue(el);
|
||||
var type = binding.getType(el);
|
||||
if (type)
|
||||
id = id + ":" + type;
|
||||
|
||||
let opts = {
|
||||
priority: allowDeferred ? "deferred" : "immediate",
|
||||
binding: binding,
|
||||
el: el
|
||||
};
|
||||
inputs.setInput(id, value, opts);
|
||||
}
|
||||
}
|
||||
|
||||
function bindInputs(scope = document) {
|
||||
var bindings = inputBindings.getBindings();
|
||||
|
||||
var inputItems = {};
|
||||
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
var binding = bindings[i].binding;
|
||||
var matches = binding.find(scope) || [];
|
||||
for (var j = 0; j < matches.length; j++) {
|
||||
var el = matches[j];
|
||||
var id = binding.getId(el);
|
||||
|
||||
// Check if ID is falsy, or if already bound
|
||||
if (!id || boundInputs[id])
|
||||
continue;
|
||||
|
||||
var type = binding.getType(el);
|
||||
var effectiveId = type ? id + ":" + type : id;
|
||||
inputItems[effectiveId] = {
|
||||
value: binding.getValue(el),
|
||||
opts: {
|
||||
immediate: true,
|
||||
binding: binding,
|
||||
el: el
|
||||
}
|
||||
};
|
||||
|
||||
/*jshint loopfunc:true*/
|
||||
var thisCallback = (function() {
|
||||
var thisBinding = binding;
|
||||
var thisEl = el;
|
||||
return function(allowDeferred) {
|
||||
valueChangeCallback(thisBinding, thisEl, allowDeferred);
|
||||
};
|
||||
})();
|
||||
|
||||
binding.subscribe(el, thisCallback);
|
||||
$(el).data('shiny-input-binding', binding);
|
||||
$(el).addClass('shiny-bound-input');
|
||||
var ratePolicy = binding.getRatePolicy(el);
|
||||
if (ratePolicy !== null) {
|
||||
inputsRate.setRatePolicy(
|
||||
effectiveId,
|
||||
ratePolicy.policy,
|
||||
ratePolicy.delay);
|
||||
}
|
||||
|
||||
boundInputs[id] = {
|
||||
binding: binding,
|
||||
node: el
|
||||
};
|
||||
|
||||
$(el).trigger({
|
||||
type: 'shiny:bound',
|
||||
binding: binding,
|
||||
bindingType: 'input'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return inputItems;
|
||||
}
|
||||
|
||||
function unbindInputs(scope = document, includeSelf = false) {
|
||||
var inputs = $(scope).find('.shiny-bound-input');
|
||||
|
||||
if (includeSelf && $(scope).hasClass('shiny-bound-input')) {
|
||||
inputs.push(scope);
|
||||
}
|
||||
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
var el = inputs[i];
|
||||
var binding = $(el).data('shiny-input-binding');
|
||||
if (!binding)
|
||||
continue;
|
||||
var id = binding.getId(el);
|
||||
$(el).removeClass('shiny-bound-input');
|
||||
delete boundInputs[id];
|
||||
binding.unsubscribe(el);
|
||||
$(el).trigger({
|
||||
type: 'shiny:unbound',
|
||||
binding: binding,
|
||||
bindingType: 'input'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _bindAll(scope) {
|
||||
bindOutputs(scope);
|
||||
return bindInputs(scope);
|
||||
}
|
||||
function unbindAll(scope, includeSelf = false) {
|
||||
unbindInputs(scope, includeSelf);
|
||||
unbindOutputs(scope, includeSelf);
|
||||
}
|
||||
exports.bindAll = function(scope) {
|
||||
// _bindAll returns input values; it doesn't send them to the server.
|
||||
// export.bindAll needs to send the values to the server.
|
||||
var currentInputItems = _bindAll(scope);
|
||||
$.each(currentInputItems, function(name, item) {
|
||||
inputs.setInput(name, item.value, item.opts);
|
||||
});
|
||||
|
||||
// Not sure if the iframe stuff is an intrinsic part of bindAll, but bindAll
|
||||
// is a convenient place to hang it. bindAll will be called anytime new HTML
|
||||
// appears that might contain inputs/outputs; it's reasonable to assume that
|
||||
// any such HTML may contain iframes as well.
|
||||
initDeferredIframes();
|
||||
};
|
||||
exports.unbindAll = unbindAll;
|
||||
|
||||
// Calls .initialize() for all of the input objects in all input bindings,
|
||||
// in the given scope.
|
||||
function initializeInputs(scope = document) {
|
||||
var bindings = inputBindings.getBindings();
|
||||
|
||||
// Iterate over all bindings
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
var binding = bindings[i].binding;
|
||||
var inputObjects = binding.find(scope) || [];
|
||||
|
||||
// Iterate over all input objects for this binding
|
||||
for (var j = 0; j < inputObjects.length; j++) {
|
||||
if (!inputObjects[j]._shiny_initialized) {
|
||||
inputObjects[j]._shiny_initialized = true;
|
||||
binding.initialize(inputObjects[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.initializeInputs = initializeInputs;
|
||||
|
||||
function getIdFromEl(el) {
|
||||
var $el = $(el);
|
||||
var bindingAdapter = $el.data("shiny-output-binding");
|
||||
if (!bindingAdapter)
|
||||
return null;
|
||||
else
|
||||
return bindingAdapter.getId();
|
||||
}
|
||||
|
||||
|
||||
// Initialize all input objects in the document, before binding
|
||||
initializeInputs(document);
|
||||
|
||||
// The input values returned by _bindAll() each have a structure like this:
|
||||
// { value: 123, opts: { ... } }
|
||||
// We want to only keep the value. This is because when the initialValues is
|
||||
// passed to ShinyApp.connect(), the ShinyApp object stores the
|
||||
// initialValues object for the duration of the session, and the opts may
|
||||
// have a reference to the DOM element, which would prevent it from being
|
||||
// GC'd.
|
||||
var initialValues = mapValues(_bindAll(document), x => x.value);
|
||||
|
||||
// The server needs to know the size of each image and plot output element,
|
||||
// in case it is auto-sizing
|
||||
$('.shiny-image-output, .shiny-plot-output, .shiny-report-size').each(function() {
|
||||
var id = getIdFromEl(this);
|
||||
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
|
||||
initialValues['.clientdata_output_' + id + '_width'] = this.offsetWidth;
|
||||
initialValues['.clientdata_output_' + id + '_height'] = this.offsetHeight;
|
||||
}
|
||||
});
|
||||
|
||||
function getComputedBgColor(el) {
|
||||
if (!el) {
|
||||
// Top of document, can't recurse further
|
||||
return null;
|
||||
}
|
||||
|
||||
let bgColor = getStyle(el, "background-color");
|
||||
let m = bgColor.match(/^rgba\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/);
|
||||
if (bgColor === "transparent" || (m && parseFloat(m[4]) === 0)) {
|
||||
// No background color on this element. See if it has a background image.
|
||||
let bgImage = getStyle(el, "background-image");
|
||||
if (bgImage && bgImage !== "none") {
|
||||
// Failed to detect background color, since it has a background image
|
||||
return null;
|
||||
} else {
|
||||
// Recurse
|
||||
return getComputedBgColor(el.parentElement);
|
||||
}
|
||||
}
|
||||
return bgColor;
|
||||
}
|
||||
|
||||
function getComputedFont(el) {
|
||||
let fontFamily = getStyle(el, "font-family");
|
||||
let fontSize = getStyle(el, "font-size");
|
||||
return {
|
||||
families: fontFamily.replace(/"/g, '').split(", "),
|
||||
size: fontSize
|
||||
};
|
||||
}
|
||||
|
||||
$('.shiny-image-output, .shiny-plot-output, .shiny-report-theme').each(function() {
|
||||
var el = this, id = getIdFromEl(el);
|
||||
initialValues['.clientdata_output_' + id + '_bg'] = getComputedBgColor(el);
|
||||
initialValues['.clientdata_output_' + id + '_fg'] = getStyle(el, "color");
|
||||
initialValues['.clientdata_output_' + id + '_accent'] = getComputedLinkColor(el);
|
||||
initialValues['.clientdata_output_' + id + '_font'] = getComputedFont(el);
|
||||
maybeAddThemeObserver(el);
|
||||
});
|
||||
|
||||
|
||||
// Resend computed styles if *an output element's* class or style attribute changes.
|
||||
// This gives us some level of confidence that getCurrentOutputInfo() will be
|
||||
// properly invalidated if output container is mutated; but unfortunately,
|
||||
// we don't have a reasonable way to detect change in *inherited* styles
|
||||
// (other than session$setCurrentTheme())
|
||||
// https://github.com/rstudio/shiny/issues/3196
|
||||
// https://github.com/rstudio/shiny/issues/2998
|
||||
function maybeAddThemeObserver(el) {
|
||||
if (!window.MutationObserver) {
|
||||
return; // IE10 and lower
|
||||
}
|
||||
|
||||
var cl = el.classList;
|
||||
var reportTheme = cl.contains('shiny-image-output') || cl.contains('shiny-plot-output') || cl.contains('shiny-report-theme');
|
||||
if (!reportTheme) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $el = $(el);
|
||||
if ($el.data("shiny-theme-observer")) {
|
||||
return; // i.e., observer is already observing
|
||||
}
|
||||
|
||||
var observerCallback = new Debouncer(null, () => doSendTheme(el), 100);
|
||||
var observer = new MutationObserver(() => observerCallback.normalCall());
|
||||
var config = {attributes: true, attributeFilter: ['style', 'class']};
|
||||
observer.observe(el, config);
|
||||
$el.data("shiny-theme-observer", observer);
|
||||
}
|
||||
|
||||
function doSendTheme(el) {
|
||||
// Sending theme info on error isn't necessary (it'd add an unnecessary additional round-trip)
|
||||
if (el.classList.contains("shiny-output-error")) {
|
||||
return;
|
||||
}
|
||||
var id = getIdFromEl(el);
|
||||
inputs.setInput('.clientdata_output_' + id + '_bg', getComputedBgColor(el));
|
||||
inputs.setInput('.clientdata_output_' + id + '_fg', getStyle(el, "color"));
|
||||
inputs.setInput('.clientdata_output_' + id + '_accent', getComputedLinkColor(el));
|
||||
inputs.setInput('.clientdata_output_' + id + '_font', getComputedFont(el));
|
||||
}
|
||||
|
||||
function doSendImageSize() {
|
||||
$('.shiny-image-output, .shiny-plot-output, .shiny-report-size').each(function() {
|
||||
var id = getIdFromEl(this);
|
||||
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
|
||||
inputs.setInput('.clientdata_output_' + id + '_width', this.offsetWidth);
|
||||
inputs.setInput('.clientdata_output_' + id + '_height', this.offsetHeight);
|
||||
}
|
||||
});
|
||||
|
||||
$('.shiny-image-output, .shiny-plot-output, .shiny-report-theme').each(function() {
|
||||
doSendTheme(this);
|
||||
});
|
||||
|
||||
$('.shiny-bound-output').each(function() {
|
||||
var $this = $(this), binding = $this.data('shiny-output-binding');
|
||||
$this.trigger({
|
||||
type: 'shiny:visualchange',
|
||||
visible: !isHidden(this),
|
||||
binding: binding
|
||||
});
|
||||
binding.onResize();
|
||||
});
|
||||
}
|
||||
var sendImageSizeDebouncer = new Debouncer(null, doSendImageSize, 0);
|
||||
function sendImageSize() {
|
||||
sendImageSizeDebouncer.normalCall();
|
||||
}
|
||||
// Make sure sendImageSize actually gets called before the inputBatchSender
|
||||
// sends data to the server.
|
||||
inputBatchSender.lastChanceCallback.push(function() {
|
||||
if (sendImageSizeDebouncer.isPending())
|
||||
sendImageSizeDebouncer.immediateCall();
|
||||
});
|
||||
|
||||
// Return true if the object or one of its ancestors in the DOM tree has
|
||||
// style='display:none'; otherwise return false.
|
||||
function isHidden(obj) {
|
||||
// null means we've hit the top of the tree. If width or height is
|
||||
// non-zero, then we know that no ancestor has display:none.
|
||||
if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) {
|
||||
return false;
|
||||
} else if (getStyle(obj, 'display') === 'none') {
|
||||
return true;
|
||||
} else {
|
||||
return(isHidden(obj.parentNode));
|
||||
}
|
||||
}
|
||||
var lastKnownVisibleOutputs = {};
|
||||
// Set initial state of outputs to hidden, if needed
|
||||
$('.shiny-bound-output').each(function() {
|
||||
var id = getIdFromEl(this);
|
||||
if (isHidden(this)) {
|
||||
initialValues['.clientdata_output_' + id + '_hidden'] = true;
|
||||
} else {
|
||||
lastKnownVisibleOutputs[id] = true;
|
||||
initialValues['.clientdata_output_' + id + '_hidden'] = false;
|
||||
}
|
||||
});
|
||||
// Send update when hidden state changes
|
||||
function doSendOutputHiddenState() {
|
||||
var visibleOutputs = {};
|
||||
$('.shiny-bound-output').each(function() {
|
||||
var id = getIdFromEl(this);
|
||||
delete lastKnownVisibleOutputs[id];
|
||||
// Assume that the object is hidden when width and height are 0
|
||||
var hidden = isHidden(this), evt = {
|
||||
type: 'shiny:visualchange',
|
||||
visible: !hidden
|
||||
};
|
||||
if (hidden) {
|
||||
inputs.setInput('.clientdata_output_' + id + '_hidden', true);
|
||||
} else {
|
||||
visibleOutputs[id] = true;
|
||||
inputs.setInput('.clientdata_output_' + id + '_hidden', false);
|
||||
}
|
||||
var $this = $(this);
|
||||
evt.binding = $this.data('shiny-output-binding');
|
||||
$this.trigger(evt);
|
||||
});
|
||||
// Anything left in lastKnownVisibleOutputs is orphaned
|
||||
for (var name in lastKnownVisibleOutputs) {
|
||||
if (lastKnownVisibleOutputs.hasOwnProperty(name))
|
||||
inputs.setInput('.clientdata_output_' + name + '_hidden', true);
|
||||
}
|
||||
// Update the visible outputs for next time
|
||||
lastKnownVisibleOutputs = visibleOutputs;
|
||||
}
|
||||
// sendOutputHiddenState gets called each time DOM elements are shown or
|
||||
// hidden. This can be in the hundreds or thousands of times at startup.
|
||||
// We'll debounce it, so that we do the actual work once per tick.
|
||||
var sendOutputHiddenStateDebouncer = new Debouncer(null, doSendOutputHiddenState, 0);
|
||||
function sendOutputHiddenState() {
|
||||
sendOutputHiddenStateDebouncer.normalCall();
|
||||
}
|
||||
// We need to make sure doSendOutputHiddenState actually gets called before
|
||||
// the inputBatchSender sends data to the server. The lastChanceCallback
|
||||
// here does that - if the debouncer has a pending call, flush it.
|
||||
inputBatchSender.lastChanceCallback.push(function() {
|
||||
if (sendOutputHiddenStateDebouncer.isPending())
|
||||
sendOutputHiddenStateDebouncer.immediateCall();
|
||||
});
|
||||
|
||||
// Given a namespace and a handler function, return a function that invokes
|
||||
// the handler only when e's namespace matches. For example, if the
|
||||
// namespace is "bs", it would match when e.namespace is "bs" or "bs.tab".
|
||||
// If the namespace is "bs.tab", it would match for "bs.tab", but not "bs".
|
||||
function filterEventsByNamespace(namespace, handler) {
|
||||
namespace = namespace.split(".");
|
||||
|
||||
return function(e) {
|
||||
var eventNamespace = e.namespace.split(".");
|
||||
|
||||
// If any of the namespace strings aren't present in this event, quit.
|
||||
for (var i=0; i<namespace.length; i++) {
|
||||
if (eventNamespace.indexOf(namespace[i]) === -1)
|
||||
return;
|
||||
}
|
||||
|
||||
handler.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
// The size of each image may change either because the browser window was
|
||||
// resized, or because a tab was shown/hidden (hidden elements report size
|
||||
// of 0x0). It's OK to over-report sizes because the input pipeline will
|
||||
// filter out values that haven't changed.
|
||||
$(window).resize(debounce(500, sendImageSize));
|
||||
// Need to register callbacks for each Bootstrap 3 class.
|
||||
var bs3classes = ['modal', 'dropdown', 'tab', 'tooltip', 'popover', 'collapse'];
|
||||
$.each(bs3classes, function(idx, classname) {
|
||||
$(document.body).on('shown.bs.' + classname + '.sendImageSize', '*',
|
||||
filterEventsByNamespace('bs', sendImageSize));
|
||||
$(document.body).on('shown.bs.' + classname + '.sendOutputHiddenState ' +
|
||||
'hidden.bs.' + classname + '.sendOutputHiddenState',
|
||||
'*', filterEventsByNamespace('bs', sendOutputHiddenState));
|
||||
});
|
||||
|
||||
// This is needed for Bootstrap 2 compatibility and for non-Bootstrap
|
||||
// related shown/hidden events (like conditionalPanel)
|
||||
$(document.body).on('shown.sendImageSize', '*', sendImageSize);
|
||||
$(document.body).on('shown.sendOutputHiddenState hidden.sendOutputHiddenState', '*',
|
||||
sendOutputHiddenState);
|
||||
|
||||
// Send initial pixel ratio, and update it if it changes
|
||||
initialValues['.clientdata_pixelratio'] = pixelRatio();
|
||||
$(window).resize(function() {
|
||||
inputs.setInput('.clientdata_pixelratio', pixelRatio());
|
||||
});
|
||||
|
||||
// Send initial URL
|
||||
initialValues['.clientdata_url_protocol'] = window.location.protocol;
|
||||
initialValues['.clientdata_url_hostname'] = window.location.hostname;
|
||||
initialValues['.clientdata_url_port'] = window.location.port;
|
||||
initialValues['.clientdata_url_pathname'] = window.location.pathname;
|
||||
|
||||
// Send initial URL search (query string) and update it if it changes
|
||||
initialValues['.clientdata_url_search'] = window.location.search;
|
||||
|
||||
$(window).on('pushstate', function(e) {
|
||||
inputs.setInput('.clientdata_url_search', window.location.search);
|
||||
});
|
||||
|
||||
$(window).on('popstate', function(e) {
|
||||
inputs.setInput('.clientdata_url_search', window.location.search);
|
||||
});
|
||||
|
||||
// This is only the initial value of the hash. The hash can change, but
|
||||
// a reactive version of this isn't sent because watching for changes can
|
||||
// require polling on some browsers. The JQuery hashchange plugin can be
|
||||
// used if this capability is important.
|
||||
initialValues['.clientdata_url_hash_initial'] = window.location.hash;
|
||||
initialValues['.clientdata_url_hash'] = window.location.hash;
|
||||
|
||||
$(window).on('hashchange', function(e) {
|
||||
inputs.setInput('.clientdata_url_hash', window.location.hash);
|
||||
});
|
||||
|
||||
// The server needs to know what singletons were rendered as part of
|
||||
// the page loading
|
||||
var singletonText = initialValues['.clientdata_singletons'] =
|
||||
$('script[type="application/shiny-singletons"]').text();
|
||||
singletons.registerNames(singletonText.split(/,/));
|
||||
|
||||
var dependencyText = $('script[type="application/html-dependencies"]').text();
|
||||
$.each(dependencyText.split(/;/), function(i, depStr) {
|
||||
var match = /\s*^(.+)\[(.+)\]\s*$/.exec(depStr);
|
||||
if (match) {
|
||||
registerDependency(match[1], match[2]);
|
||||
}
|
||||
});
|
||||
|
||||
// We've collected all the initial values--start the server process!
|
||||
inputsNoResend.reset(initialValues);
|
||||
shinyapp.connect(initialValues);
|
||||
$(document).one("shiny:connected", function() {
|
||||
initDeferredIframes();
|
||||
});
|
||||
|
||||
} // function initShiny()
|
||||
|
||||
|
||||
// Give any deferred iframes a chance to load.
|
||||
function initDeferredIframes() {
|
||||
if (!window.Shiny || !window.Shiny.shinyapp || !window.Shiny.shinyapp.isConnected()) {
|
||||
// If somehow we accidentally call this before the server connection is
|
||||
// established, just ignore the call. At the time of this writing it
|
||||
// doesn't happen, but it's easy to imagine a later refactoring putting
|
||||
// us in this situation and it'd be hard to notice with either manual
|
||||
// testing or automated tests, because the only effect is on HTTP request
|
||||
// timing. (Update: Actually Aron saw this being called without even
|
||||
// window.Shiny being defined, but it was hard to repro.)
|
||||
return;
|
||||
}
|
||||
|
||||
$(".shiny-frame-deferred").each(function (i, el) {
|
||||
var $el = $(el);
|
||||
$el.removeClass("shiny-frame-deferred");
|
||||
$el.attr("src", $el.attr("data-deferred-src"));
|
||||
$el.attr("data-deferred-src", null);
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
// Init Shiny a little later than document ready, so user code can
|
||||
// run first (i.e. to register bindings)
|
||||
setTimeout(initShiny, 1);
|
||||
});
|
||||
44
srcts/old/js/input_binding.js
Normal file
44
srcts/old/js/input_binding.js
Normal file
@@ -0,0 +1,44 @@
|
||||
var InputBinding = exports.InputBinding = function() {};
|
||||
|
||||
(function() {
|
||||
|
||||
// Returns a jQuery object or element array that contains the
|
||||
// descendants of scope that match this binding
|
||||
this.find = function(scope) { throw "Not implemented"; };
|
||||
|
||||
this.getId = function(el) {
|
||||
return el['data-input-id'] || el.id;
|
||||
};
|
||||
|
||||
// Gives the input a type in case the server needs to know it
|
||||
// to deserialize the JSON correctly
|
||||
this.getType = function() { return false; };
|
||||
this.getValue = function(el) { throw "Not implemented"; };
|
||||
|
||||
// The callback method takes one argument, whose value is boolean. If true,
|
||||
// allow deferred (debounce or throttle) sending depending on the value of
|
||||
// getRatePolicy. If false, send value immediately.
|
||||
this.subscribe = function(el, callback) { };
|
||||
this.unsubscribe = function(el) { };
|
||||
|
||||
// This is used for receiving messages that tell the input object to do
|
||||
// things, such as setting values (including min, max, and others).
|
||||
// 'data' should be an object with elements corresponding to value, min,
|
||||
// max, etc., as appropriate for the type of input object. It also should
|
||||
// trigger a change event.
|
||||
this.receiveMessage = function(el, data) { throw "Not implemented"; };
|
||||
this.getState = function(el, data) { throw "Not implemented"; };
|
||||
|
||||
this.getRatePolicy = function() { return null; };
|
||||
|
||||
// Some input objects need initialization before being bound. This is
|
||||
// called when the document is ready (for statically-added input objects),
|
||||
// and when new input objects are added to the document with
|
||||
// htmlOutputBinding.renderValue() (for dynamically-added input objects).
|
||||
// This is called before the input is bound.
|
||||
this.initialize = function(el) { };
|
||||
|
||||
// This is called after unbinding the output.
|
||||
this.dispose = function(el) { };
|
||||
|
||||
}).call(InputBinding.prototype);
|
||||
65
srcts/old/js/input_binding_actionbutton.js
Normal file
65
srcts/old/js/input_binding_actionbutton.js
Normal file
@@ -0,0 +1,65 @@
|
||||
var actionButtonInputBinding = new InputBinding();
|
||||
$.extend(actionButtonInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find(".action-button");
|
||||
},
|
||||
getValue: function(el) {
|
||||
return $(el).data('val') || 0;
|
||||
},
|
||||
setValue: function(el, value) {
|
||||
$(el).data('val', value);
|
||||
},
|
||||
getType: function(el) {
|
||||
return 'shiny.action';
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on("click.actionButtonInputBinding", function(e) {
|
||||
var $el = $(this);
|
||||
var val = $el.data('val') || 0;
|
||||
$el.data('val', val + 1);
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
getState: function(el) {
|
||||
return { value: this.getValue(el) };
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
var $el = $(el);
|
||||
|
||||
// retrieve current label and icon
|
||||
var label = $el.text();
|
||||
var icon = '';
|
||||
|
||||
// to check (and store) the previous icon, we look for a $el child
|
||||
// object that has an i tag, and some (any) class (this prevents
|
||||
// italicized text - which has an i tag but, usually, no class -
|
||||
// from being mistakenly selected)
|
||||
if ($el.find('i[class]').length > 0) {
|
||||
var icon_html = $el.find('i[class]')[0];
|
||||
if (icon_html === $el.children()[0]) { // another check for robustness
|
||||
icon = $(icon_html).prop('outerHTML');
|
||||
}
|
||||
}
|
||||
|
||||
// update the requested properties
|
||||
if (data.hasOwnProperty('label')) label = data.label;
|
||||
if (data.hasOwnProperty('icon')) {
|
||||
icon = data.icon;
|
||||
// if the user entered icon=character(0), remove the icon
|
||||
if (icon.length === 0) icon = '';
|
||||
}
|
||||
|
||||
// produce new html
|
||||
$el.html(icon + ' ' + label);
|
||||
},
|
||||
unsubscribe: function(el) {
|
||||
$(el).off(".actionButtonInputBinding");
|
||||
}
|
||||
});
|
||||
inputBindings.register(actionButtonInputBinding, 'shiny.actionButtonInput');
|
||||
|
||||
|
||||
$(document).on('click', 'a.action-button', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
38
srcts/old/js/input_binding_checkbox.js
Normal file
38
srcts/old/js/input_binding_checkbox.js
Normal file
@@ -0,0 +1,38 @@
|
||||
var checkboxInputBinding = new InputBinding();
|
||||
$.extend(checkboxInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('input[type="checkbox"]');
|
||||
},
|
||||
getValue: function(el) {
|
||||
return el.checked;
|
||||
},
|
||||
setValue: function(el, value) {
|
||||
el.checked = value;
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on('change.checkboxInputBinding', function(event) {
|
||||
callback(true);
|
||||
});
|
||||
},
|
||||
unsubscribe: function(el) {
|
||||
$(el).off('.checkboxInputBinding');
|
||||
},
|
||||
getState: function(el) {
|
||||
return {
|
||||
label: $(el).parent().find('span').text(),
|
||||
value: el.checked
|
||||
};
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
if (data.hasOwnProperty('value'))
|
||||
el.checked = data.value;
|
||||
|
||||
// checkboxInput()'s label works different from other
|
||||
// input labels...the label container should always exist
|
||||
if (data.hasOwnProperty('label'))
|
||||
$(el).parent().find('span').text(data.label);
|
||||
|
||||
$(el).trigger('change');
|
||||
}
|
||||
});
|
||||
inputBindings.register(checkboxInputBinding, 'shiny.checkboxInput');
|
||||
100
srcts/old/js/input_binding_checkboxgroup.js
Normal file
100
srcts/old/js/input_binding_checkboxgroup.js
Normal file
@@ -0,0 +1,100 @@
|
||||
var checkboxGroupInputBinding = new InputBinding();
|
||||
$.extend(checkboxGroupInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('.shiny-input-checkboxgroup');
|
||||
},
|
||||
getValue: function(el) {
|
||||
// Select the checkbox objects that have name equal to the grouping div's id
|
||||
var $objs = $('input:checkbox[name="' + $escape(el.id) + '"]:checked');
|
||||
var values = new Array($objs.length);
|
||||
for (var i = 0; i < $objs.length; i ++) {
|
||||
values[i] = $objs[i].value;
|
||||
}
|
||||
return values;
|
||||
},
|
||||
setValue: function(el, value) {
|
||||
// Clear all checkboxes
|
||||
$('input:checkbox[name="' + $escape(el.id) + '"]').prop('checked', false);
|
||||
|
||||
// Accept array
|
||||
if (value instanceof Array) {
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
$('input:checkbox[name="' + $escape(el.id) + '"][value="' + $escape(value[i]) + '"]')
|
||||
.prop('checked', true);
|
||||
}
|
||||
// Else assume it's a single value
|
||||
} else {
|
||||
$('input:checkbox[name="' + $escape(el.id) + '"][value="' + $escape(value) + '"]')
|
||||
.prop('checked', true);
|
||||
}
|
||||
|
||||
},
|
||||
getState: function(el) {
|
||||
var $objs = $('input:checkbox[name="' + $escape(el.id) + '"]');
|
||||
|
||||
// Store options in an array of objects, each with with value and label
|
||||
var options = new Array($objs.length);
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
options[i] = { value: $objs[i].value,
|
||||
label: this._getLabel($objs[i]) };
|
||||
}
|
||||
|
||||
return { label: this._getLabelNode(el).text(),
|
||||
value: this.getValue(el),
|
||||
options: options
|
||||
};
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
var $el = $(el);
|
||||
|
||||
// This will replace all the options
|
||||
if (data.hasOwnProperty('options')) {
|
||||
// Clear existing options and add each new one
|
||||
$el.find('div.shiny-options-group').remove();
|
||||
// Backward compatibility: for HTML generated by shinybootstrap2 package
|
||||
$el.find('label.checkbox').remove();
|
||||
$el.append(data.options);
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('value'))
|
||||
this.setValue(el, data.value);
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
|
||||
$(el).trigger('change');
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on('change.checkboxGroupInputBinding', function(event) {
|
||||
callback();
|
||||
});
|
||||
},
|
||||
unsubscribe: function(el) {
|
||||
$(el).off('.checkboxGroupInputBinding');
|
||||
},
|
||||
// Get the DOM element that contains the top-level label
|
||||
_getLabelNode: function(el) {
|
||||
return $(el).find('label[for="' + $escape(el.id) + '"]');
|
||||
},
|
||||
// Given an input DOM object, get the associated label. Handles labels
|
||||
// that wrap the input as well as labels associated with 'for' attribute.
|
||||
_getLabel: function(obj) {
|
||||
// If <label><input /><span>label text</span></label>
|
||||
if (obj.parentNode.tagName === "LABEL") {
|
||||
return $(obj.parentNode).find('span').text().trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
// Given an input DOM object, set the associated label. Handles labels
|
||||
// that wrap the input as well as labels associated with 'for' attribute.
|
||||
_setLabel: function(obj, value) {
|
||||
// If <label><input /><span>label text</span></label>
|
||||
if (obj.parentNode.tagName === "LABEL") {
|
||||
$(obj.parentNode).find('span').text(value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
});
|
||||
inputBindings.register(checkboxGroupInputBinding, 'shiny.checkboxGroupInput');
|
||||
249
srcts/old/js/input_binding_date.js
Normal file
249
srcts/old/js/input_binding_date.js
Normal file
@@ -0,0 +1,249 @@
|
||||
var dateInputBinding = new InputBinding();
|
||||
$.extend(dateInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('.shiny-date-input');
|
||||
},
|
||||
getType: function(el) {
|
||||
return "shiny.date";
|
||||
},
|
||||
// Return the date in an unambiguous format, yyyy-mm-dd (as opposed to a
|
||||
// format like mm/dd/yyyy)
|
||||
getValue: function(el) {
|
||||
var date = $(el).find('input').bsDatepicker('getUTCDate');
|
||||
return formatDateUTC(date);
|
||||
},
|
||||
// value must be an unambiguous string like '2001-01-01', or a Date object.
|
||||
setValue: function(el, value) {
|
||||
// R's NA, which is null here will remove current value
|
||||
if (value === null) {
|
||||
$(el).find('input').val('').bsDatepicker('update');
|
||||
return;
|
||||
}
|
||||
|
||||
var date = this._newDate(value);
|
||||
// If date is invalid, do nothing
|
||||
if (isNaN(date))
|
||||
return;
|
||||
|
||||
$(el).find('input').bsDatepicker('setUTCDate', date);
|
||||
},
|
||||
getState: function(el) {
|
||||
var $el = $(el);
|
||||
var $input = $el.find('input');
|
||||
|
||||
var min = $input.data('datepicker').startDate;
|
||||
var max = $input.data('datepicker').endDate;
|
||||
|
||||
// Stringify min and max. If min and max aren't set, they will be
|
||||
// -Infinity and Infinity; replace these with null.
|
||||
min = (min === -Infinity) ? null : formatDateUTC(min);
|
||||
max = (max === Infinity) ? null : formatDateUTC(max);
|
||||
|
||||
// startViewMode is stored as a number; convert to string
|
||||
var startview = $input.data('datepicker').startViewMode;
|
||||
if (startview === 2) startview = 'decade';
|
||||
else if (startview === 1) startview = 'year';
|
||||
else if (startview === 0) startview = 'month';
|
||||
|
||||
return {
|
||||
label: this._getLabelNode(el).text(),
|
||||
value: this.getValue(el),
|
||||
valueString: $input.val(),
|
||||
min: min,
|
||||
max: max,
|
||||
language: $input.data('datepicker').language,
|
||||
weekstart: $input.data('datepicker').weekStart,
|
||||
format: this._formatToString($input.data('datepicker').format),
|
||||
startview: startview
|
||||
};
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
var $input = $(el).find('input');
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
|
||||
if (data.hasOwnProperty('min'))
|
||||
this._setMin($input[0], data.min);
|
||||
|
||||
if (data.hasOwnProperty('max'))
|
||||
this._setMax($input[0], data.max);
|
||||
|
||||
// Must set value only after min and max have been set. If new value is
|
||||
// outside the bounds of the previous min/max, then the result will be a
|
||||
// blank input.
|
||||
if (data.hasOwnProperty('value'))
|
||||
this.setValue(el, data.value);
|
||||
|
||||
$(el).trigger('change');
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on('keyup.dateInputBinding input.dateInputBinding', function(event) {
|
||||
// Use normal debouncing policy when typing
|
||||
callback(true);
|
||||
});
|
||||
$(el).on('changeDate.dateInputBinding change.dateInputBinding', function(event) {
|
||||
// Send immediately when clicked
|
||||
callback(false);
|
||||
});
|
||||
},
|
||||
unsubscribe: function(el) {
|
||||
$(el).off('.dateInputBinding');
|
||||
},
|
||||
getRatePolicy: function() {
|
||||
return {
|
||||
policy: 'debounce',
|
||||
delay: 250
|
||||
};
|
||||
},
|
||||
initialize: function(el) {
|
||||
var $input = $(el).find('input');
|
||||
|
||||
// The challenge with dates is that we want them to be at 00:00 in UTC so
|
||||
// that we can do comparisons with them. However, the Date object itself
|
||||
// does not carry timezone information, so we should call _floorDateTime()
|
||||
// on Dates as soon as possible so that we know we're always working with
|
||||
// consistent objects.
|
||||
|
||||
var date = $input.data('initial-date');
|
||||
// If initial_date is null, set to current date
|
||||
if (date === undefined || date === null) {
|
||||
// Get local date, but normalized to beginning of day in UTC.
|
||||
date = this._floorDateTime(this._dateAsUTC(new Date()));
|
||||
}
|
||||
|
||||
this.setValue(el, date);
|
||||
|
||||
// Set the start and end dates, from min-date and max-date. These always
|
||||
// use yyyy-mm-dd format, instead of bootstrap-datepicker's built-in
|
||||
// support for date-startdate and data-enddate, which use the current
|
||||
// date format.
|
||||
if ($input.data('min-date') !== undefined) {
|
||||
this._setMin($input[0], $input.data('min-date'));
|
||||
}
|
||||
if ($input.data('max-date') !== undefined) {
|
||||
this._setMax($input[0], $input.data('max-date'));
|
||||
}
|
||||
},
|
||||
_getLabelNode: function(el) {
|
||||
return $(el).find('label[for="' + $escape(el.id) + '"]');
|
||||
},
|
||||
// Given a format object from a date picker, return a string
|
||||
_formatToString: function(format) {
|
||||
// Format object has structure like:
|
||||
// { parts: ['mm', 'dd', 'yy'], separators: ['', '/', '/' ,''] }
|
||||
var str = '';
|
||||
for (var i = 0; i < format.parts.length; i++) {
|
||||
str += format.separators[i] + format.parts[i];
|
||||
}
|
||||
str += format.separators[i];
|
||||
return str;
|
||||
},
|
||||
// Given an unambiguous date string or a Date object, set the min (start) date.
|
||||
// null will unset. undefined will result in no change,
|
||||
_setMin: function(el, date) {
|
||||
if (date === undefined)
|
||||
return;
|
||||
if (date === null) {
|
||||
$(el).bsDatepicker('setStartDate', null);
|
||||
return;
|
||||
}
|
||||
|
||||
date = this._newDate(date);
|
||||
// If date parsing fails, do nothing
|
||||
if (date === null)
|
||||
return;
|
||||
|
||||
if (isNaN(date))
|
||||
return;
|
||||
// Workarounds for
|
||||
// https://github.com/rstudio/shiny/issues/2335
|
||||
var curValue = $(el).bsDatepicker('getUTCDate');
|
||||
|
||||
// Note that there's no `setUTCStartDate`, so we need to convert this Date.
|
||||
// It starts at 00:00 UTC, and we convert it to 00:00 in local time, which
|
||||
// is what's needed for `setStartDate`.
|
||||
$(el).bsDatepicker('setStartDate', this._UTCDateAsLocal(date));
|
||||
|
||||
// If the new min is greater than the current date, unset the current date.
|
||||
if (date && curValue && date.getTime() > curValue.getTime()) {
|
||||
$(el).bsDatepicker('clearDates');
|
||||
} else {
|
||||
// Setting the date needs to be done AFTER `setStartDate`, because the
|
||||
// datepicker has a bug where calling `setStartDate` will clear the date
|
||||
// internally (even though it will still be visible in the UI) when a
|
||||
// 2-digit year format is used.
|
||||
// https://github.com/eternicode/bootstrap-datepicker/issues/2010
|
||||
$(el).bsDatepicker('setUTCDate', curValue);
|
||||
}
|
||||
},
|
||||
// Given an unambiguous date string or a Date object, set the max (end) date
|
||||
// null will unset.
|
||||
_setMax: function(el, date) {
|
||||
if (date === undefined)
|
||||
return;
|
||||
if (date === null) {
|
||||
$(el).bsDatepicker('setEndDate', null);
|
||||
return;
|
||||
}
|
||||
|
||||
date = this._newDate(date);
|
||||
// If date parsing fails, do nothing
|
||||
if (date === null)
|
||||
return;
|
||||
|
||||
if (isNaN(date))
|
||||
return;
|
||||
|
||||
// Workaround for same issue as in _setMin.
|
||||
var curValue = $(el).bsDatepicker('getUTCDate');
|
||||
|
||||
$(el).bsDatepicker('setEndDate', this._UTCDateAsLocal(date));
|
||||
|
||||
// If the new min is greater than the current date, unset the current date.
|
||||
if (date && curValue && date.getTime() < curValue.getTime()) {
|
||||
$(el).bsDatepicker('clearDates');
|
||||
} else {
|
||||
$(el).bsDatepicker('setUTCDate', curValue);
|
||||
}
|
||||
},
|
||||
// Given a date string of format yyyy-mm-dd, return a Date object with
|
||||
// that date at 12AM UTC.
|
||||
// If date is a Date object, return it unchanged.
|
||||
_newDate: function(date) {
|
||||
if (date instanceof Date)
|
||||
return date;
|
||||
if (!date)
|
||||
return null;
|
||||
|
||||
// Get Date object - this will be at 12AM in UTC, but may print
|
||||
// differently at the Javascript console.
|
||||
var d = parseDate(date);
|
||||
|
||||
// If invalid date, return null
|
||||
if (isNaN(d))
|
||||
return null;
|
||||
|
||||
return d;
|
||||
},
|
||||
// A Date can have any time during a day; this will return a new Date object
|
||||
// set to 00:00 in UTC.
|
||||
_floorDateTime: function(date) {
|
||||
date = new Date(date.getTime());
|
||||
date.setUTCHours(0, 0, 0, 0);
|
||||
return date;
|
||||
},
|
||||
// Given a Date object, return a Date object which has the same "clock time"
|
||||
// in UTC. For example, if input date is 2013-02-01 23:00:00 GMT-0600 (CST),
|
||||
// output will be 2013-02-01 23:00:00 UTC. Note that the JS console may
|
||||
// print this in local time, as "Sat Feb 02 2013 05:00:00 GMT-0600 (CST)".
|
||||
_dateAsUTC: function(date) {
|
||||
return new Date(date.getTime() - date.getTimezoneOffset() * 60000);
|
||||
},
|
||||
// The inverse of _dateAsUTC. This is needed to adjust time zones because
|
||||
// some bootstrap-datepicker methods only take local dates as input, and not
|
||||
// UTC.
|
||||
_UTCDateAsLocal: function(date) {
|
||||
return new Date(date.getTime() + date.getTimezoneOffset() * 60000);
|
||||
}
|
||||
});
|
||||
inputBindings.register(dateInputBinding, 'shiny.dateInput');
|
||||
147
srcts/old/js/input_binding_daterange.js
Normal file
147
srcts/old/js/input_binding_daterange.js
Normal file
@@ -0,0 +1,147 @@
|
||||
var dateRangeInputBinding = {};
|
||||
$.extend(dateRangeInputBinding, dateInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('.shiny-date-range-input');
|
||||
},
|
||||
// Return the date in an unambiguous format, yyyy-mm-dd (as opposed to a
|
||||
// format like mm/dd/yyyy)
|
||||
getValue: function(el) {
|
||||
var $inputs = $(el).find('input');
|
||||
var start = $inputs.eq(0).bsDatepicker('getUTCDate');
|
||||
var end = $inputs.eq(1).bsDatepicker('getUTCDate');
|
||||
|
||||
return [formatDateUTC(start), formatDateUTC(end)];
|
||||
},
|
||||
// value must be an object, with optional fields `start` and `end`. These
|
||||
// should be unambiguous strings like '2001-01-01', or Date objects.
|
||||
setValue: function(el, value) {
|
||||
if (!(value instanceof Object)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the start and end input objects
|
||||
var $inputs = $(el).find('input');
|
||||
|
||||
// If value is undefined, don't try to set
|
||||
// null will remove the current value
|
||||
if (value.start !== undefined) {
|
||||
if (value.start === null) {
|
||||
$inputs.eq(0).val('').bsDatepicker('update');
|
||||
} else {
|
||||
var start = this._newDate(value.start);
|
||||
$inputs.eq(0).bsDatepicker('setUTCDate', start);
|
||||
}
|
||||
}
|
||||
if (value.end !== undefined) {
|
||||
if (value.end === null) {
|
||||
$inputs.eq(1).val('').bsDatepicker('update');
|
||||
} else {
|
||||
var end = this._newDate(value.end);
|
||||
$inputs.eq(1).bsDatepicker('setUTCDate', end);
|
||||
}
|
||||
}
|
||||
},
|
||||
getState: function(el) {
|
||||
var $el = $(el);
|
||||
var $inputs = $el.find('input');
|
||||
var $startinput = $inputs.eq(0);
|
||||
var $endinput = $inputs.eq(1);
|
||||
|
||||
// For many of the properties, assume start and end have the same values
|
||||
var min = $startinput.bsDatepicker('getStartDate');
|
||||
var max = $startinput.bsDatepicker('getEndDate');
|
||||
|
||||
// Stringify min and max. If min and max aren't set, they will be
|
||||
// -Infinity and Infinity; replace these with null.
|
||||
min = (min === -Infinity) ? null : formatDateUTC(min);
|
||||
max = (max === Infinity) ? null : formatDateUTC(max);
|
||||
|
||||
// startViewMode is stored as a number; convert to string
|
||||
var startview = $startinput.data('datepicker').startView;
|
||||
if (startview === 2) startview = 'decade';
|
||||
else if (startview === 1) startview = 'year';
|
||||
else if (startview === 0) startview = 'month';
|
||||
|
||||
return {
|
||||
label: this._getLabelNode(el).text(),
|
||||
value: this.getValue(el),
|
||||
valueString: [ $startinput.val(), $endinput.val() ],
|
||||
min: min,
|
||||
max: max,
|
||||
weekstart: $startinput.data('datepicker').weekStart,
|
||||
format: this._formatToString($startinput.data('datepicker').format),
|
||||
language: $startinput.data('datepicker').language,
|
||||
startview: startview
|
||||
};
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
var $el = $(el);
|
||||
var $inputs = $el.find('input');
|
||||
var $startinput = $inputs.eq(0);
|
||||
var $endinput = $inputs.eq(1);
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
|
||||
if (data.hasOwnProperty('min')) {
|
||||
this._setMin($startinput[0], data.min);
|
||||
this._setMin($endinput[0], data.min);
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('max')) {
|
||||
this._setMax($startinput[0], data.max);
|
||||
this._setMax($endinput[0], data.max);
|
||||
}
|
||||
|
||||
// Must set value only after min and max have been set. If new value is
|
||||
// outside the bounds of the previous min/max, then the result will be a
|
||||
// blank input.
|
||||
if (data.hasOwnProperty('value'))
|
||||
this.setValue(el, data.value);
|
||||
|
||||
$el.trigger('change');
|
||||
},
|
||||
initialize: function(el) {
|
||||
var $el = $(el);
|
||||
var $inputs = $el.find('input');
|
||||
var $startinput = $inputs.eq(0);
|
||||
var $endinput = $inputs.eq(1);
|
||||
|
||||
var start = $startinput.data('initial-date');
|
||||
var end = $endinput.data('initial-date');
|
||||
|
||||
// If empty/null, use local date, but as UTC
|
||||
if (start === undefined || start === null)
|
||||
start = this._dateAsUTC(new Date());
|
||||
|
||||
if (end === undefined || end === null)
|
||||
end = this._dateAsUTC(new Date());
|
||||
|
||||
this.setValue(el, { "start": start, "end": end });
|
||||
|
||||
// // Set the start and end dates, from min-date and max-date. These always
|
||||
// // use yyyy-mm-dd format, instead of bootstrap-datepicker's built-in
|
||||
// // support for date-startdate and data-enddate, which use the current
|
||||
// // date format.
|
||||
this._setMin($startinput[0], $startinput.data('min-date'));
|
||||
this._setMin($endinput[0], $startinput.data('min-date'));
|
||||
this._setMax($startinput[0], $endinput.data('max-date'));
|
||||
this._setMax($endinput[0], $endinput.data('max-date'));
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on('keyup.dateRangeInputBinding input.dateRangeInputBinding', function(event) {
|
||||
// Use normal debouncing policy when typing
|
||||
callback(true);
|
||||
});
|
||||
$(el).on('changeDate.dateRangeInputBinding change.dateRangeInputBinding', function(event) {
|
||||
// Send immediately when clicked
|
||||
callback(false);
|
||||
});
|
||||
},
|
||||
unsubscribe: function(el) {
|
||||
$(el).off('.dateRangeInputBinding');
|
||||
},
|
||||
_getLabelNode: function(el) {
|
||||
return $(el).find('label[for="' + $escape(el.id) + '"]');
|
||||
},
|
||||
});
|
||||
inputBindings.register(dateRangeInputBinding, 'shiny.dateRangeInput');
|
||||
401
srcts/old/js/input_binding_fileinput.js
Normal file
401
srcts/old/js/input_binding_fileinput.js
Normal file
@@ -0,0 +1,401 @@
|
||||
var FileUploader = function(shinyapp, id, files, el) {
|
||||
this.shinyapp = shinyapp;
|
||||
this.id = id;
|
||||
this.el = el;
|
||||
FileProcessor.call(this, files);
|
||||
};
|
||||
$.extend(FileUploader.prototype, FileProcessor.prototype);
|
||||
(function() {
|
||||
this.makeRequest = function(method, args, onSuccess, onFailure, blobs) {
|
||||
this.shinyapp.makeRequest(method, args, onSuccess, onFailure, blobs);
|
||||
};
|
||||
this.onBegin = function(files, cont) {
|
||||
var self = this;
|
||||
|
||||
// Reset progress bar
|
||||
this.$setError(null);
|
||||
this.$setActive(true);
|
||||
this.$setVisible(true);
|
||||
this.onProgress(null, 0);
|
||||
|
||||
this.totalBytes = 0;
|
||||
this.progressBytes = 0;
|
||||
$.each(files, function(i, file) {
|
||||
self.totalBytes += file.size;
|
||||
});
|
||||
|
||||
var fileInfo = $.map(files, function(file, i) {
|
||||
return {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type
|
||||
};
|
||||
});
|
||||
|
||||
this.makeRequest(
|
||||
'uploadInit', [fileInfo],
|
||||
function(response) {
|
||||
self.jobId = response.jobId;
|
||||
self.uploadUrl = response.uploadUrl;
|
||||
cont();
|
||||
},
|
||||
function(error) {
|
||||
self.onError(error);
|
||||
});
|
||||
};
|
||||
this.onFile = function(file, cont) {
|
||||
var self = this;
|
||||
this.onProgress(file, 0);
|
||||
|
||||
$.ajax(this.uploadUrl, {
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
xhr: function() {
|
||||
var xhrVal = $.ajaxSettings.xhr();
|
||||
if (xhrVal.upload) {
|
||||
xhrVal.upload.onprogress = function(e) {
|
||||
if (e.lengthComputable) {
|
||||
self.onProgress(
|
||||
file,
|
||||
(self.progressBytes + e.loaded) / self.totalBytes);
|
||||
}
|
||||
};
|
||||
}
|
||||
return xhrVal;
|
||||
},
|
||||
data: file,
|
||||
contentType: 'application/octet-stream',
|
||||
processData: false,
|
||||
success: function() {
|
||||
self.progressBytes += file.size;
|
||||
cont();
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
self.onError(jqXHR.responseText || textStatus);
|
||||
}
|
||||
});
|
||||
};
|
||||
this.onComplete = function() {
|
||||
var self = this;
|
||||
|
||||
var fileInfo = $.map(this.files, function(file, i) {
|
||||
return {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type
|
||||
};
|
||||
});
|
||||
|
||||
// Trigger shiny:inputchanged. Unlike a normal shiny:inputchanged event,
|
||||
// it's not possible to modify the information before the values get
|
||||
// sent to the server.
|
||||
var evt = jQuery.Event("shiny:inputchanged");
|
||||
evt.name = this.id;
|
||||
evt.value = fileInfo;
|
||||
evt.binding = fileInputBinding;
|
||||
evt.el = this.el;
|
||||
evt.inputType = 'shiny.fileupload';
|
||||
$(document).trigger(evt);
|
||||
|
||||
this.makeRequest(
|
||||
'uploadEnd', [this.jobId, this.id],
|
||||
function(response) {
|
||||
self.$setActive(false);
|
||||
self.onProgress(null, 1);
|
||||
self.$bar().text('Upload complete');
|
||||
// Reset the file input's value to "". This allows the same file to be
|
||||
// uploaded again. https://stackoverflow.com/a/22521275
|
||||
$(evt.el).val("");
|
||||
},
|
||||
function(error) {
|
||||
self.onError(error);
|
||||
});
|
||||
this.$bar().text('Finishing upload');
|
||||
};
|
||||
this.onError = function(message) {
|
||||
this.$setError(message || '');
|
||||
this.$setActive(false);
|
||||
};
|
||||
this.onAbort = function() {
|
||||
this.$setVisible(false);
|
||||
};
|
||||
this.onProgress = function(file, completed) {
|
||||
this.$bar().width(Math.round(completed*100) + '%');
|
||||
this.$bar().text(file ? file.name : '');
|
||||
};
|
||||
this.$container = function() {
|
||||
return $('#' + $escape(this.id) + '_progress.shiny-file-input-progress');
|
||||
};
|
||||
this.$bar = function() {
|
||||
return $('#' + $escape(this.id) + '_progress.shiny-file-input-progress .progress-bar');
|
||||
};
|
||||
this.$setVisible = function(visible) {
|
||||
this.$container().css('visibility', visible ? 'visible' : 'hidden');
|
||||
};
|
||||
this.$setError = function(error) {
|
||||
this.$bar().toggleClass('progress-bar-danger', (error !== null));
|
||||
if (error !== null) {
|
||||
this.onProgress(null, 1);
|
||||
this.$bar().text(error);
|
||||
}
|
||||
};
|
||||
this.$setActive = function(active) {
|
||||
this.$container().toggleClass('active', !!active);
|
||||
};
|
||||
}).call(FileUploader.prototype);
|
||||
|
||||
|
||||
// NOTE On Safari, at least version 10.1.2, *if the developer console is open*,
|
||||
// setting the input's value will behave strangely because of a Safari bug. The
|
||||
// uploaded file's name will appear over the placeholder value, instead of
|
||||
// replacing it. The workaround is to restart Safari. When I (Alan Dipert) ran
|
||||
// into this bug Winston Chang helped me diagnose the exact problem, and Winston
|
||||
// then submitted a bug report to Apple.
|
||||
function setFileText($el, files) {
|
||||
var $fileText = $el.closest('div.input-group').find('input[type=text]');
|
||||
if (files.length === 1) {
|
||||
$fileText.val(files[0].name);
|
||||
} else {
|
||||
$fileText.val(files.length + " files");
|
||||
}
|
||||
}
|
||||
|
||||
// If previously selected files are uploading, abort that.
|
||||
function abortCurrentUpload($el) {
|
||||
var uploader = $el.data('currentUploader');
|
||||
if (uploader) uploader.abort();
|
||||
// Clear data-restore attribute if present.
|
||||
$el.removeAttr('data-restore');
|
||||
}
|
||||
|
||||
function uploadDroppedFilesIE10Plus(el, files) {
|
||||
var $el = $(el);
|
||||
abortCurrentUpload($el);
|
||||
|
||||
// Set the label in the text box
|
||||
setFileText($el, files);
|
||||
|
||||
// Start the new upload and put the uploader in 'currentUploader'.
|
||||
$el.data('currentUploader',
|
||||
new FileUploader(exports.shinyapp,
|
||||
fileInputBinding.getId(el),
|
||||
files,
|
||||
el));
|
||||
}
|
||||
|
||||
function uploadFiles(evt) {
|
||||
var $el = $(evt.target);
|
||||
abortCurrentUpload($el);
|
||||
|
||||
var files = evt.target.files;
|
||||
var id = fileInputBinding.getId(evt.target);
|
||||
|
||||
if (files.length === 0)
|
||||
return;
|
||||
|
||||
// Set the label in the text box
|
||||
setFileText($el, files);
|
||||
|
||||
// Start the new upload and put the uploader in 'currentUploader'.
|
||||
$el.data('currentUploader',
|
||||
new FileUploader(exports.shinyapp, id, files, evt.target));
|
||||
}
|
||||
|
||||
// Here we maintain a list of all the current file inputs. This is necessary
|
||||
// because we need to trigger events on them in order to respond to file drag
|
||||
// events. For example, they should all light up when a file is dragged on to
|
||||
// the page.
|
||||
var $fileInputs = $();
|
||||
|
||||
var fileInputBinding = new InputBinding();
|
||||
$.extend(fileInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('input[type="file"]');
|
||||
},
|
||||
getId: function(el) {
|
||||
return InputBinding.prototype.getId.call(this, el) || el.name;
|
||||
},
|
||||
getValue: function(el) {
|
||||
// This returns a non-undefined value only when there's a 'data-restore'
|
||||
// attribute, which is set only when restoring Shiny state. If a file is
|
||||
// uploaded through the browser, 'data-restore' gets cleared.
|
||||
var data = $(el).attr('data-restore');
|
||||
if (data) {
|
||||
data = JSON.parse(data);
|
||||
|
||||
// Set the label in the text box
|
||||
var $fileText = $(el).closest('div.input-group').find('input[type=text]');
|
||||
if (data.name.length === 1) {
|
||||
$fileText.val(data.name[0]);
|
||||
} else {
|
||||
$fileText.val(data.name.length + " files");
|
||||
}
|
||||
|
||||
// Manually set up progress bar. A bit inelegant because it duplicates
|
||||
// code from FileUploader, but duplication is less bad than alternatives.
|
||||
var $progress = $(el).closest('div.form-group').find('.progress');
|
||||
var $bar = $progress.find('.progress-bar');
|
||||
$progress.removeClass('active');
|
||||
$bar.width('100%');
|
||||
$bar.css('visibility', 'visible');
|
||||
|
||||
return data;
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
setValue: function(el, value) {
|
||||
// Not implemented
|
||||
},
|
||||
getType: function(el) {
|
||||
// This will be used only when restoring a file from a saved state.
|
||||
return 'shiny.file';
|
||||
},
|
||||
_zoneOf: function(el) {
|
||||
return $(el).closest("div.input-group");
|
||||
},
|
||||
// This function makes it possible to attach listeners to the dragenter,
|
||||
// dragleave, and drop events of a single element with children. It's not
|
||||
// intuitive to do directly because outer elements fire "dragleave" events
|
||||
// both when the drag leaves the element and when the drag enters a child. To
|
||||
// make it easier, we maintain a count of the elements being dragged across
|
||||
// and trigger 3 new types of event:
|
||||
//
|
||||
// 1. draghover:enter - When a drag enters el and any of its children.
|
||||
// 2. draghover:leave - When the drag leaves el and all of its children.
|
||||
// 3. draghover:drop - When an item is dropped on el or any of its children.
|
||||
_enableDraghover: function(el) {
|
||||
let $el = $(el),
|
||||
childCounter = 0;
|
||||
$el.on({
|
||||
"dragenter.draghover": e => {
|
||||
if (childCounter++ === 0) {
|
||||
$el.trigger("draghover:enter", e);
|
||||
}
|
||||
},
|
||||
"dragleave.draghover": e => {
|
||||
if (--childCounter === 0) {
|
||||
$el.trigger("draghover:leave", e);
|
||||
}
|
||||
if (childCounter < 0) {
|
||||
console.error("draghover childCounter is negative somehow");
|
||||
}
|
||||
},
|
||||
"dragover.draghover": e => {
|
||||
e.preventDefault();
|
||||
},
|
||||
"drop.draghover": e => {
|
||||
childCounter = 0;
|
||||
$el.trigger("draghover:drop", e);
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
return $el;
|
||||
},
|
||||
_disableDraghover: function(el) {
|
||||
return $(el).off(".draghover");
|
||||
},
|
||||
_ZoneClass: {
|
||||
ACTIVE: "shiny-file-input-active",
|
||||
OVER: "shiny-file-input-over"
|
||||
},
|
||||
_enableDocumentEvents: function() {
|
||||
let $doc = $("html"),
|
||||
{ACTIVE, OVER} = this._ZoneClass;
|
||||
this._enableDraghover($doc)
|
||||
.on({
|
||||
"draghover:enter.draghover": e => {
|
||||
this._zoneOf($fileInputs).addClass(ACTIVE);
|
||||
},
|
||||
"draghover:leave.draghover": e => {
|
||||
this._zoneOf($fileInputs).removeClass(ACTIVE);
|
||||
},
|
||||
"draghover:drop.draghover": e => {
|
||||
this._zoneOf($fileInputs)
|
||||
.removeClass(OVER)
|
||||
.removeClass(ACTIVE);
|
||||
}
|
||||
});
|
||||
},
|
||||
_disableDocumentEvents: function() {
|
||||
let $doc = $("html");
|
||||
$doc.off(".draghover");
|
||||
this._disableDraghover($doc);
|
||||
},
|
||||
_canSetFiles: function(fileList) {
|
||||
var testEl = document.createElement("input");
|
||||
testEl.type = "file";
|
||||
try {
|
||||
testEl.files = fileList;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
_handleDrop: function(e, el) {
|
||||
const files = e.originalEvent.dataTransfer.files,
|
||||
$el = $(el);
|
||||
if (files === undefined || files === null) {
|
||||
// 1. The FileList object isn't supported by this browser, and
|
||||
// there's nothing else we can try. (< IE 10)
|
||||
console.log("Dropping files is not supported on this browser. (no FileList)");
|
||||
} else if (!this._canSetFiles(files)) {
|
||||
// 2. The browser doesn't support assigning a type=file input's .files
|
||||
// property, but we do have a FileList to work with. (IE10+/Edge)
|
||||
$el.val("");
|
||||
uploadDroppedFilesIE10Plus(el, files);
|
||||
} else {
|
||||
// 3. The browser supports FileList and input.files assignment.
|
||||
// (Chrome, Safari)
|
||||
$el.val("");
|
||||
el.files = e.originalEvent.dataTransfer.files;
|
||||
// Recent versions of Firefox (57+, or "Quantum" and beyond) don't seem to
|
||||
// automatically trigger a change event, so we trigger one manually here.
|
||||
// On browsers that do trigger change, this operation appears to be
|
||||
// idempotent, as el.files doesn't change between events.
|
||||
$el.trigger("change");
|
||||
}
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on("change.fileInputBinding", uploadFiles);
|
||||
// Here we try to set up the necessary events for Drag and Drop ("DnD").
|
||||
if ($fileInputs.length === 0) this._enableDocumentEvents();
|
||||
$fileInputs = $fileInputs.add(el);
|
||||
let $zone = this._zoneOf(el),
|
||||
{OVER} = this._ZoneClass;
|
||||
this._enableDraghover($zone)
|
||||
.on({
|
||||
"draghover:enter.draghover": e => {
|
||||
$zone.addClass(OVER);
|
||||
},
|
||||
"draghover:leave.draghover": e => {
|
||||
$zone.removeClass(OVER);
|
||||
// Prevent this event from bubbling to the document handler,
|
||||
// which would deactivate all zones.
|
||||
e.stopPropagation();
|
||||
},
|
||||
"draghover:drop.draghover": (e, dropEvent) => {
|
||||
this._handleDrop(dropEvent, el);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
unsubscribe: function(el) {
|
||||
let $el = $(el),
|
||||
$zone = this._zoneOf(el);
|
||||
|
||||
$zone
|
||||
.removeClass(this._ZoneClass.OVER)
|
||||
.removeClass(this._ZoneClass.ACTIVE);
|
||||
|
||||
this._disableDraghover($zone);
|
||||
$el.off(".fileInputBinding");
|
||||
$zone.off(".draghover");
|
||||
|
||||
// Remove el from list of inputs and (maybe) clean up global event handlers.
|
||||
$fileInputs = $fileInputs.not(el);
|
||||
if ($fileInputs.length === 0) this._disableDocumentEvents();
|
||||
}
|
||||
});
|
||||
inputBindings.register(fileInputBinding, 'shiny.fileInputBinding');
|
||||
42
srcts/old/js/input_binding_number.js
Normal file
42
srcts/old/js/input_binding_number.js
Normal file
@@ -0,0 +1,42 @@
|
||||
var numberInputBinding = {};
|
||||
$.extend(numberInputBinding, textInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('input[type="number"]');
|
||||
},
|
||||
getValue: function(el) {
|
||||
var numberVal = $(el).val();
|
||||
if (/^\s*$/.test(numberVal)) // Return null if all whitespace
|
||||
return null;
|
||||
else if (!isNaN(numberVal)) // If valid Javascript number string, coerce to number
|
||||
return +numberVal;
|
||||
else
|
||||
return numberVal; // If other string like "1e6", send it unchanged
|
||||
},
|
||||
setValue: function(el, value) {
|
||||
el.value = value;
|
||||
},
|
||||
getType: function(el) {
|
||||
return "shiny.number";
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
if (data.hasOwnProperty('value')) el.value = data.value;
|
||||
if (data.hasOwnProperty('min')) el.min = data.min;
|
||||
if (data.hasOwnProperty('max')) el.max = data.max;
|
||||
if (data.hasOwnProperty('step')) el.step = data.step;
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
|
||||
$(el).trigger('change');
|
||||
},
|
||||
getState: function(el) {
|
||||
return { label: this._getLabelNode(el).text(),
|
||||
value: this.getValue(el),
|
||||
min: Number(el.min),
|
||||
max: Number(el.max),
|
||||
step: Number(el.step) };
|
||||
},
|
||||
_getLabelNode: function(el) {
|
||||
return $(el).parent().find('label[for="' + $escape(el.id) + '"]');
|
||||
}
|
||||
});
|
||||
inputBindings.register(numberInputBinding, 'shiny.numberInput');
|
||||
10
srcts/old/js/input_binding_password.js
Normal file
10
srcts/old/js/input_binding_password.js
Normal file
@@ -0,0 +1,10 @@
|
||||
var passwordInputBinding = {};
|
||||
$.extend(passwordInputBinding, textInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('input[type="password"]');
|
||||
},
|
||||
getType: function(el) {
|
||||
return "shiny.password";
|
||||
}
|
||||
});
|
||||
inputBindings.register(passwordInputBinding, 'shiny.passwordInput');
|
||||
95
srcts/old/js/input_binding_radio.js
Normal file
95
srcts/old/js/input_binding_radio.js
Normal file
@@ -0,0 +1,95 @@
|
||||
var radioInputBinding = new InputBinding();
|
||||
$.extend(radioInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('.shiny-input-radiogroup');
|
||||
},
|
||||
getValue: function(el) {
|
||||
// Select the radio objects that have name equal to the grouping div's id
|
||||
var checked_items = $('input:radio[name="' + $escape(el.id) + '"]:checked');
|
||||
|
||||
if (checked_items.length === 0) {
|
||||
// If none are checked, the input will return null (it's the default on load,
|
||||
// but it wasn't emptied when calling updateRadioButtons with character(0)
|
||||
return null;
|
||||
}
|
||||
|
||||
return checked_items.val();
|
||||
},
|
||||
setValue: function(el, value) {
|
||||
if ($.isArray(value) && value.length === 0) {
|
||||
// Removing all checked item if the sent data is empty
|
||||
$('input:radio[name="' + $escape(el.id) + '"]').prop('checked', false);
|
||||
} else {
|
||||
$('input:radio[name="' + $escape(el.id) + '"][value="' + $escape(value) + '"]').prop('checked', true);
|
||||
}
|
||||
|
||||
},
|
||||
getState: function(el) {
|
||||
var $objs = $('input:radio[name="' + $escape(el.id) + '"]');
|
||||
|
||||
// Store options in an array of objects, each with with value and label
|
||||
var options = new Array($objs.length);
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
options[i] = { value: $objs[i].value,
|
||||
label: this._getLabel($objs[i]) };
|
||||
}
|
||||
|
||||
return {
|
||||
label: this._getLabelNode(el).text(),
|
||||
value: this.getValue(el),
|
||||
options: options
|
||||
};
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
var $el = $(el);
|
||||
// This will replace all the options
|
||||
if (data.hasOwnProperty('options')) {
|
||||
// Clear existing options and add each new one
|
||||
$el.find('div.shiny-options-group').remove();
|
||||
// Backward compatibility: for HTML generated by shinybootstrap2 package
|
||||
$el.find('label.radio').remove();
|
||||
$el.append(data.options);
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('value'))
|
||||
this.setValue(el, data.value);
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
|
||||
$(el).trigger('change');
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on('change.radioInputBinding', function(event) {
|
||||
callback();
|
||||
});
|
||||
},
|
||||
unsubscribe: function(el) {
|
||||
$(el).off('.radioInputBinding');
|
||||
},
|
||||
// Get the DOM element that contains the top-level label
|
||||
_getLabelNode: function(el) {
|
||||
return $(el).parent().find('label[for="' + $escape(el.id) + '"]');
|
||||
},
|
||||
// Given an input DOM object, get the associated label. Handles labels
|
||||
// that wrap the input as well as labels associated with 'for' attribute.
|
||||
_getLabel: function(obj) {
|
||||
// If <label><input /><span>label text</span></label>
|
||||
if (obj.parentNode.tagName === "LABEL") {
|
||||
return $(obj.parentNode).find('span').text().trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
// Given an input DOM object, set the associated label. Handles labels
|
||||
// that wrap the input as well as labels associated with 'for' attribute.
|
||||
_setLabel: function(obj, value) {
|
||||
// If <label><input /><span>label text</span></label>
|
||||
if (obj.parentNode.tagName === "LABEL") {
|
||||
$(obj.parentNode).find('span').text(value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
});
|
||||
inputBindings.register(radioInputBinding, 'shiny.radioInput');
|
||||
206
srcts/old/js/input_binding_select.js
Normal file
206
srcts/old/js/input_binding_select.js
Normal file
@@ -0,0 +1,206 @@
|
||||
var selectInputBinding = new InputBinding();
|
||||
$.extend(selectInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('select');
|
||||
},
|
||||
getType: function(el) {
|
||||
var $el = $(el);
|
||||
if (!$el.hasClass("symbol")) {
|
||||
// default character type
|
||||
return null;
|
||||
}
|
||||
if ($el.attr("multiple") === "multiple") {
|
||||
return 'shiny.symbolList';
|
||||
} else {
|
||||
return 'shiny.symbol';
|
||||
}
|
||||
},
|
||||
getId: function(el) {
|
||||
return InputBinding.prototype.getId.call(this, el) || el.name;
|
||||
},
|
||||
getValue: function(el) {
|
||||
return $(el).val();
|
||||
},
|
||||
setValue: function(el, value) {
|
||||
if (!this._is_selectize(el)) {
|
||||
$(el).val(value);
|
||||
} else {
|
||||
let selectize = this._selectize(el);
|
||||
if (selectize) {
|
||||
selectize.setValue(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
getState: function(el) {
|
||||
// Store options in an array of objects, each with with value and label
|
||||
var options = new Array(el.length);
|
||||
for (var i = 0; i < el.length; i++) {
|
||||
options[i] = { value: el[i].value,
|
||||
label: el[i].label };
|
||||
}
|
||||
|
||||
return {
|
||||
label: this._getLabelNode(el),
|
||||
value: this.getValue(el),
|
||||
options: options
|
||||
};
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
var $el = $(el), selectize;
|
||||
|
||||
// This will replace all the options
|
||||
if (data.hasOwnProperty('options')) {
|
||||
selectize = this._selectize(el);
|
||||
// Must destroy selectize before appending new options, otherwise
|
||||
// selectize will restore the original select
|
||||
if (selectize) selectize.destroy();
|
||||
// Clear existing options and add each new one
|
||||
$el.empty().append(data.options);
|
||||
this._selectize(el);
|
||||
}
|
||||
|
||||
// re-initialize selectize
|
||||
if (data.hasOwnProperty('config')) {
|
||||
$el.parent()
|
||||
.find('script[data-for="' + $escape(el.id) + '"]')
|
||||
.replaceWith(data.config);
|
||||
this._selectize(el, true);
|
||||
}
|
||||
|
||||
// use server-side processing for selectize
|
||||
if (data.hasOwnProperty('url')) {
|
||||
selectize = this._selectize(el);
|
||||
selectize.clearOptions();
|
||||
var loaded = false;
|
||||
selectize.settings.load = function(query, callback) {
|
||||
var settings = selectize.settings;
|
||||
$.ajax({
|
||||
url: data.url,
|
||||
data: {
|
||||
query: query,
|
||||
field: JSON.stringify([settings.searchField]),
|
||||
value: settings.valueField,
|
||||
conju: settings.searchConjunction,
|
||||
maxop: settings.maxOptions
|
||||
},
|
||||
type: 'GET',
|
||||
error: function() {
|
||||
callback();
|
||||
},
|
||||
success: function(res) {
|
||||
// res = [{label: '1', value: '1', group: '1'}, ...]
|
||||
// success is called after options are added, but
|
||||
// groups need to be added manually below
|
||||
$.each(res, function(index, elem) {
|
||||
// Call selectize.addOptionGroup once for each optgroup; the
|
||||
// first argument is the group ID, the second is an object with
|
||||
// the group's label and value. We use the current settings of
|
||||
// the selectize object to decide the fieldnames of that obj.
|
||||
let optgroupId = elem[settings.optgroupField || "optgroup"];
|
||||
let optgroup = {};
|
||||
optgroup[settings.optgroupLabelField || "label"] = optgroupId;
|
||||
optgroup[settings.optgroupValueField || "value"] = optgroupId;
|
||||
selectize.addOptionGroup(optgroupId, optgroup);
|
||||
});
|
||||
callback(res);
|
||||
if (!loaded) {
|
||||
if (data.hasOwnProperty('value')) {
|
||||
selectize.setValue(data.value);
|
||||
} else if (settings.maxItems === 1) {
|
||||
// first item selected by default only for single-select
|
||||
selectize.setValue(res[0].value);
|
||||
}
|
||||
}
|
||||
loaded = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
// perform an empty search after changing the `load` function
|
||||
selectize.load(function(callback) {
|
||||
selectize.settings.load.apply(selectize, ['', callback]);
|
||||
});
|
||||
} else if (data.hasOwnProperty('value')) {
|
||||
this.setValue(el, data.value);
|
||||
}
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
|
||||
$(el).trigger('change');
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on('change.selectInputBinding', event => {
|
||||
// https://github.com/rstudio/shiny/issues/2162
|
||||
// Prevent spurious events that are gonna be squelched in
|
||||
// a second anyway by the onItemRemove down below
|
||||
if (el.nonempty && this.getValue(el) === "") {
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
});
|
||||
},
|
||||
unsubscribe: function(el) {
|
||||
$(el).off('.selectInputBinding');
|
||||
},
|
||||
initialize: function(el) {
|
||||
this._selectize(el);
|
||||
},
|
||||
_getLabelNode: function(el) {
|
||||
let escaped_id = $escape(el.id);
|
||||
if (this._is_selectize(el)) {
|
||||
escaped_id += "-selectized";
|
||||
}
|
||||
return $(el).parent().parent().find('label[for="' + escaped_id + '"]');
|
||||
},
|
||||
// Return true if it's a selectize input, false if it's a regular select input.
|
||||
_is_selectize: function(el) {
|
||||
var config = $(el).parent().find('script[data-for="' + $escape(el.id) + '"]');
|
||||
return (config.length > 0);
|
||||
},
|
||||
_selectize: function(el, update) {
|
||||
if (!$.fn.selectize) return undefined;
|
||||
var $el = $(el);
|
||||
var config = $el.parent().find('script[data-for="' + $escape(el.id) + '"]');
|
||||
if (config.length === 0) return undefined;
|
||||
|
||||
var options = $.extend({
|
||||
labelField: 'label',
|
||||
valueField: 'value',
|
||||
searchField: ['label']
|
||||
}, JSON.parse(config.html()));
|
||||
|
||||
// selectize created from selectInput()
|
||||
if (typeof(config.data('nonempty')) !== 'undefined') {
|
||||
el.nonempty = true;
|
||||
options = $.extend(options, {
|
||||
onItemRemove: function(value) {
|
||||
if (this.getValue() === "")
|
||||
$("select#" + $escape(el.id)).empty().append($("<option/>", {
|
||||
"value": value,
|
||||
"selected": true
|
||||
})).trigger("change");
|
||||
},
|
||||
onDropdownClose: function($dropdown) {
|
||||
if (this.getValue() === "")
|
||||
this.setValue($("select#" + $escape(el.id)).val());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
el.nonempty = false;
|
||||
}
|
||||
// options that should be eval()ed
|
||||
if (config.data('eval') instanceof Array)
|
||||
$.each(config.data('eval'), function(i, x) {
|
||||
/*jshint evil: true*/
|
||||
options[x] = eval('(' + options[x] + ')');
|
||||
});
|
||||
var control = $el.selectize(options)[0].selectize;
|
||||
// .selectize() does not really update settings; must destroy and rebuild
|
||||
if (update) {
|
||||
var settings = $.extend(control.settings, options);
|
||||
control.destroy();
|
||||
control = $el.selectize(settings)[0].selectize;
|
||||
}
|
||||
return control;
|
||||
}
|
||||
});
|
||||
inputBindings.register(selectInputBinding, 'shiny.selectInput');
|
||||
311
srcts/old/js/input_binding_slider.js
Normal file
311
srcts/old/js/input_binding_slider.js
Normal file
@@ -0,0 +1,311 @@
|
||||
// Necessary to get hidden sliders to send their updated values
|
||||
function forceIonSliderUpdate(slider) {
|
||||
if (slider.$cache && slider.$cache.input)
|
||||
slider.$cache.input.trigger('change');
|
||||
else
|
||||
console.log("Couldn't force ion slider to update");
|
||||
}
|
||||
|
||||
function getTypePrettifyer(dataType, timeFormat, timezone) {
|
||||
var timeFormatter;
|
||||
var prettify;
|
||||
if (dataType === 'date') {
|
||||
timeFormatter = strftime.utc();
|
||||
prettify = function(num) {
|
||||
return timeFormatter(timeFormat, new Date(num));
|
||||
};
|
||||
|
||||
} else if (dataType === 'datetime') {
|
||||
if (timezone)
|
||||
timeFormatter = strftime.timezone(timezone);
|
||||
else
|
||||
timeFormatter = strftime;
|
||||
|
||||
prettify = function(num) {
|
||||
return timeFormatter(timeFormat, new Date(num));
|
||||
};
|
||||
|
||||
} else {
|
||||
// The default prettify function for ion.rangeSlider adds thousands
|
||||
// separators after the decimal mark, so we have our own version here.
|
||||
// (#1958)
|
||||
prettify = function(num) {
|
||||
// When executed, `this` will refer to the `IonRangeSlider.options`
|
||||
// object.
|
||||
return formatNumber(num, this.prettify_separator);
|
||||
};
|
||||
}
|
||||
return prettify;
|
||||
}
|
||||
|
||||
var sliderInputBinding = {};
|
||||
$.extend(sliderInputBinding, textInputBinding, {
|
||||
find: function(scope) {
|
||||
// Check if ionRangeSlider plugin is loaded
|
||||
if (!$.fn.ionRangeSlider)
|
||||
return [];
|
||||
|
||||
return $(scope).find('input.js-range-slider');
|
||||
},
|
||||
getType: function(el) {
|
||||
var dataType = $(el).data('data-type');
|
||||
if (dataType === 'date')
|
||||
return 'shiny.date';
|
||||
else if (dataType === 'datetime')
|
||||
return 'shiny.datetime';
|
||||
else
|
||||
return false;
|
||||
},
|
||||
getValue: function(el) {
|
||||
var $el = $(el);
|
||||
var result = $(el).data('ionRangeSlider').result;
|
||||
|
||||
// Function for converting numeric value from slider to appropriate type.
|
||||
var convert;
|
||||
var dataType = $el.data('data-type');
|
||||
if (dataType === 'date') {
|
||||
convert = function(val) {
|
||||
return formatDateUTC(new Date(+val));
|
||||
};
|
||||
} else if (dataType === 'datetime') {
|
||||
convert = function(val) {
|
||||
// Convert ms to s
|
||||
return +val / 1000;
|
||||
};
|
||||
} else {
|
||||
convert = function(val) { return +val; };
|
||||
}
|
||||
|
||||
if (this._numValues(el) === 2) {
|
||||
return [convert(result.from), convert(result.to)];
|
||||
}
|
||||
else {
|
||||
return convert(result.from);
|
||||
}
|
||||
|
||||
},
|
||||
setValue: function(el, value) {
|
||||
var $el = $(el);
|
||||
var slider = $el.data('ionRangeSlider');
|
||||
|
||||
$el.data('immediate', true);
|
||||
try {
|
||||
if (this._numValues(el) === 2 && value instanceof Array) {
|
||||
slider.update({ from: value[0], to: value[1] });
|
||||
} else {
|
||||
slider.update({ from: value });
|
||||
}
|
||||
|
||||
forceIonSliderUpdate(slider);
|
||||
} finally {
|
||||
$el.data('immediate', false);
|
||||
}
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on('change.sliderInputBinding', function(event) {
|
||||
callback(!$(el).data('immediate') && !$(el).data('animating'));
|
||||
});
|
||||
},
|
||||
unsubscribe: function(el) {
|
||||
$(el).off('.sliderInputBinding');
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
var $el = $(el);
|
||||
var slider = $el.data('ionRangeSlider');
|
||||
var msg = {};
|
||||
|
||||
if (data.hasOwnProperty('value')) {
|
||||
if (this._numValues(el) === 2 && data.value instanceof Array) {
|
||||
msg.from = data.value[0];
|
||||
msg.to = data.value[1];
|
||||
} else {
|
||||
msg.from = data.value;
|
||||
}
|
||||
}
|
||||
var sliderFeatures = ['min', 'max', 'step'];
|
||||
for (var i = 0; i < sliderFeatures.length; i++) {
|
||||
var feats = sliderFeatures[i];
|
||||
if (data.hasOwnProperty(feats)) {
|
||||
msg[feats] = data[feats];
|
||||
}
|
||||
}
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
|
||||
var domElements = ['data-type', 'time-format', 'timezone'];
|
||||
for (var i = 0; i < domElements.length; i++) {
|
||||
var elem = domElements[i];
|
||||
if (data.hasOwnProperty(elem)) {
|
||||
$el.data(elem, data[elem]);
|
||||
}
|
||||
}
|
||||
|
||||
var dataType = $el.data('data-type');
|
||||
var timeFormat = $el.data('time-format');
|
||||
var timezone = $el.data('timezone');
|
||||
|
||||
msg.prettify = getTypePrettifyer(dataType, timeFormat, timezone);
|
||||
|
||||
$el.data('immediate', true);
|
||||
try {
|
||||
slider.update(msg);
|
||||
forceIonSliderUpdate(slider);
|
||||
} finally {
|
||||
$el.data('immediate', false);
|
||||
}
|
||||
},
|
||||
getRatePolicy: function() {
|
||||
return {
|
||||
policy: 'debounce',
|
||||
delay: 250
|
||||
};
|
||||
},
|
||||
getState: function(el) {
|
||||
},
|
||||
initialize: function(el) {
|
||||
var opts = {};
|
||||
var $el = $(el);
|
||||
var dataType = $el.data('data-type');
|
||||
var timeFormat = $el.data('time-format');
|
||||
var timezone = $el.data('timezone');
|
||||
|
||||
opts.prettify = getTypePrettifyer(dataType, timeFormat, timezone);
|
||||
|
||||
$el.ionRangeSlider(opts);
|
||||
},
|
||||
_getLabelNode: function(el) {
|
||||
return $(el).parent().find('label[for="' + $escape(el.id) + '"]');
|
||||
},
|
||||
// Number of values; 1 for single slider, 2 for range slider
|
||||
_numValues: function(el) {
|
||||
if ($(el).data('ionRangeSlider').options.type === 'double')
|
||||
return 2;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
inputBindings.register(sliderInputBinding, 'shiny.sliderInput');
|
||||
|
||||
|
||||
// Format numbers for nicer output.
|
||||
// formatNumber(1234567.12345) === "1,234,567.12345"
|
||||
// formatNumber(1234567.12345, ".", ",") === "1.234.567,12345"
|
||||
// formatNumber(1000, " ") === "1 000"
|
||||
// formatNumber(20) === "20"
|
||||
// formatNumber(1.2345e24) === "1.2345e+24"
|
||||
function formatNumber(num, thousand_sep = ",", decimal_sep = ".") {
|
||||
let parts = num.toString().split(".");
|
||||
|
||||
// Add separators to portion before decimal mark.
|
||||
parts[0] = parts[0].replace(/(\d{1,3}(?=(?:\d\d\d)+(?!\d)))/g, "$1" + thousand_sep);
|
||||
|
||||
if (parts.length === 1)
|
||||
return parts[0];
|
||||
else if (parts.length === 2)
|
||||
return parts[0] + decimal_sep + parts[1];
|
||||
else
|
||||
return "";
|
||||
};
|
||||
|
||||
$(document).on('click', '.slider-animate-button', function(evt) {
|
||||
evt.preventDefault();
|
||||
var self = $(this);
|
||||
var target = $('#' + $escape(self.attr('data-target-id')));
|
||||
var startLabel = 'Play';
|
||||
var stopLabel = 'Pause';
|
||||
var loop = self.attr('data-loop') !== undefined &&
|
||||
!/^\s*false\s*$/i.test(self.attr('data-loop'));
|
||||
var animInterval = self.attr('data-interval');
|
||||
if (isNaN(animInterval))
|
||||
animInterval = 1500;
|
||||
else
|
||||
animInterval = +animInterval;
|
||||
|
||||
if (!target.data('animTimer')) {
|
||||
var slider;
|
||||
var timer;
|
||||
|
||||
// Separate code paths:
|
||||
// Backward compatible code for old-style jsliders (Shiny <= 0.10.2.2),
|
||||
// and new-style ionsliders.
|
||||
if (target.hasClass('jslider')) {
|
||||
slider = target.slider();
|
||||
|
||||
// If we're currently at the end, restart
|
||||
if (!slider.canStepNext())
|
||||
slider.resetToStart();
|
||||
|
||||
timer = setInterval(function() {
|
||||
if (loop && !slider.canStepNext()) {
|
||||
slider.resetToStart();
|
||||
}
|
||||
else {
|
||||
slider.stepNext();
|
||||
if (!loop && !slider.canStepNext()) {
|
||||
self.click(); // stop the animation
|
||||
}
|
||||
}
|
||||
}, animInterval);
|
||||
|
||||
} else {
|
||||
slider = target.data('ionRangeSlider');
|
||||
// Single sliders have slider.options.type == "single", and only the
|
||||
// `from` value is used. Double sliders have type == "double", and also
|
||||
// use the `to` value for the right handle.
|
||||
var sliderCanStep = function() {
|
||||
if (slider.options.type === "double")
|
||||
return slider.result.to < slider.result.max;
|
||||
else
|
||||
return slider.result.from < slider.result.max;
|
||||
};
|
||||
var sliderReset = function() {
|
||||
var val = { from: slider.result.min };
|
||||
// Preserve the current spacing for double sliders
|
||||
if (slider.options.type === "double")
|
||||
val.to = val.from + (slider.result.to - slider.result.from);
|
||||
|
||||
slider.update(val);
|
||||
forceIonSliderUpdate(slider);
|
||||
};
|
||||
var sliderStep = function() {
|
||||
// Don't overshoot the end
|
||||
var val = {
|
||||
from: Math.min(slider.result.max, slider.result.from + slider.options.step)
|
||||
};
|
||||
if (slider.options.type === "double")
|
||||
val.to = Math.min(slider.result.max, slider.result.to + slider.options.step);
|
||||
|
||||
slider.update(val);
|
||||
forceIonSliderUpdate(slider);
|
||||
};
|
||||
|
||||
// If we're currently at the end, restart
|
||||
if (!sliderCanStep())
|
||||
sliderReset();
|
||||
|
||||
timer = setInterval(function() {
|
||||
if (loop && !sliderCanStep()) {
|
||||
sliderReset();
|
||||
}
|
||||
else {
|
||||
sliderStep();
|
||||
if (!loop && !sliderCanStep()) {
|
||||
self.click(); // stop the animation
|
||||
}
|
||||
}
|
||||
}, animInterval);
|
||||
}
|
||||
|
||||
target.data('animTimer', timer);
|
||||
self.attr('title', stopLabel);
|
||||
self.addClass('playing');
|
||||
target.data('animating', true);
|
||||
}
|
||||
else {
|
||||
clearTimeout(target.data('animTimer'));
|
||||
target.removeData('animTimer');
|
||||
self.attr('title', startLabel);
|
||||
self.removeClass('playing');
|
||||
target.removeData('animating');
|
||||
}
|
||||
});
|
||||
53
srcts/old/js/input_binding_tabinput.js
Normal file
53
srcts/old/js/input_binding_tabinput.js
Normal file
@@ -0,0 +1,53 @@
|
||||
var bootstrapTabInputBinding = new InputBinding();
|
||||
$.extend(bootstrapTabInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('ul.nav.shiny-tab-input');
|
||||
},
|
||||
getValue: function(el) {
|
||||
var anchor = $(el).find('li:not(.dropdown).active').children('a');
|
||||
if (anchor.length === 1)
|
||||
return this._getTabName(anchor);
|
||||
|
||||
return null;
|
||||
},
|
||||
setValue: function(el, value) {
|
||||
let self = this;
|
||||
let success = false;
|
||||
if (value) {
|
||||
let anchors = $(el).find('li:not(.dropdown)').children('a');
|
||||
anchors.each(function() {
|
||||
if (self._getTabName($(this)) === value) {
|
||||
$(this).tab('show');
|
||||
success = true;
|
||||
return false; // Break out of each()
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (!success) {
|
||||
// This is to handle the case where nothing is selected, e.g. the last tab
|
||||
// was removed using removeTab.
|
||||
$(el).trigger("change");
|
||||
}
|
||||
},
|
||||
getState: function(el) {
|
||||
return { value: this.getValue(el) };
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
if (data.hasOwnProperty('value'))
|
||||
this.setValue(el, data.value);
|
||||
$(el).trigger("change");
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on('change shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding', function(event) {
|
||||
callback();
|
||||
});
|
||||
},
|
||||
unsubscribe: function(el) {
|
||||
$(el).off('.bootstrapTabInputBinding');
|
||||
},
|
||||
_getTabName: function(anchor) {
|
||||
return anchor.attr('data-value') || anchor.text();
|
||||
}
|
||||
});
|
||||
inputBindings.register(bootstrapTabInputBinding, 'shiny.bootstrapTabInput');
|
||||
59
srcts/old/js/input_binding_text.js
Normal file
59
srcts/old/js/input_binding_text.js
Normal file
@@ -0,0 +1,59 @@
|
||||
var textInputBinding = new InputBinding();
|
||||
$.extend(textInputBinding, {
|
||||
find: function(scope) {
|
||||
var $inputs = $(scope).find('input[type="text"], input[type="search"], input[type="url"], input[type="email"]');
|
||||
// selectize.js 0.12.4 inserts a hidden text input with an
|
||||
// id that ends in '-selectized'. The .not() selector below
|
||||
// is to prevent textInputBinding from accidentally picking up
|
||||
// this hidden element as a shiny input (#2396)
|
||||
return $inputs.not('input[type="text"][id$="-selectized"]');
|
||||
},
|
||||
getId: function(el) {
|
||||
return InputBinding.prototype.getId.call(this, el) || el.name;
|
||||
},
|
||||
getValue: function(el) {
|
||||
return el.value;
|
||||
},
|
||||
setValue: function(el, value) {
|
||||
el.value = value;
|
||||
},
|
||||
subscribe: function(el, callback) {
|
||||
$(el).on('keyup.textInputBinding input.textInputBinding', function(event) {
|
||||
callback(true);
|
||||
});
|
||||
$(el).on('change.textInputBinding', function(event) {
|
||||
callback(false);
|
||||
});
|
||||
},
|
||||
unsubscribe: function(el) {
|
||||
$(el).off('.textInputBinding');
|
||||
},
|
||||
receiveMessage: function(el, data) {
|
||||
if (data.hasOwnProperty('value'))
|
||||
this.setValue(el, data.value);
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
|
||||
if (data.hasOwnProperty('placeholder'))
|
||||
el.placeholder = data.placeholder;
|
||||
|
||||
$(el).trigger('change');
|
||||
},
|
||||
getState: function(el) {
|
||||
return {
|
||||
label: this._getLabelNode(el).text(),
|
||||
value: el.value,
|
||||
placeholder: el.placeholder
|
||||
};
|
||||
},
|
||||
getRatePolicy: function() {
|
||||
return {
|
||||
policy: 'debounce',
|
||||
delay: 250
|
||||
};
|
||||
},
|
||||
_getLabelNode: function(el) {
|
||||
return $(el).parent().find('label[for="' + $escape(el.id) + '"]');
|
||||
}
|
||||
});
|
||||
inputBindings.register(textInputBinding, 'shiny.textInput');
|
||||
7
srcts/old/js/input_binding_textarea.js
Normal file
7
srcts/old/js/input_binding_textarea.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var textareaInputBinding = {};
|
||||
$.extend(textareaInputBinding, textInputBinding, {
|
||||
find: function(scope) {
|
||||
return $(scope).find('textarea');
|
||||
}
|
||||
});
|
||||
inputBindings.register(textareaInputBinding, 'shiny.textareaInput');
|
||||
409
srcts/old/js/input_rate.js
Normal file
409
srcts/old/js/input_rate.js
Normal file
@@ -0,0 +1,409 @@
|
||||
var Invoker = function(target, func) {
|
||||
this.target = target;
|
||||
this.func = func;
|
||||
};
|
||||
|
||||
(function() {
|
||||
this.normalCall =
|
||||
this.immediateCall = function() {
|
||||
this.func.apply(this.target, arguments);
|
||||
};
|
||||
}).call(Invoker.prototype);
|
||||
|
||||
var Debouncer = function(target, func, delayMs) {
|
||||
this.target = target;
|
||||
this.func = func;
|
||||
this.delayMs = delayMs;
|
||||
|
||||
this.timerId = null;
|
||||
this.args = null;
|
||||
};
|
||||
|
||||
(function() {
|
||||
this.normalCall = function() {
|
||||
var self = this;
|
||||
|
||||
this.$clearTimer();
|
||||
this.args = arguments;
|
||||
|
||||
this.timerId = setTimeout(function() {
|
||||
// IE8 doesn't reliably clear timeout, so this additional
|
||||
// check is needed
|
||||
if (self.timerId === null)
|
||||
return;
|
||||
self.$clearTimer();
|
||||
self.$invoke();
|
||||
}, this.delayMs);
|
||||
};
|
||||
this.immediateCall = function() {
|
||||
this.$clearTimer();
|
||||
this.args = arguments;
|
||||
this.$invoke();
|
||||
};
|
||||
this.isPending = function() {
|
||||
return this.timerId !== null;
|
||||
};
|
||||
this.$clearTimer = function() {
|
||||
if (this.timerId !== null) {
|
||||
clearTimeout(this.timerId);
|
||||
this.timerId = null;
|
||||
}
|
||||
};
|
||||
this.$invoke = function() {
|
||||
this.func.apply(this.target, this.args);
|
||||
this.args = null;
|
||||
};
|
||||
}).call(Debouncer.prototype);
|
||||
|
||||
var Throttler = function(target, func, delayMs) {
|
||||
this.target = target;
|
||||
this.func = func;
|
||||
this.delayMs = delayMs;
|
||||
|
||||
this.timerId = null;
|
||||
this.args = null;
|
||||
};
|
||||
|
||||
(function() {
|
||||
this.normalCall = function() {
|
||||
var self = this;
|
||||
|
||||
this.args = arguments;
|
||||
if (this.timerId === null) {
|
||||
this.$invoke();
|
||||
this.timerId = setTimeout(function() {
|
||||
// IE8 doesn't reliably clear timeout, so this additional
|
||||
// check is needed
|
||||
if (self.timerId === null)
|
||||
return;
|
||||
self.$clearTimer();
|
||||
if (self.args)
|
||||
self.normalCall.apply(self, self.args);
|
||||
}, this.delayMs);
|
||||
}
|
||||
};
|
||||
this.immediateCall = function() {
|
||||
this.$clearTimer();
|
||||
this.args = arguments;
|
||||
this.$invoke();
|
||||
};
|
||||
this.isPending = function() {
|
||||
return this.timerId !== null;
|
||||
};
|
||||
this.$clearTimer = function() {
|
||||
if (this.timerId !== null) {
|
||||
clearTimeout(this.timerId);
|
||||
this.timerId = null;
|
||||
}
|
||||
};
|
||||
this.$invoke = function() {
|
||||
this.func.apply(this.target, this.args);
|
||||
this.args = null;
|
||||
};
|
||||
}).call(Throttler.prototype);
|
||||
|
||||
// Returns a debounced version of the given function.
|
||||
// Debouncing means that when the function is invoked,
|
||||
// there is a delay of `threshold` milliseconds before
|
||||
// it is actually executed, and if the function is
|
||||
// invoked again before that threshold has elapsed then
|
||||
// the clock starts over.
|
||||
//
|
||||
// For example, if a function is debounced with a
|
||||
// threshold of 1000ms, then calling it 17 times at
|
||||
// 900ms intervals will result in a single execution
|
||||
// of the underlying function, 1000ms after the 17th
|
||||
// call.
|
||||
function debounce(threshold, func) {
|
||||
var timerId = null;
|
||||
var self, args;
|
||||
return function() {
|
||||
self = this;
|
||||
args = arguments;
|
||||
if (timerId !== null) {
|
||||
clearTimeout(timerId);
|
||||
timerId = null;
|
||||
}
|
||||
timerId = setTimeout(function() {
|
||||
// IE8 doesn't reliably clear timeout, so this additional
|
||||
// check is needed
|
||||
if (timerId === null)
|
||||
return;
|
||||
timerId = null;
|
||||
func.apply(self, args);
|
||||
}, threshold);
|
||||
};
|
||||
}
|
||||
|
||||
// Returns a throttled version of the given function.
|
||||
// Throttling means that the underlying function will
|
||||
// be executed no more than once every `threshold`
|
||||
// milliseconds.
|
||||
//
|
||||
// For example, if a function is throttled with a
|
||||
// threshold of 1000ms, then calling it 17 times at
|
||||
// 900ms intervals will result in something like 15
|
||||
// or 16 executions of the underlying function.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function throttle(threshold, func) {
|
||||
var executionPending = false;
|
||||
var timerId = null;
|
||||
var self, args;
|
||||
|
||||
function throttled() {
|
||||
self = null;
|
||||
args = null;
|
||||
if (timerId === null) {
|
||||
// Haven't seen a call recently. Execute now and
|
||||
// start a timer to buffer any subsequent calls.
|
||||
timerId = setTimeout(function() {
|
||||
// When time expires, clear the timer; and if
|
||||
// there has been a call in the meantime, repeat.
|
||||
timerId = null;
|
||||
if (executionPending) {
|
||||
executionPending = false;
|
||||
throttled.apply(self, args);
|
||||
}
|
||||
}, threshold);
|
||||
func.apply(this, arguments);
|
||||
}
|
||||
else {
|
||||
// Something executed recently. Don't do anything
|
||||
// except set up target/arguments to be called later
|
||||
executionPending = true;
|
||||
self = this;
|
||||
args = arguments;
|
||||
}
|
||||
}
|
||||
return throttled;
|
||||
}
|
||||
|
||||
|
||||
// Schedules data to be sent to shinyapp at the next setTimeout(0).
|
||||
// Batches multiple input calls into one websocket message.
|
||||
var InputBatchSender = function(shinyapp) {
|
||||
this.shinyapp = shinyapp;
|
||||
this.timerId = null;
|
||||
this.pendingData = {};
|
||||
this.reentrant = false;
|
||||
this.lastChanceCallback = [];
|
||||
};
|
||||
(function() {
|
||||
this.setInput = function(nameType, value, opts) {
|
||||
this.pendingData[nameType] = value;
|
||||
|
||||
if (!this.reentrant) {
|
||||
if (opts.priority === "event") {
|
||||
this.$sendNow();
|
||||
} else if (!this.timerId) {
|
||||
this.timerId = setTimeout(this.$sendNow.bind(this), 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.$sendNow = function() {
|
||||
if (this.reentrant) {
|
||||
console.trace("Unexpected reentrancy in InputBatchSender!");
|
||||
}
|
||||
|
||||
this.reentrant = true;
|
||||
try {
|
||||
this.timerId = null;
|
||||
$.each(this.lastChanceCallback, (i, callback) => {
|
||||
callback();
|
||||
});
|
||||
var currentData = this.pendingData;
|
||||
this.pendingData = {};
|
||||
this.shinyapp.sendInput(currentData);
|
||||
} finally {
|
||||
this.reentrant = false;
|
||||
}
|
||||
};
|
||||
}).call(InputBatchSender.prototype);
|
||||
|
||||
|
||||
var InputNoResendDecorator = function(target, initialValues) {
|
||||
this.target = target;
|
||||
this.lastSentValues = this.reset(initialValues);
|
||||
};
|
||||
(function() {
|
||||
this.setInput = function(nameType, value, opts) {
|
||||
const { name: inputName, inputType: inputType } = splitInputNameType(nameType);
|
||||
const jsonValue = JSON.stringify(value);
|
||||
|
||||
if (opts.priority !== "event" &&
|
||||
this.lastSentValues[inputName] &&
|
||||
this.lastSentValues[inputName].jsonValue === jsonValue &&
|
||||
this.lastSentValues[inputName].inputType === inputType) {
|
||||
return;
|
||||
}
|
||||
this.lastSentValues[inputName] = { jsonValue, inputType };
|
||||
this.target.setInput(nameType, value, opts);
|
||||
};
|
||||
this.reset = function(values = {}) {
|
||||
// Given an object with flat name-value format:
|
||||
// { x: "abc", "y.shiny.number": 123 }
|
||||
// Create an object in cache format and save it:
|
||||
// { x: { jsonValue: '"abc"', inputType: "" },
|
||||
// y: { jsonValue: "123", inputType: "shiny.number" } }
|
||||
const cacheValues = {};
|
||||
|
||||
for (let inputName in values) {
|
||||
if (values.hasOwnProperty(inputName)) {
|
||||
let { name, inputType } = splitInputNameType(inputName);
|
||||
cacheValues[name] = {
|
||||
jsonValue: JSON.stringify(values[inputName]),
|
||||
inputType: inputType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.lastSentValues = cacheValues;
|
||||
};
|
||||
this.forget = function(name) {
|
||||
delete this.lastSentValues[name];
|
||||
};
|
||||
}).call(InputNoResendDecorator.prototype);
|
||||
|
||||
|
||||
var InputEventDecorator = function(target) {
|
||||
this.target = target;
|
||||
};
|
||||
(function() {
|
||||
this.setInput = function(nameType, value, opts) {
|
||||
var evt = jQuery.Event("shiny:inputchanged");
|
||||
|
||||
const input = splitInputNameType(nameType);
|
||||
evt.name = input.name;
|
||||
evt.inputType = input.inputType;
|
||||
evt.value = value;
|
||||
evt.binding = opts.binding;
|
||||
evt.el = opts.el;
|
||||
evt.priority = opts.priority;
|
||||
|
||||
$(opts.el).trigger(evt);
|
||||
|
||||
if (!evt.isDefaultPrevented()) {
|
||||
let name = evt.name;
|
||||
if (evt.inputType !== '') name += ':' + evt.inputType;
|
||||
|
||||
// Most opts aren't passed along to lower levels in the input decorator
|
||||
// stack.
|
||||
this.target.setInput(name, evt.value, { priority: opts.priority });
|
||||
}
|
||||
};
|
||||
}).call(InputEventDecorator.prototype);
|
||||
|
||||
|
||||
var InputRateDecorator = function(target) {
|
||||
this.target = target;
|
||||
this.inputRatePolicies = {};
|
||||
};
|
||||
(function() {
|
||||
// Note that the first argument of setInput() and setRatePolicy()
|
||||
// are passed both the input name (i.e., inputId) and type.
|
||||
// https://github.com/rstudio/shiny/blob/67d3a/srcjs/init_shiny.js#L111-L126
|
||||
// However, $ensureInit() and $doSetInput() are meant to be passed just
|
||||
// the input name (i.e., inputId), which is why we distinguish between
|
||||
// nameType and name.
|
||||
this.setInput = function(nameType, value, opts) {
|
||||
const {name: inputName} = splitInputNameType(nameType);
|
||||
|
||||
this.$ensureInit(inputName);
|
||||
|
||||
if (opts.priority !== "deferred")
|
||||
this.inputRatePolicies[inputName].immediateCall(nameType, value, opts);
|
||||
else
|
||||
this.inputRatePolicies[inputName].normalCall(nameType, value, opts);
|
||||
};
|
||||
this.setRatePolicy = function(nameType, mode, millis) {
|
||||
const {name: inputName} = splitInputNameType(nameType);
|
||||
|
||||
if (mode === 'direct') {
|
||||
this.inputRatePolicies[inputName] = new Invoker(this, this.$doSetInput);
|
||||
}
|
||||
else if (mode === 'debounce') {
|
||||
this.inputRatePolicies[inputName] = new Debouncer(this, this.$doSetInput, millis);
|
||||
}
|
||||
else if (mode === 'throttle') {
|
||||
this.inputRatePolicies[inputName] = new Throttler(this, this.$doSetInput, millis);
|
||||
}
|
||||
};
|
||||
this.$ensureInit = function(name) {
|
||||
if (!(name in this.inputRatePolicies))
|
||||
this.setRatePolicy(name, 'direct');
|
||||
};
|
||||
this.$doSetInput = function(nameType, value, opts) {
|
||||
this.target.setInput(nameType, value, opts);
|
||||
};
|
||||
}).call(InputRateDecorator.prototype);
|
||||
|
||||
|
||||
var InputDeferDecorator = function(target) {
|
||||
this.target = target;
|
||||
this.pendingInput = {};
|
||||
};
|
||||
(function() {
|
||||
this.setInput = function(nameType, value, opts) {
|
||||
if (/^\./.test(nameType))
|
||||
this.target.setInput(nameType, value, opts);
|
||||
else
|
||||
this.pendingInput[nameType] = { value, opts };
|
||||
};
|
||||
this.submit = function() {
|
||||
for (var nameType in this.pendingInput) {
|
||||
if (this.pendingInput.hasOwnProperty(nameType)) {
|
||||
let { value, opts } = this.pendingInput[nameType];
|
||||
this.target.setInput(nameType, value, opts);
|
||||
}
|
||||
}
|
||||
};
|
||||
}).call(InputDeferDecorator.prototype);
|
||||
|
||||
|
||||
const InputValidateDecorator = function(target) {
|
||||
this.target = target;
|
||||
};
|
||||
(function() {
|
||||
this.setInput = function(nameType, value, opts) {
|
||||
if (!nameType)
|
||||
throw "Can't set input with empty name.";
|
||||
|
||||
opts = addDefaultInputOpts(opts);
|
||||
|
||||
this.target.setInput(nameType, value, opts);
|
||||
};
|
||||
}).call(InputValidateDecorator.prototype);
|
||||
|
||||
|
||||
// Merge opts with defaults, and return a new object.
|
||||
function addDefaultInputOpts(opts) {
|
||||
|
||||
opts = $.extend({
|
||||
priority: "immediate",
|
||||
binding: null,
|
||||
el: null
|
||||
}, opts);
|
||||
|
||||
if (opts && typeof(opts.priority) !== "undefined") {
|
||||
switch (opts.priority) {
|
||||
case "deferred":
|
||||
case "immediate":
|
||||
case "event":
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unexpected input value mode: '" + opts.priority + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
|
||||
function splitInputNameType(nameType) {
|
||||
const name2 = nameType.split(':');
|
||||
return {
|
||||
name: name2[0],
|
||||
inputType: name2.length > 1 ? name2[1] : ''
|
||||
};
|
||||
}
|
||||
66
srcts/old/js/modal.js
Normal file
66
srcts/old/js/modal.js
Normal file
@@ -0,0 +1,66 @@
|
||||
exports.modal = {
|
||||
|
||||
// Show a modal dialog. This is meant to handle two types of cases: one is
|
||||
// that the content is a Bootstrap modal dialog, and the other is that the
|
||||
// content is non-Bootstrap. Bootstrap modals require some special handling,
|
||||
// which is coded in here.
|
||||
show: function({ html='', deps=[] } = {}) {
|
||||
|
||||
// If there was an existing Bootstrap modal, then there will be a modal-
|
||||
// backdrop div that was added outside of the modal wrapper, and it must be
|
||||
// removed; otherwise there can be multiple of these divs.
|
||||
$('.modal-backdrop').remove();
|
||||
|
||||
// Get existing wrapper DOM element, or create if needed.
|
||||
let $modal = $('#shiny-modal-wrapper');
|
||||
if ($modal.length === 0) {
|
||||
$modal = $('<div id="shiny-modal-wrapper"></div>');
|
||||
$(document.body).append($modal);
|
||||
|
||||
// If the wrapper's content is a Bootstrap modal, then when the inner
|
||||
// modal is hidden, remove the entire thing, including wrapper.
|
||||
$modal.on('hidden.bs.modal', function(e) {
|
||||
if (e.target === $("#shiny-modal")[0]) {
|
||||
exports.unbindAll($modal);
|
||||
$modal.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$modal.on('keydown.shinymodal', function(e) {
|
||||
// If we're listening for Esc, don't let the event propagate. See
|
||||
// https://github.com/rstudio/shiny/issues/1453. The value of
|
||||
// data("keyboard") needs to be checked inside the handler, because at
|
||||
// the time that $modal.on() is called, the $("#shiny-modal") div doesn't
|
||||
// yet exist.
|
||||
if ($("#shiny-modal").data("keyboard") === false)
|
||||
return;
|
||||
|
||||
if (e.keyCode === 27) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Set/replace contents of wrapper with html.
|
||||
exports.renderContent($modal, { html: html, deps: deps });
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
const $modal = $('#shiny-modal-wrapper');
|
||||
|
||||
$modal.off('keydown.shinymodal');
|
||||
|
||||
// Look for a Bootstrap modal and if present, trigger hide event. This will
|
||||
// trigger the hidden.bs.modal callback that we set in show(), which unbinds
|
||||
// and removes the element.
|
||||
if ($modal.find('.modal').length > 0) {
|
||||
$modal.find('.modal').modal('hide');
|
||||
|
||||
} else {
|
||||
// If not a Bootstrap modal dialog, simply unbind and remove it.
|
||||
exports.unbindAll($modal);
|
||||
$modal.remove();
|
||||
}
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user