Compare commits

...

59 Commits

Author SHA1 Message Date
wch
f06a2ec9ee devtools::document() (GitHub Actions) 2022-06-10 15:00:23 +00:00
Winston Chang
26f032d7f8 Code formatting 2022-06-10 09:55:07 -05:00
Joe Cheng
864d0112cb Update NEWS 2022-06-10 09:55:07 -05:00
Joe Cheng
0813478276 Tweak docs, rebuild Rd files 2022-06-10 09:55:07 -05:00
Neutron3529
3aa13401b5 make mathjax configurable
Provide two option, `MathJax_url` and `MathJax_config`, to enable the modification of the default mathjax.js.
Since the response of `mathjax.rstudio.com` could be slower in some regions, it is better to provide a default value and a configure technique to enable the modification of the default mathjax url.

change the names of the two newly added options

update the doc of newly added shiny.mathjax.*
2022-06-10 09:55:07 -05:00
Ryan Barnard
9fcc1fe8ad Fixed automatic guessing of Content-Type in downloadHandler (#3393)
* Set default downloadHandler contentType to NULL.

The change from %OR% to %||% broke automatic guessing of content type
since `NA %||% ...` evaluates to `NA`. Setting the default contentType
to NULL restores the previous behavior of automatically setting the
content type based on the file extension.

* Updated NEWS.md: downloadHandler contentType fix.

* Update NEWS.md

Co-authored-by: Winston Chang <winston@stdout.org>
Co-authored-by: Barret Schloerke <barret@rstudio.com>
2022-06-10 09:39:00 -05:00
Barret Schloerke
5d30b55372 Spelling defintion -> definition (#3649) 2022-06-09 16:52:27 -04:00
Carson Sievert
78d77ce373 insertUI() now supports execution of <script> (#3630) 2022-05-10 11:43:21 -05:00
Joe Cheng
2cae04186b Merge pull request #3628 from rstudio/joe/feature/autoreload-custom-url
Add ability for autoreload ws to be at a custom URL
2022-05-04 16:35:39 -07:00
Joe Cheng
59bddea1e9 Use external, not internal, sourcemaps for extras 2022-05-04 16:23:48 -07:00
Joe Cheng
d6bd3d9f9b Add ability for autoreload ws to be at a custom URL 2022-05-04 09:05:53 -07:00
Joe Cheng
8eb7b056f2 devmode should activate autoreload (#3620)
devmode should activate autoreload

It said it didn't, but until this commit, it appeared not to
2022-04-27 13:51:02 -07:00
Barret Schloerke
40ae9a903e Spelling (#3618) 2022-04-23 12:25:11 -04:00
Barret Schloerke
5b6c80d4b2 Update shinyAppTemplate() content to use {shinytest2} (#3599) 2022-04-22 16:10:11 -04:00
Dean Attali
fd7518018c Update internal docs: reexports.yml -> reexports.json (#3522) 2022-03-11 11:59:35 -05:00
Kathryn Doering
5c03326a8c Use HEAD for ref instead of master in runGitHub() (#3564)
Co-authored-by: Kathryn Doering <kathryn.doering@noaa.gov>
2022-02-14 15:53:33 -05:00
Barret Schloerke
2c82ee0235 Bump dev version (#3588) 2022-02-14 15:24:47 -05:00
Barret Schloerke
ac84be956a Opt-in to C collate order in test snapshots (#3515) 2022-02-14 14:12:25 -05:00
Winston Chang
0fb154cc1e Trigger input event even when there is no input binding (#3584)
Co-authored-by: Winston Chang <winston@stdout.org>
Co-authored-by: Barret Schloerke <schloerke@gmail.com>
2022-02-14 13:45:59 -05:00
Carson Sievert
837e8d33f6 Update stack trace test expectations (#3550) 2022-02-14 13:14:10 -05:00
Winston Chang
3365bfc395 Merge pull request #3583 from ismirsehregal/main 2022-02-09 17:27:18 -06:00
ismirsehregal
135fe21278 Update runapp.R
Fixed typo
2022-02-09 15:11:34 +01:00
Carson Sievert
fc7e237000 Pass args from knit_print.shiny.render.function() down to it's use of the knit_print() generic (#3569)
Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
Co-authored-by: Barret Schloerke <barret@rstudio.com>
Co-authored-by: schloerke <schloerke@users.noreply.github.com>
2022-01-11 12:21:18 -06:00
Winston Chang
de8134742d Update NEWS 2022-01-11 10:41:19 -06:00
Winston Chang
f814034835 Merge pull request #3570 from romainfrancois/shinyActionButtonValue 2022-01-11 10:38:28 -06:00
Romain Francois
6d9fad29f3 put the extension class on the left.
from: https://github.com/tidyverse/dplyr/issues/6154
2022-01-11 12:49:48 +01:00
Lionel Henry
313ae9044d Handle chained errors (#3567)
Closes tidyverse/dplyr#5552
Part of #3566
2022-01-10 18:33:14 -08:00
Barret Schloerke
9389160af0 Cache the reactlog version found. Remove mustWork argument to system_file(). (#3554) 2021-12-08 15:30:37 -05:00
Barret Schloerke
6a7ffeff68 master -> main; use shiny-workflows (#3535) 2021-11-19 17:50:57 -05:00
Winston Chang
bc6ff57cb7 Remove old unused JS files (#3547) 2021-11-17 22:01:19 -06:00
Carson Sievert
b52b9e4520 Some UI-related speed improvements (#3541) 2021-11-17 16:25:27 -06:00
Winston Chang
fb71ab6146 Editor configuration improvements 2021-11-16 15:38:21 -06:00
Carson Sievert
d8c7a634ff Fix failing tabPanel() test broken by testthat 3.1.0 (#3543) 2021-11-05 16:45:16 -05:00
Winston Chang
396dd2632e Merge pull request #3537 from rstudio/htmldep 2021-11-05 15:56:09 -05:00
Winston Chang
c11875a5f0 Fix appending to list 2021-11-05 15:52:14 -05:00
Winston Chang
2e599faf1f Fix comment 2021-11-05 15:47:23 -05:00
Winston Chang
a5a8385420 Use all_files=FALSE for html dependencies
This commit sets all_files=FALSE for html dependencies in www/shared.
2021-11-04 11:24:46 -05:00
Winston Chang
33ed698e5b Remove “file =“ 2021-11-04 11:15:01 -05:00
Winston Chang
ed547fdf40 Rebuild JS files 2021-11-03 21:28:15 -05:00
Winston Chang
0b1c35c92b Use .map() and .forEeach() instead of for loops 2021-11-03 21:28:11 -05:00
Winston Chang
d304bdf333 Rename HtmlDepSimplified to HtmlDepNormalized 2021-11-03 20:54:53 -05:00
Winston Chang
a9255e6b12 Extract jquery-ui dependency into function 2021-11-03 20:54:53 -05:00
Winston Chang
45429fb798 Streamline code for adding scripts and attachments 2021-11-03 13:56:37 -05:00
Winston Chang
1206d1d3ba Use array of objects to represent meta tags 2021-11-03 13:56:37 -05:00
Winston Chang
af44a447a1 Fix type specification of HTML dependency meta values 2021-11-03 11:40:49 -05:00
Winston Chang
d7fb6d1793 Fixes for strict null checks 2021-11-03 11:40:49 -05:00
Winston Chang
cb0083adb2 Update VSCode editor settings 2021-11-03 11:40:49 -05:00
Winston Chang
77bae68f26 Update NEWS 2021-11-03 11:40:48 -05:00
Winston Chang
e9f8b4d552 Use htmlDependencies with ‘src=file’ for showcase mode 2021-11-03 11:39:28 -05:00
Winston Chang
aee6b74cfb Minor cleanups 2021-11-03 11:39:28 -05:00
Winston Chang
29b6b03297 Extract restyle code into separate code path 2021-11-03 11:39:28 -05:00
Winston Chang
b5ebd8a645 Fix attachment handling and add more specific types 2021-11-03 11:39:28 -05:00
Winston Chang
356ba8c5a1 Add simplifyHtmlDependency() function 2021-11-03 11:39:28 -05:00
Winston Chang
5aa5cb1794 Make dep.src.href field optional 2021-11-03 11:39:28 -05:00
Winston Chang
09c609e417 Bump version to 1.7.1.9001 2021-11-03 11:39:28 -05:00
Winston Chang
10e7d11846 Allow array of attributes for stylesheets 2021-11-03 11:39:28 -05:00
Winston Chang
4e442312a7 Serve HTML dependencies from dynamic paths 2021-11-03 11:39:28 -05:00
Winston Chang
8ea97df3f2 Fix getid access of data-input-id attribute (#3538) 2021-11-03 11:38:54 -05:00
Winston Chang
a8c14dab96 Bump version to 1.7.1.9000 2021-10-04 11:09:26 -05:00
182 changed files with 2538 additions and 10327 deletions

12
.github/shiny-workflows/routine.sh vendored Normal file
View File

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

View File

@@ -1,139 +1,23 @@
# NOTE: This workflow is overkill for most R packages
# check-standard.yaml is likely a better choice
# usethis::use_github_action("check-standard") will install it.
# Workflow derived from https://github.com/rstudio/shiny-workflows
#
# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag.
# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions
# NOTE: This Shiny team GHA workflow is overkill for most R packages.
# For most R packages it is better to use https://github.com/r-lib/actions
on:
push:
branches:
- main
- master
branches: [main, rc-**]
pull_request:
branches:
- main
- master
- rc-v**
branches: [main]
schedule:
- cron: '0 5 * * 1' # every monday
name: R-CMD-check
name: Package checks
jobs:
rversions:
name: R Versions
runs-on: ubuntu-latest
outputs:
devel: ${{ steps.devel.outputs.installed-r-version }}
release: ${{ steps.release.outputs.installed-r-version }}
oldrel1: ${{ steps.oldrel1.outputs.installed-r-version }}
oldrel2: ${{ steps.oldrel2.outputs.installed-r-version }}
oldrel3: ${{ steps.oldrel3.outputs.installed-r-version }}
oldrel4: ${{ steps.oldrel4.outputs.installed-r-version }}
steps:
- { name: devel, uses: r-lib/actions/setup-r@master, id: devel, with: { r-version: devel, install-r: false }}
- { name: release, uses: r-lib/actions/setup-r@master, id: release, with: { r-version: release, install-r: false }}
- { name: oldrel/1, uses: r-lib/actions/setup-r@master, id: oldrel1, with: { r-version: oldrel/1, install-r: false }}
- { name: oldrel/2, uses: r-lib/actions/setup-r@master, id: oldrel2, with: { r-version: oldrel/2, install-r: false }}
- { name: oldrel/3, uses: r-lib/actions/setup-r@master, id: oldrel3, with: { r-version: oldrel/3, install-r: false }}
- { name: oldrel/4, uses: r-lib/actions/setup-r@master, id: oldrel4, with: { r-version: oldrel/4, install-r: false }}
website:
uses: rstudio/shiny-workflows/.github/workflows/website.yaml@v1
routine:
uses: rstudio/shiny-workflows/.github/workflows/routine.yaml@v1
with:
node-version: "14.x"
R-CMD-check:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
needs:
- rversions
strategy:
fail-fast: false
matrix:
config:
- {os: macOS-latest, r: '${{ needs.rversions.outputs.release }}'}
- {os: windows-latest, r: '${{ needs.rversions.outputs.release }}'}
- {os: windows-latest, r: '3.6'}
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.devel }}', http-user-agent: "release" }
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.release }}'}
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.oldrel1 }}'}
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.oldrel2 }}'}
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.oldrel3 }}'}
- {os: ubuntu-20.04, r: '${{ needs.rversions.outputs.oldrel4 }}'}
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.devel }}', http-user-agent: "release" }
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.release }}'}
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.oldrel1 }}'}
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.oldrel2 }}'}
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.oldrel3 }}'}
- {os: ubuntu-18.04, r: '${{ needs.rversions.outputs.oldrel4 }}'}
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
steps:
# https://github.com/actions/checkout/issues/135
- name: Set git to use LF
if: runner.os == 'Windows'
run: |
git config --system core.autocrlf false
git config --system core.eol lf
- uses: actions/checkout@v2
- uses: r-lib/actions/setup-r@v1
id: install-r
with:
r-version: ${{ matrix.config.r }}
http-user-agent: ${{ matrix.config.http-user-agent }}
use-public-rspm: true
- uses: r-lib/actions/setup-pandoc@v1
- uses: r-lib/actions/setup-r-dependencies@v1
with:
extra-packages: rcmdcheck
# xquartz and cairo are needed for Cairo package.
# harfbuzz and fribidi are needed for textshaping package.
- name: Mac systemdeps
if: runner.os == 'macOS'
run: |
brew install --cask xquartz
brew install cairo
brew install harfbuzz fribidi
- name: Find PhantomJS path
id: phantomjs
run: |
echo "::set-output name=path::$(Rscript -e 'cat(shinytest:::phantom_paths()[[1]])')"
- name: Cache PhantomJS
uses: actions/cache@v2
with:
path: ${{ steps.phantomjs.outputs.path }}
key: ${{ matrix.config.os }}-phantomjs
restore-keys: ${{ matrix.config.os }}-phantomjs
- name: Install PhantomJS
run: >
Rscript
-e "if (!shinytest::dependenciesInstalled()) shinytest::installDependencies()"
- name: Check
env:
_R_CHECK_CRAN_INCOMING_: false
_R_CHECK_FORCE_SUGGESTS_: ${{ matrix.config.r != 'devel' }}
run: |
options(crayon.enabled = TRUE)
rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check")
shell: Rscript {0}
- name: Show testthat output
if: always()
run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true
shell: bash
- name: Upload check results
if: failure()
uses: actions/upload-artifact@main
with:
name: ${{ matrix.config.os }}-r${{ matrix.config.r }}-results
path: check
uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1

View File

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

View File

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

16
.vscode/settings.json vendored
View File

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

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 1.7.1
Version: 1.7.1.9003
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"),
@@ -105,7 +105,6 @@ Suggests:
ggplot2,
reactlog (>= 1.0.0),
magrittr,
shinytest (>= 1.4.0.9003),
yaml,
future,
dygraphs,
@@ -189,6 +188,7 @@ Collate:
'shinywrappers.R'
'showcase.R'
'snapshot.R'
'staticimports.R'
'tar.R'
'test-export.R'
'test-server.R'
@@ -201,8 +201,10 @@ Collate:
'version_selectize.R'
'version_strftime.R'
'viewer.R'
RoxygenNote: 7.1.2
RoxygenNote: 7.2.0
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RdMacros: lifecycle
Config/testthat/edition: 3
Config/Needs/check:
rstudio/shinytest2

37
NEWS.md
View File

@@ -1,3 +1,38 @@
shiny development
================
## Full changelog
### Breaking changes
### Minor new features and improvements
* Shiny's internal HTML dependencies are now mounted dynamically instead of statically. (#3537)
* HTML dependencies that are sent to dynamic UI now have better type checking, and no longer require a `dep.src.href` field. (#3537)
* Default for `ref` input in `runGithub()` changed from `"master"` to `"HEAD"`. (#3346)
* When taking a test snapshot, the sort order of the json keys of the `input`, `output`, and `export` fields is currently sorted using the locale of the machine. This can lead to inconsistent test snapshot results. To opt-in to a consistent ordering of snapshot fields with `{shinytest}`, please set the global option `options(shiny.snapshotsortc = TRUE)`. `{shinytest2}` users do not need to set this value. (#3515)
* The auto-reload feature (`options(shiny.autoreload=TRUE)`) was not being activated by `devmode(TRUE)`, despite a console message asserting that it was. (#3620)
* Add `shiny.mathjax.url` and `shiny.mathjax.config` options for configuring the MathJax URL used by `withMathJax`. Thanks, @Neutron3529! (#3639)
### Bug fixes
* Closed tidyverse/dplyr#5552: Compatibility of dplyr 1.0 (and rlang chained errors in general) with `req()`, `validate()`, and friends.
* Closed #1545: `insertUI()` now executes `<script>` tags. (#3630)
* Closed #2955: Input and output bindings previously attempted to use `el['data-input-id']`, but that never worked. They now use `el.getAttribute('data-input-id')` instead. (#3538)
* Closed tidyverse/dplyr#6154: Values from an `actionButton()` had S3 classes in the incorrect order.
* Fixed a bug where updating an input value without a corresponding Input binding element did not trigger a JavaScript `shiny:inputchanged` event. Now, if no Input binding element is found, the `shiny:inputchanged` event is triggered on `window.document`. (#3584)
* Restored the previous behavior of automatically guessing the `Content-Type` header for `downloadHandler` functions when no explicit `contentType` argument is supplied. (#3393)
shiny 1.7.1
===========
@@ -40,7 +75,7 @@ shiny 1.7.0
### Other improvements
* Shiny's core JavaScript code was converted to TypeScript. For the latest development information, please see the [README.md in `./srcts`](https://github.com/rstudio/shiny/tree/master/srcts). (#3296)
* Shiny's core JavaScript code was converted to TypeScript. For the latest development information, please see the [README.md in `./srcts`](https://github.com/rstudio/shiny/tree/v1.7.0/srcts). (#3296)
* Switched from `digest::digest()` to `rlang::hash()` for hashing. (#3264)

View File

@@ -10,8 +10,7 @@
#' 2: app.R : Main application file
#' 3: R/example.R : Helper file with R code
#' 4: R/example-module.R : Example module
#' 5: tests/shinytest/ : Tests using the shinytest package
#' 6: tests/testthat/ : Tests using the testthat package
#' 5: tests/testthat/ : Tests using the testthat and shinytest2 package
#' ```
#'
#' If option 1 is selected, the full example application including the
@@ -24,13 +23,11 @@
#' | |- example-module.R
#' | `- example.R
#' `- tests
#' |- shinytest.R
#' |- shinytest
#' | `- mytest.R
#' |- testthat.R
#' `- testthat
#' |- test-examplemodule.R
#' |- test-server.R
#' |- test-shinytest2.R
#' `- test-sort.R
#' ```
#'
@@ -45,20 +42,20 @@
#' * `tests/` contains various tests for the application. You may
#' choose to use or remove any of them. They can be executed by the
#' [runTests()] function.
#' * `tests/shinytest.R` is a test runner for test files in the
#' `tests/shinytest/` directory.
#' * `tests/shinytest/mytest.R` is a test that uses the
#' [shinytest](https://rstudio.github.io/shinytest/) package to do
#' snapshot-based testing.
#' * `tests/testthat.R` is a test runner for test files in the
#' `tests/testthat/` directory using the [testthat](https://testthat.r-lib.org/) package.
#' `tests/testthat/` directory using the
#' [shinytest2](https://rstudio.github.io/shinytest2/reference/test_app.html)
#' package.
#' * `tests/testthat/test-examplemodule.R` is a test for an application's module server function.
#' * `tests/testthat/test-server.R` is a test for the application's server code
#' * `tests/testthat/test-shinytest2.R` is a test that uses the
#' [shinytest2](https://rstudio.github.io/shinytest2/) package to do
#' snapshot-based testing.
#' * `tests/testthat/test-sort.R` is a test for a supporting function in the `R/` directory.
#'
#' @param path Path to create new shiny application template.
#' @param examples Either one of "default", "ask", "all", or any combination of
#' "app", "rdir", "module", "shinytest", and "testthat". In an
#' "app", "rdir", "module", and "tests". In an
#' interactive session, "default" falls back to "ask"; in a non-interactive
#' session, "default" falls back to "all". With "ask", this function will
#' prompt the user to select which template items will be added to the new app
@@ -79,15 +76,19 @@ shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
# =======================================================
choices <- c(
app = "app.R : Main application file",
rdir = "R/example.R : Helper file with R code",
module = "R/example-module.R : Example module",
shinytest = "tests/shinytest/ : Tests using the shinytest package",
testthat = "tests/testthat/ : Tests using the testthat package"
app = "app.R : Main application file",
rdir = "R/example.R : Helper file with R code",
module = "R/example-module.R : Example module",
tests = "tests/testthat/ : Tests using {testthat} and {shinytest2}"
)
# Support legacy value
examples[examples == "shinytest"] <- "tests"
examples[examples == "testthat"] <- "tests"
examples <- unique(examples)
if (identical(examples, "default")) {
if (interactive()) {
if (rlang::is_interactive()) {
examples <- "ask"
} else {
examples <- "all"
@@ -124,18 +125,8 @@ shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
return(invisible())
}
if ("shinytest" %in% examples) {
if (!is_available("shinytest", "1.4.0"))
{
message(
"The tests/shinytest directory needs shinytest 1.4.0 or later to work properly."
)
if (is_available("shinytest")) {
message("You currently have shinytest ",
utils::packageVersion("shinytest"), " installed.")
}
}
if ("tests" %in% examples) {
rlang::check_installed("shinytest2", "for {testthat} tests to work as expected")
}
# =======================================================
@@ -152,7 +143,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
@@ -208,16 +199,13 @@ shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
}
# Copy the files for a tests/ subdirectory
copy_test_dir <- function(name) {
copy_test_dir <- function() {
files <- dir(template_path("tests"), recursive = TRUE)
# Note: This is not the same as using dir(pattern = "^shinytest"), since
# that will not match files inside of shinytest/.
files <- files[grepl(paste0("^", name), files)]
# Filter out files that are not module files in the R directory.
if (! "rdir" %in% examples) {
# find all files in the testthat folder that are not module or server files
is_r_folder_file <- (!grepl("module|server", basename(files))) & (dirname(files) == "testthat")
is_r_folder_file <- !grepl("module|server|shinytest2|testthat", basename(files))
files <- files[!is_r_folder_file]
}
@@ -282,12 +270,10 @@ shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
copy_file(file.path("R", module_files))
}
# tests/ dir
if ("shinytest" %in% examples) {
copy_test_dir("shinytest")
}
if ("testthat" %in% examples) {
copy_test_dir("testthat")
# tests/testthat dir
if ("tests" %in% examples) {
copy_test_dir()
}
invisible()
}

View File

@@ -6,7 +6,7 @@
#' @param sidebarPanel The [sidebarPanel] containing input controls
#' @param mainPanel The [mainPanel] containing outputs
#' @keywords internal
#' @return A UI defintion that can be passed to the [shinyUI] function
#' @return A UI definition that can be passed to the [shinyUI] function
#' @export
pageWithSidebar <- function(headerPanel,
sidebarPanel,

View File

@@ -13,7 +13,7 @@
#' 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.
#' @return A UI definition 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
@@ -111,7 +111,7 @@ fluidRow <- function(...) {
#' @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.
#' @return A UI definition 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

View File

@@ -24,7 +24,7 @@ NULL
#' 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 defintion that can be passed to the [shinyUI] function.
#' @return A UI definition that can be passed to the [shinyUI] function.
#'
#' @note The `basicPage` function is deprecated, you should use the
#' [fluidPage()] function instead.
@@ -138,8 +138,7 @@ bs_theme_deps <- function(theme) {
}
is_bs_theme <- function(x) {
is_available("bslib", "0.2.0.9000") &&
bslib::is_bs_theme(x)
bslib::is_bs_theme(x)
}
#' Obtain Shiny's Bootstrap Sass theme
@@ -215,11 +214,10 @@ registerThemeDependency <- function(func) {
bootstrapDependency <- function(theme) {
htmlDependency(
"bootstrap", bootstrapVersion,
c(
href = "shared/bootstrap",
file = system.file("www/shared/bootstrap", package = "shiny")
),
"bootstrap",
bootstrapVersion,
src = "www/shared/bootstrap",
package = "shiny",
script = c(
"js/bootstrap.min.js",
# Safely adding accessibility plugin for screen readers and keyboard users; no break for sighted aspects (see https://github.com/paypal/bootstrap-accessibility-plugin)
@@ -387,7 +385,7 @@ collapseSizes <- function(padding) {
#' @inheritParams bootstrapPage
#' @param icon Optional icon to appear on a `navbarMenu` tab.
#'
#' @return A UI defintion that can be passed to the [shinyUI] function.
#' @return A UI definition that can be passed to the [shinyUI] function.
#'
#' @details The `navbarMenu` function can be used to create an embedded
#' menu within the navbar that in turns includes additional tabPanels (see
@@ -1109,11 +1107,17 @@ tableOutput <- function(outputId) {
dataTableDependency <- list(
htmlDependency(
"datatables", "1.10.5", c(href = "shared/datatables"),
"datatables",
"1.10.5",
src = "www/shared/datatables",
package = "shiny",
script = "js/jquery.dataTables.min.js"
),
htmlDependency(
"datatables-bootstrap", "1.10.5", c(href = "shared/datatables"),
"datatables-bootstrap",
"1.10.5",
src = "www/shared/datatables",
package = "shiny",
stylesheet = c("css/dataTables.bootstrap.css", "css/dataTables.extra.css"),
script = "js/dataTables.bootstrap.js"
)
@@ -1153,7 +1157,7 @@ dataTableOutput <- function(outputId) {
htmlOutput <- function(outputId, inline = FALSE,
container = if (inline) span else div, ...)
{
if (anyUnnamed(list(...))) {
if (any_unnamed(list(...))) {
warning("Unnamed elements in ... will be replaced with dynamic UI.")
}
container(id = outputId, class="shiny-html-output", ...)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -612,7 +612,7 @@ getGgplotCoordmap <- function(p, width, height, res) {
find_panel_info <- function(b) {
# Structure of ggplot objects changed after 2.1.0. After 2.2.1, there was a
# an API for extracting the necessary information.
ggplot_ver <- utils::packageVersion("ggplot2")
ggplot_ver <- get_package_version("ggplot2")
if (ggplot_ver > "2.2.1") {
find_panel_info_api(b)

View File

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

View File

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

View File

@@ -181,7 +181,7 @@ registerInputHandler("shiny.datetime", function(val, ...){
registerInputHandler("shiny.action", function(val, shinysession, name) {
# mark up the action button value with a special class so we can recognize it later
class(val) <- c(class(val), "shinyActionButtonValue")
class(val) <- c("shinyActionButtonValue", class(val))
val
})

View File

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

View File

@@ -94,6 +94,10 @@ getShinyOption <- function(name, default = NULL) {
#' numbers to JSON format to send to the client web browser.}
#' \item{shiny.launch.browser (defaults to `interactive()`)}{A boolean which controls the default behavior
#' when an app is run. See [runApp()] for more information.}
#' \item{shiny.mathjax.url (defaults to `"https://mathjax.rstudio.com/latest/MathJax.js"`)}{
#' The URL that should be used to load MathJax, via [withMathJax()].}
#' \item{shiny.mathjax.config (defaults to `"config=TeX-AMS-MML_HTMLorMML"`)}{The querystring
#' used to load MathJax, via [withMathJax()].}
#' \item{shiny.maxRequestSize (defaults to 5MB)}{This is a number which specifies the maximum
#' web request size, which serves as a size limit for file uploads.}
#' \item{shiny.minified (defaults to `TRUE`)}{By default
@@ -125,6 +129,9 @@ getShinyOption <- function(name, default = NULL) {
#' console.}
#' \item{shiny.testmode (defaults to `FALSE`)}{If `TRUE`, then various features for testing Shiny
#' applications are enabled.}
#' \item{shiny.snapshotsortc (defaults to `FALSE`)}{If `TRUE`, test snapshot keys
#' for \pkg{shinytest} will be sorted consistently using the C locale. Snapshots
#' retrieved by \pkg{shinytest2} will always sort using the C locale.}
#' \item{shiny.trace (defaults to `FALSE`)}{Print messages sent between the R server and the web
#' browser client to the R console. This is useful for debugging. Possible
#' values are `"send"` (only print messages sent to the client),

View File

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

View File

@@ -193,7 +193,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
staticPaths <- list()
}
fallbackWWWDir <- system.file("www-dir", package = "shiny")
fallbackWWWDir <- system_file("www-dir", package = "shiny")
serverSource <- cachedFuncWithFile(appDir, "server.R", case.sensitive = FALSE,
function(serverR) {
@@ -286,7 +286,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
#
# The return value is a function that halts monitoring when called.
initAutoReloadMonitor <- function(dir) {
if (!getOption("shiny.autoreload", FALSE)) {
if (!get_devmode_option("shiny.autoreload", FALSE)) {
return(function(){})
}
@@ -339,7 +339,7 @@ initAutoReloadMonitor <- function(dir) {
#' @param appDir The application directory. If `appDir` is `NULL` or
#' not supplied, the nearest enclosing directory that is a Shiny app, starting
#' with the current directory, is used.
#' @param renv The environmeny in which the files in the `R/` directory should
#' @param renv The environment in which the files in the `R/` directory should
#' be evaluated.
#' @param globalrenv The environment in which `global.R` should be evaluated. If
#' `NULL`, `global.R` will not be evaluated at all.
@@ -455,7 +455,7 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
staticPaths <- list()
}
fallbackWWWDir <- system.file("www-dir", package = "shiny")
fallbackWWWDir <- system_file("www-dir", package = "shiny")
oldwd <- NULL
monitorHandle <- NULL

View File

@@ -14,7 +14,11 @@ NULL
#' # now we can just write "static" content without withMathJax()
#' div("more math here $$\\sqrt{2}$$")
withMathJax <- function(...) {
path <- 'https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
path <- paste0(
getOption("shiny.mathjax.url", "https://mathjax.rstudio.com/latest/MathJax.js"),
"?",
getOption("shiny.mathjax.config", "config=TeX-AMS-MML_HTMLorMML")
)
tagList(
tags$head(
singleton(tags$script(src = path, type = 'text/javascript'))
@@ -39,7 +43,7 @@ renderPage <- function(ui, showcase=0, testMode=FALSE) {
# Put the body into the default template
ui <- htmlTemplate(
system.file("template", "default.html", package = "shiny"),
system_file("template", "default.html", package = "shiny"),
lang = lang,
body = ui,
# this template is a complete HTML document
@@ -55,8 +59,14 @@ renderPage <- function(ui, showcase=0, testMode=FALSE) {
if (testMode) {
# Add code injection listener if in test mode
shiny_deps[[length(shiny_deps) + 1]] <-
htmlDependency("shiny-testmode", shinyPackageVersion(),
c(href="shared"), script = "shiny-testmode.js")
htmlDependency(
"shiny-testmode",
get_package_version("shiny"),
src = "www/shared",
package = "shiny",
script = "shiny-testmode.js",
all_files = FALSE
)
}
html <- renderDocument(ui, shiny_deps, processDep = createWebDependency)
@@ -68,23 +78,19 @@ jqueryDependency <- function() {
if (version == 3) {
return(htmlDependency(
"jquery", version_jquery,
src = c(
href = "shared",
file = "www/shared"
),
src = "www/shared",
package = "shiny",
script = "jquery.min.js"
script = "jquery.min.js",
all_files = FALSE
))
}
if (version == 1) {
return(htmlDependency(
"jquery", "1.12.4",
src = c(
href = "shared/legacy",
file = "www/shared/legacy"
),
src = "www/shared/legacy",
package = "shiny",
script = "jquery.min.js"
script = "jquery.min.js",
all_files = FALSE
))
}
stop("Unsupported version of jQuery: ", version)
@@ -95,8 +101,9 @@ shinyDependencies <- function() {
bslib::bs_dependency_defer(shinyDependencyCSS),
htmlDependency(
name = "shiny-javascript",
version = shinyPackageVersion(),
src = c(href = "shared"),
version = get_package_version("shiny"),
src = "www/shared",
package = "shiny",
script =
if (isTRUE(
get_devmode_option(
@@ -106,24 +113,27 @@ shinyDependencies <- function() {
))
"shiny.min.js"
else
"shiny.js"
"shiny.js",
all_files = FALSE
)
)
}
shinyDependencyCSS <- function(theme) {
version <- shinyPackageVersion()
version <- get_package_version("shiny")
if (!is_bs_theme(theme)) {
return(htmlDependency(
name = "shiny-css",
version = version,
src = c(href = "shared"),
stylesheet = "shiny.min.css"
src = "www/shared",
package = "shiny",
stylesheet = "shiny.min.css",
all_files = FALSE
))
}
scss_home <- system.file("www/shared/shiny_scss", package = "shiny")
scss_home <- system_file("www/shared/shiny_scss", package = "shiny")
scss_files <- file.path(scss_home, c("bootstrap.scss", "shiny.scss"))
scss_files <- lapply(scss_files, sass::sass_file)

View File

@@ -778,9 +778,9 @@ renderUI <- function(expr, env = parent.frame(), quoted = FALSE,
#' function.)
#' @param contentType A string of the download's
#' [content type](https://en.wikipedia.org/wiki/Internet_media_type), for
#' example `"text/csv"` or `"image/png"`. If `NULL` or
#' `NA`, the content type will be guessed based on the filename
#' extension, or `application/octet-stream` if the extension is unknown.
#' example `"text/csv"` or `"image/png"`. If `NULL`, the content type
#' will be guessed based on the filename extension, or
#' `application/octet-stream` if the extension is unknown.
#' @param outputArgs A list of arguments to be passed through to the implicit
#' call to [downloadButton()] when `downloadHandler` is used
#' in an interactive R Markdown document.
@@ -810,7 +810,7 @@ renderUI <- function(expr, env = parent.frame(), quoted = FALSE,
#' shinyApp(ui, server)
#' }
#' @export
downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()) {
downloadHandler <- function(filename, content, contentType=NULL, outputArgs=list()) {
renderFunc <- function(shinysession, name, ...) {
shinysession$registerDownload(name, filename, contentType, content)
}
@@ -953,7 +953,7 @@ renderDataTable <- function(expr, options = NULL, searchDelay = 500,
DT10Names <- function() {
rbind(
utils::read.table(
system.file('www/shared/datatables/upgrade1.10.txt', package = 'shiny'),
system_file('www/shared/datatables/upgrade1.10.txt', package = 'shiny'),
stringsAsFactors = FALSE
),
c('aoColumns', 'Removed') # looks like an omission on the upgrade guide

View File

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

216
R/staticimports.R Normal file
View File

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

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
library(shinytest)
expect_pass(testApp("../", suffix = osName()))

View File

@@ -1,12 +0,0 @@
app <- ShinyDriver$new("../../")
app$snapshotInit("mytest")
app$snapshot()
{{
if (isTRUE(module)) {
'
app$setInputs(`examplemodule1-button` = "click")
app$setInputs(`examplemodule1-button` = "click")
app$snapshot()'
}
}}

View File

@@ -1,9 +1 @@
library(testthat)
test_dir(
"./testthat",
# Run in the app's environment containing all support methods.
env = shiny::loadSupport(),
# Display the regular progress output and throw an error if any test error is found
reporter = c("progress", "fail")
)
shinytest2::test_app()

View File

@@ -14,5 +14,4 @@ if (isTRUE(rdir)) {
expect_equal(output$sequence, "1 2 3 4 5 6 7 8 9 10 11 12")
'
}
}}
})
}}})

View File

@@ -0,0 +1,18 @@
library(shinytest2)
test_that("Initial snapshot values are consistent", {
app <- AppDriver$new(name = "init")
app$expect_values()
}){{
if (isTRUE(module)) {
HTML('
test_that("Module values are consistent", {
app <- AppDriver$new(name = "mod")
app$click("examplemodule1-button")
app$click("examplemodule1-button")
app$expect_values()
})')
}
}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../../srcts/src/utils/eval.ts", "../../../srcts/extras/shiny-testmode.ts"],
"sourcesContent": ["//esbuild.github.io/content-types/#direct-eval\n//tl/dr;\n// * Direct usage of `eval(\"x\")` is bad with bundled code.\n// * Instead, use indirect calls to `eval` such as `indirectEval(\"x\")`\n// * Even just renaming the function works well enough.\n// > This is known as \"indirect eval\" because eval is not being called directly, and so does not trigger the grammatical special case for direct eval in the JavaScript VM. You can call indirect eval using any syntax at all except for an expression of the exact form eval('x'). For example, var eval2 = eval; eval2('x') and [eval][0]('x') and window.eval('x') are all indirect eval calls.\n// > When you use indirect eval, the code is evaluated in the global scope instead of in the inline scope of the caller.\nvar indirectEval = eval;\nexport { indirectEval };", "/* eslint-disable unicorn/filename-case */\nimport { indirectEval } from \"../src/utils/eval\"; // Listen for messages from parent frame. This file is only added when the\n// shiny.testmode option is TRUE.\n\nwindow.addEventListener(\"message\", function (e) {\n var message = e.data;\n if (message.code) indirectEval(message.code);\n});"],
"mappings": ";YAOA,GAAI,GAAe,KCHnB,OAAO,iBAAiB,UAAW,SAAU,EAAG,CAC9C,GAAI,GAAU,EAAE,KAChB,AAAI,EAAQ,MAAM,EAAa,EAAQ",
"names": []
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -85,40 +85,40 @@ user. Always \code{NULL} for a \code{MockShinySesion}.}
\section{Methods}{
\subsection{Public methods}{
\itemize{
\item \href{#method-new}{\code{MockShinySession$new()}}
\item \href{#method-onFlush}{\code{MockShinySession$onFlush()}}
\item \href{#method-onFlushed}{\code{MockShinySession$onFlushed()}}
\item \href{#method-onEnded}{\code{MockShinySession$onEnded()}}
\item \href{#method-isEnded}{\code{MockShinySession$isEnded()}}
\item \href{#method-isClosed}{\code{MockShinySession$isClosed()}}
\item \href{#method-close}{\code{MockShinySession$close()}}
\item \href{#method-cycleStartAction}{\code{MockShinySession$cycleStartAction()}}
\item \href{#method-fileUrl}{\code{MockShinySession$fileUrl()}}
\item \href{#method-setInputs}{\code{MockShinySession$setInputs()}}
\item \href{#method-.scheduleTask}{\code{MockShinySession$.scheduleTask()}}
\item \href{#method-elapse}{\code{MockShinySession$elapse()}}
\item \href{#method-.now}{\code{MockShinySession$.now()}}
\item \href{#method-defineOutput}{\code{MockShinySession$defineOutput()}}
\item \href{#method-getOutput}{\code{MockShinySession$getOutput()}}
\item \href{#method-ns}{\code{MockShinySession$ns()}}
\item \href{#method-flushReact}{\code{MockShinySession$flushReact()}}
\item \href{#method-makeScope}{\code{MockShinySession$makeScope()}}
\item \href{#method-setEnv}{\code{MockShinySession$setEnv()}}
\item \href{#method-setReturned}{\code{MockShinySession$setReturned()}}
\item \href{#method-getReturned}{\code{MockShinySession$getReturned()}}
\item \href{#method-genId}{\code{MockShinySession$genId()}}
\item \href{#method-rootScope}{\code{MockShinySession$rootScope()}}
\item \href{#method-unhandledError}{\code{MockShinySession$unhandledError()}}
\item \href{#method-freezeValue}{\code{MockShinySession$freezeValue()}}
\item \href{#method-onSessionEnded}{\code{MockShinySession$onSessionEnded()}}
\item \href{#method-registerDownload}{\code{MockShinySession$registerDownload()}}
\item \href{#method-getCurrentOutputInfo}{\code{MockShinySession$getCurrentOutputInfo()}}
\item \href{#method-clone}{\code{MockShinySession$clone()}}
\item \href{#method-MockShinySession-new}{\code{MockShinySession$new()}}
\item \href{#method-MockShinySession-onFlush}{\code{MockShinySession$onFlush()}}
\item \href{#method-MockShinySession-onFlushed}{\code{MockShinySession$onFlushed()}}
\item \href{#method-MockShinySession-onEnded}{\code{MockShinySession$onEnded()}}
\item \href{#method-MockShinySession-isEnded}{\code{MockShinySession$isEnded()}}
\item \href{#method-MockShinySession-isClosed}{\code{MockShinySession$isClosed()}}
\item \href{#method-MockShinySession-close}{\code{MockShinySession$close()}}
\item \href{#method-MockShinySession-cycleStartAction}{\code{MockShinySession$cycleStartAction()}}
\item \href{#method-MockShinySession-fileUrl}{\code{MockShinySession$fileUrl()}}
\item \href{#method-MockShinySession-setInputs}{\code{MockShinySession$setInputs()}}
\item \href{#method-MockShinySession-.scheduleTask}{\code{MockShinySession$.scheduleTask()}}
\item \href{#method-MockShinySession-elapse}{\code{MockShinySession$elapse()}}
\item \href{#method-MockShinySession-.now}{\code{MockShinySession$.now()}}
\item \href{#method-MockShinySession-defineOutput}{\code{MockShinySession$defineOutput()}}
\item \href{#method-MockShinySession-getOutput}{\code{MockShinySession$getOutput()}}
\item \href{#method-MockShinySession-ns}{\code{MockShinySession$ns()}}
\item \href{#method-MockShinySession-flushReact}{\code{MockShinySession$flushReact()}}
\item \href{#method-MockShinySession-makeScope}{\code{MockShinySession$makeScope()}}
\item \href{#method-MockShinySession-setEnv}{\code{MockShinySession$setEnv()}}
\item \href{#method-MockShinySession-setReturned}{\code{MockShinySession$setReturned()}}
\item \href{#method-MockShinySession-getReturned}{\code{MockShinySession$getReturned()}}
\item \href{#method-MockShinySession-genId}{\code{MockShinySession$genId()}}
\item \href{#method-MockShinySession-rootScope}{\code{MockShinySession$rootScope()}}
\item \href{#method-MockShinySession-unhandledError}{\code{MockShinySession$unhandledError()}}
\item \href{#method-MockShinySession-freezeValue}{\code{MockShinySession$freezeValue()}}
\item \href{#method-MockShinySession-onSessionEnded}{\code{MockShinySession$onSessionEnded()}}
\item \href{#method-MockShinySession-registerDownload}{\code{MockShinySession$registerDownload()}}
\item \href{#method-MockShinySession-getCurrentOutputInfo}{\code{MockShinySession$getCurrentOutputInfo()}}
\item \href{#method-MockShinySession-clone}{\code{MockShinySession$clone()}}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-new"></a>}}
\if{latex}{\out{\hypertarget{method-new}{}}}
\if{html}{\out{<a id="method-MockShinySession-new"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-new}{}}}
\subsection{Method \code{new()}}{
Create a new MockShinySession.
\subsection{Usage}{
@@ -127,8 +127,8 @@ Create a new MockShinySession.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onFlush"></a>}}
\if{latex}{\out{\hypertarget{method-onFlush}{}}}
\if{html}{\out{<a id="method-MockShinySession-onFlush"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-onFlush}{}}}
\subsection{Method \code{onFlush()}}{
Define a callback to be invoked before a reactive flush
\subsection{Usage}{
@@ -146,8 +146,8 @@ Define a callback to be invoked before a reactive flush
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onFlushed"></a>}}
\if{latex}{\out{\hypertarget{method-onFlushed}{}}}
\if{html}{\out{<a id="method-MockShinySession-onFlushed"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-onFlushed}{}}}
\subsection{Method \code{onFlushed()}}{
Define a callback to be invoked after a reactive flush
\subsection{Usage}{
@@ -165,8 +165,8 @@ Define a callback to be invoked after a reactive flush
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onEnded"></a>}}
\if{latex}{\out{\hypertarget{method-onEnded}{}}}
\if{html}{\out{<a id="method-MockShinySession-onEnded"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-onEnded}{}}}
\subsection{Method \code{onEnded()}}{
Define a callback to be invoked when the session ends
\subsection{Usage}{
@@ -182,8 +182,8 @@ Define a callback to be invoked when the session ends
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-isEnded"></a>}}
\if{latex}{\out{\hypertarget{method-isEnded}{}}}
\if{html}{\out{<a id="method-MockShinySession-isEnded"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-isEnded}{}}}
\subsection{Method \code{isEnded()}}{
Returns \code{FALSE} if the session has not yet been closed
\subsection{Usage}{
@@ -192,8 +192,8 @@ Returns \code{FALSE} if the session has not yet been closed
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-isClosed"></a>}}
\if{latex}{\out{\hypertarget{method-isClosed}{}}}
\if{html}{\out{<a id="method-MockShinySession-isClosed"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-isClosed}{}}}
\subsection{Method \code{isClosed()}}{
Returns \code{FALSE} if the session has not yet been closed
\subsection{Usage}{
@@ -202,8 +202,8 @@ Returns \code{FALSE} if the session has not yet been closed
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-close"></a>}}
\if{latex}{\out{\hypertarget{method-close}{}}}
\if{html}{\out{<a id="method-MockShinySession-close"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-close}{}}}
\subsection{Method \code{close()}}{
Closes the session
\subsection{Usage}{
@@ -212,8 +212,8 @@ Closes the session
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-cycleStartAction"></a>}}
\if{latex}{\out{\hypertarget{method-cycleStartAction}{}}}
\if{html}{\out{<a id="method-MockShinySession-cycleStartAction"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-cycleStartAction}{}}}
\subsection{Method \code{cycleStartAction()}}{
Unsophisticated mock implementation that merely invokes
\subsection{Usage}{
@@ -229,8 +229,8 @@ Unsophisticated mock implementation that merely invokes
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-fileUrl"></a>}}
\if{latex}{\out{\hypertarget{method-fileUrl}{}}}
\if{html}{\out{<a id="method-MockShinySession-fileUrl"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-fileUrl}{}}}
\subsection{Method \code{fileUrl()}}{
Base64-encode the given file. Needed for image rendering.
\subsection{Usage}{
@@ -250,8 +250,8 @@ Base64-encode the given file. Needed for image rendering.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-setInputs"></a>}}
\if{latex}{\out{\hypertarget{method-setInputs}{}}}
\if{html}{\out{<a id="method-MockShinySession-setInputs"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-setInputs}{}}}
\subsection{Method \code{setInputs()}}{
Sets reactive values associated with the \code{session$inputs}
object and flushes the reactives.
@@ -280,8 +280,8 @@ session$setInputs(x=1, y=2)
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-.scheduleTask"></a>}}
\if{latex}{\out{\hypertarget{method-.scheduleTask}{}}}
\if{html}{\out{<a id="method-MockShinySession-.scheduleTask"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-.scheduleTask}{}}}
\subsection{Method \code{.scheduleTask()}}{
An internal method which shouldn't be used by others.
Schedules \code{callback} for execution after some number of \code{millis}
@@ -301,8 +301,8 @@ milliseconds.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-elapse"></a>}}
\if{latex}{\out{\hypertarget{method-elapse}{}}}
\if{html}{\out{<a id="method-MockShinySession-elapse"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-elapse}{}}}
\subsection{Method \code{elapse()}}{
Simulate the passing of time by the given number of milliseconds.
\subsection{Usage}{
@@ -318,8 +318,8 @@ Simulate the passing of time by the given number of milliseconds.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-.now"></a>}}
\if{latex}{\out{\hypertarget{method-.now}{}}}
\if{html}{\out{<a id="method-MockShinySession-.now"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-.now}{}}}
\subsection{Method \code{.now()}}{
An internal method which shouldn't be used by others.
\subsection{Usage}{
@@ -331,8 +331,8 @@ Elapsed time in milliseconds.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-defineOutput"></a>}}
\if{latex}{\out{\hypertarget{method-defineOutput}{}}}
\if{html}{\out{<a id="method-MockShinySession-defineOutput"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-defineOutput}{}}}
\subsection{Method \code{defineOutput()}}{
An internal method which shouldn't be used by others.
Defines an output in a way that sets private$currentOutputName
@@ -354,8 +354,8 @@ appropriately.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getOutput"></a>}}
\if{latex}{\out{\hypertarget{method-getOutput}{}}}
\if{html}{\out{<a id="method-MockShinySession-getOutput"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-getOutput}{}}}
\subsection{Method \code{getOutput()}}{
An internal method which shouldn't be used by others. Forces
evaluation of any reactive dependencies of the output function.
@@ -376,8 +376,8 @@ output.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-ns"></a>}}
\if{latex}{\out{\hypertarget{method-ns}{}}}
\if{html}{\out{<a id="method-MockShinySession-ns"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-ns}{}}}
\subsection{Method \code{ns()}}{
Returns the given id prefixed by this namespace's id.
\subsection{Usage}{
@@ -396,8 +396,8 @@ The id with a namespace prefix.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-flushReact"></a>}}
\if{latex}{\out{\hypertarget{method-flushReact}{}}}
\if{html}{\out{<a id="method-MockShinySession-flushReact"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-flushReact}{}}}
\subsection{Method \code{flushReact()}}{
Trigger a reactive flush right now.
\subsection{Usage}{
@@ -406,8 +406,8 @@ Trigger a reactive flush right now.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-makeScope"></a>}}
\if{latex}{\out{\hypertarget{method-makeScope}{}}}
\if{html}{\out{<a id="method-MockShinySession-makeScope"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-makeScope}{}}}
\subsection{Method \code{makeScope()}}{
Create and return a namespace-specific session proxy.
\subsection{Usage}{
@@ -426,8 +426,8 @@ A new session proxy.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-setEnv"></a>}}
\if{latex}{\out{\hypertarget{method-setEnv}{}}}
\if{html}{\out{<a id="method-MockShinySession-setEnv"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-setEnv}{}}}
\subsection{Method \code{setEnv()}}{
Set the environment associated with a testServer() call, but
only if it has not previously been set. This ensures that only the
@@ -449,8 +449,8 @@ The provided \code{env}.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-setReturned"></a>}}
\if{latex}{\out{\hypertarget{method-setReturned}{}}}
\if{html}{\out{<a id="method-MockShinySession-setReturned"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-setReturned}{}}}
\subsection{Method \code{setReturned()}}{
Set the value returned by the module call and proactively
flush. Note that this method may be called multiple times if modules
@@ -472,8 +472,8 @@ The provided \code{value}.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getReturned"></a>}}
\if{latex}{\out{\hypertarget{method-getReturned}{}}}
\if{html}{\out{<a id="method-MockShinySession-getReturned"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-getReturned}{}}}
\subsection{Method \code{getReturned()}}{
Get the value returned by the module call.
\subsection{Usage}{
@@ -485,8 +485,8 @@ The value returned by the module call
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-genId"></a>}}
\if{latex}{\out{\hypertarget{method-genId}{}}}
\if{html}{\out{<a id="method-MockShinySession-genId"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-genId}{}}}
\subsection{Method \code{genId()}}{
Generate a distinct character identifier for use as a proxy
namespace.
@@ -499,8 +499,8 @@ A character identifier unique to the current session.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-rootScope"></a>}}
\if{latex}{\out{\hypertarget{method-rootScope}{}}}
\if{html}{\out{<a id="method-MockShinySession-rootScope"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-rootScope}{}}}
\subsection{Method \code{rootScope()}}{
Provides a way to access the root \code{MockShinySession} from
any descendant proxy.
@@ -513,8 +513,8 @@ The root \code{MockShinySession}.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-unhandledError"></a>}}
\if{latex}{\out{\hypertarget{method-unhandledError}{}}}
\if{html}{\out{<a id="method-MockShinySession-unhandledError"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-unhandledError}{}}}
\subsection{Method \code{unhandledError()}}{
Called by observers when a reactive expression errors.
\subsection{Usage}{
@@ -530,8 +530,8 @@ Called by observers when a reactive expression errors.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-freezeValue"></a>}}
\if{latex}{\out{\hypertarget{method-freezeValue}{}}}
\if{html}{\out{<a id="method-MockShinySession-freezeValue"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-freezeValue}{}}}
\subsection{Method \code{freezeValue()}}{
Freeze a value until the flush cycle completes.
\subsection{Usage}{
@@ -549,8 +549,8 @@ Freeze a value until the flush cycle completes.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onSessionEnded"></a>}}
\if{latex}{\out{\hypertarget{method-onSessionEnded}{}}}
\if{html}{\out{<a id="method-MockShinySession-onSessionEnded"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-onSessionEnded}{}}}
\subsection{Method \code{onSessionEnded()}}{
Registers the given callback to be invoked when the session
is closed (i.e. the connection to the client has been severed). The
@@ -570,8 +570,8 @@ guaranteed.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-registerDownload"></a>}}
\if{latex}{\out{\hypertarget{method-registerDownload}{}}}
\if{html}{\out{<a id="method-MockShinySession-registerDownload"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-registerDownload}{}}}
\subsection{Method \code{registerDownload()}}{
Associated a downloadable file with the session.
\subsection{Usage}{
@@ -598,8 +598,8 @@ function.)}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getCurrentOutputInfo"></a>}}
\if{latex}{\out{\hypertarget{method-getCurrentOutputInfo}{}}}
\if{html}{\out{<a id="method-MockShinySession-getCurrentOutputInfo"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-getCurrentOutputInfo}{}}}
\subsection{Method \code{getCurrentOutputInfo()}}{
Get information about the output that is currently being
executed.
@@ -614,8 +614,8 @@ output, or \code{NULL} if no output is currently executing.
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-clone"></a>}}
\if{latex}{\out{\hypertarget{method-clone}{}}}
\if{html}{\out{<a id="method-MockShinySession-clone"></a>}}
\if{latex}{\out{\hypertarget{method-MockShinySession-clone}{}}}
\subsection{Method \code{clone()}}{
The objects of this class are cloneable with this method.
\subsection{Usage}{

View File

@@ -62,19 +62,19 @@ shinyApp(ui, server)
\section{Methods}{
\subsection{Public methods}{
\itemize{
\item \href{#method-new}{\code{Progress$new()}}
\item \href{#method-set}{\code{Progress$set()}}
\item \href{#method-inc}{\code{Progress$inc()}}
\item \href{#method-getMin}{\code{Progress$getMin()}}
\item \href{#method-getMax}{\code{Progress$getMax()}}
\item \href{#method-getValue}{\code{Progress$getValue()}}
\item \href{#method-close}{\code{Progress$close()}}
\item \href{#method-clone}{\code{Progress$clone()}}
\item \href{#method-Progress-new}{\code{Progress$new()}}
\item \href{#method-Progress-set}{\code{Progress$set()}}
\item \href{#method-Progress-inc}{\code{Progress$inc()}}
\item \href{#method-Progress-getMin}{\code{Progress$getMin()}}
\item \href{#method-Progress-getMax}{\code{Progress$getMax()}}
\item \href{#method-Progress-getValue}{\code{Progress$getValue()}}
\item \href{#method-Progress-close}{\code{Progress$close()}}
\item \href{#method-Progress-clone}{\code{Progress$clone()}}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-new"></a>}}
\if{latex}{\out{\hypertarget{method-new}{}}}
\if{html}{\out{<a id="method-Progress-new"></a>}}
\if{latex}{\out{\hypertarget{method-Progress-new}{}}}
\subsection{Method \code{new()}}{
Creates a new progress panel (but does not display it).
\subsection{Usage}{
@@ -107,8 +107,8 @@ is for backward-compatibility).}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-set"></a>}}
\if{latex}{\out{\hypertarget{method-set}{}}}
\if{html}{\out{<a id="method-Progress-set"></a>}}
\if{latex}{\out{\hypertarget{method-Progress-set}{}}}
\subsection{Method \code{set()}}{
Updates the progress panel. When called the first time, the
progress panel is displayed.
@@ -135,8 +135,8 @@ relative to \code{message}.}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-inc"></a>}}
\if{latex}{\out{\hypertarget{method-inc}{}}}
\if{html}{\out{<a id="method-Progress-inc"></a>}}
\if{latex}{\out{\hypertarget{method-Progress-inc}{}}}
\subsection{Method \code{inc()}}{
Like \code{set}, this updates the progress panel. The difference
is that \code{inc} increases the progress bar by \code{amount}, instead of
@@ -163,8 +163,8 @@ relative to \code{message}.}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getMin"></a>}}
\if{latex}{\out{\hypertarget{method-getMin}{}}}
\if{html}{\out{<a id="method-Progress-getMin"></a>}}
\if{latex}{\out{\hypertarget{method-Progress-getMin}{}}}
\subsection{Method \code{getMin()}}{
Returns the minimum value.
\subsection{Usage}{
@@ -173,8 +173,8 @@ Returns the minimum value.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getMax"></a>}}
\if{latex}{\out{\hypertarget{method-getMax}{}}}
\if{html}{\out{<a id="method-Progress-getMax"></a>}}
\if{latex}{\out{\hypertarget{method-Progress-getMax}{}}}
\subsection{Method \code{getMax()}}{
Returns the maximum value.
\subsection{Usage}{
@@ -183,8 +183,8 @@ Returns the maximum value.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getValue"></a>}}
\if{latex}{\out{\hypertarget{method-getValue}{}}}
\if{html}{\out{<a id="method-Progress-getValue"></a>}}
\if{latex}{\out{\hypertarget{method-Progress-getValue}{}}}
\subsection{Method \code{getValue()}}{
Returns the current value.
\subsection{Usage}{
@@ -193,8 +193,8 @@ Returns the current value.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-close"></a>}}
\if{latex}{\out{\hypertarget{method-close}{}}}
\if{html}{\out{<a id="method-Progress-close"></a>}}
\if{latex}{\out{\hypertarget{method-Progress-close}{}}}
\subsection{Method \code{close()}}{
Removes the progress panel. Future calls to \code{set} and
\code{close} will be ignored.
@@ -204,8 +204,8 @@ Removes the progress panel. Future calls to \code{set} and
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-clone"></a>}}
\if{latex}{\out{\hypertarget{method-clone}{}}}
\if{html}{\out{<a id="method-Progress-clone"></a>}}
\if{latex}{\out{\hypertarget{method-Progress-clone}{}}}
\subsection{Method \code{clone()}}{
The objects of this class are cloneable with this method.
\subsection{Usage}{

View File

@@ -40,7 +40,9 @@ 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="sourceCode NA">}}\preformatted{r <- reactive(\{ input$x * input$y \}) \%>\%
bindCache(input$x, input$y)
}\if{html}{\out{</div>}}
@@ -63,7 +65,9 @@ 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="sourceCode NA">}}\preformatted{r <- reactive(\{ compute(bigdata()) \} \%>\%
bindCache(\{ extract_most_recent_time(bigdata()) \})
}\if{html}{\out{</div>}}
@@ -105,7 +109,9 @@ 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="sourceCode NA">}}\preformatted{r <- reactive(\{ input$x * input$y \}) \%>\%
bindCache(input$x, input$y)
}\if{html}{\out{</div>}}
@@ -154,24 +160,30 @@ persists beyond the current R session.
To use different settings for an application-scoped cache, you can call
\code{\link[=shinyOptions]{shinyOptions()}} at the top of your app.R, server.R, or
global.R. For example, this will create a cache with 500 MB of space
instead of the default 200 MB:\preformatted{shinyOptions(cache = cachem::cache_mem(max_size = 500e6))
}
instead of the default 200 MB:
\if{html}{\out{<div class="sourceCode">}}\preformatted{shinyOptions(cache = cachem::cache_mem(max_size = 500e6))
}\if{html}{\out{</div>}}
To use different settings for a session-scoped cache, you can set
\code{self$cache} at the top of your server function. By default, it will create
a 200 MB memory cache for each session, but you can replace it with
something different. To use the session-scoped cache, you must also call
\code{bindCache()} with \code{cache="session"}. This will create a 100 MB cache for
the session:\preformatted{function(input, output, session) \{
the session:
\if{html}{\out{<div class="sourceCode">}}\preformatted{function(input, output, session) \{
session$cache <- cachem::cache_mem(max_size = 100e6)
...
\}
}
}\if{html}{\out{</div>}}
If you want to use a cache that is shared across multiple R processes, you
can use a \code{\link[cachem:cache_disk]{cachem::cache_disk()}}. You can create a application-level shared
cache by putting this at the top of your app.R, server.R, or global.R:\preformatted{shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache"))
}
cache by putting this at the top of your app.R, server.R, or global.R:
\if{html}{\out{<div class="sourceCode">}}\preformatted{shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache"))
}\if{html}{\out{</div>}}
This will create a subdirectory in your system temp directory named
\code{myapp-cache} (replace \code{myapp-cache} with a unique name of
@@ -181,8 +193,10 @@ stops of the R process, as long as you do not reboot.
To have the cache persist even across multiple reboots, you can create the
cache in a location outside of the temp directory. For example, it could
be a subdirectory of the application:\preformatted{shinyOptions(cache = cachem::cache_disk("./myapp-cache"))
}
be a subdirectory of the application:
\if{html}{\out{<div class="sourceCode">}}\preformatted{shinyOptions(cache = cachem::cache_disk("./myapp-cache"))
}\if{html}{\out{</div>}}
In this case, resetting the cache will have to be done manually, by deleting
the directory.
@@ -247,9 +261,11 @@ You may need to provide a \code{cacheHint} to \code{\link[=createRenderFunction]
\code{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)
The potential problem is a cache collision. Consider the following:
\if{html}{\out{<div class="sourceCode">}}\preformatted{output$x1 <- renderText(\{ input$x \}) \%>\% bindCache(input$x)
output$x2 <- renderText(\{ input$x * 2 \}) \%>\% bindCache(input$x)
}
}\if{html}{\out{</div>}}
Both \code{output$x1} and \code{output$x2} use \code{input$x} as part of their cache key,
but if it were the only thing used in the cache key, then the two outputs
@@ -258,7 +274,9 @@ 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="sourceCode NA">}}\preformatted{r <- renderText(\{ input$x \})
shiny:::extractCacheHint(r)
}\if{html}{\out{</div>}}
@@ -289,7 +307,9 @@ that may influence the final value.
For \pkg{htmlwidgets}, it will try to automatically infer a cache hint;
again, you can inspect the cache hint with \code{shiny:::extractCacheHint()} and
also test it in an application. If you do need to explicitly provide a
cache hint, pass it to \code{shinyRenderWidget}. For example:\preformatted{renderMyWidget <- function(expr) \{
cache hint, pass it to \code{shinyRenderWidget}. For example:
\if{html}{\out{<div class="sourceCode">}}\preformatted{renderMyWidget <- function(expr) \{
q <- rlang::enquo0(expr)
htmlwidgets::shinyRenderWidget(
@@ -299,7 +319,7 @@ cache hint, pass it to \code{shinyRenderWidget}. For example:\preformatted{rende
cacheHint = list(label = "myWidget", userQuo = q)
)
\}
}
}\if{html}{\out{</div>}}
If your \code{render} function sets any internal state, you may find it useful
in your call to \code{\link[=createRenderFunction]{createRenderFunction()}} to use
@@ -330,8 +350,10 @@ For developers of such code, they should call \code{\link[=createRenderFunction]
When \code{bindCache()} is used with \code{renderPlot()}, the \code{height} and \code{width}
passed to the original \code{renderPlot()} are ignored. They are superseded by
\code{sizePolicy} argument passed to `bindCache. The default is:\preformatted{sizePolicy = sizeGrowthRatio(width = 400, height = 400, growthRate = 1.2)
}
\code{sizePolicy} argument passed to `bindCache. The default is:
\if{html}{\out{<div class="sourceCode">}}\preformatted{sizePolicy = sizeGrowthRatio(width = 400, height = 400, growthRate = 1.2)
}\if{html}{\out{</div>}}
\code{sizePolicy} must be a function that takes a two-element numeric vector as
input, representing the width and height of the \verb{<img>} element in the

View File

@@ -171,7 +171,9 @@ 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="sourceCode NA">}}\preformatted{r <- reactive(\{
Sys.sleep(2) # Pretend this is an expensive computation
input$x * input$y
\}) \%>\%

View File

@@ -28,7 +28,7 @@ This will be used as the lang in the \code{<html>} tag, as in \code{<html lang="
The default (NULL) results in an empty string.}
}
\value{
A UI defintion that can be passed to the \link{shinyUI} function.
A UI definition that can be passed to the \link{shinyUI} function.
}
\description{
Create a Shiny UI page that loads the CSS and JavaScript for

View File

@@ -101,7 +101,9 @@ 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="sourceCode 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 +128,9 @@ 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="sourceCode 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 +169,9 @@ 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="sourceCode r">}}\preformatted{in_devmode <- function() \{
isTRUE(getOption("shiny.devmode", FALSE)) &&
!identical(Sys.getenv("TESTTHAT"), "true")
\}
@@ -201,7 +207,9 @@ 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="sourceCode r">}}\preformatted{get_devmode_option <- function(name, default = NULL, devmode_default, devmode_message) \{
if (!in_devmode()) \{
# Dev Mode disabled, act like `getOption()`
return(getOption(name, default = default))

View File

@@ -40,7 +40,7 @@ deleted from disk. If \code{FALSE} (the default), it will do nothing when
finalized.}
\item{missing}{A value to return when \code{get(key)} is called but the key is not
present in the cache. The default is a \code{\link[cachem:reexports]{key_missing()}} object. It is
present in the cache. The default is a \code{\link[cachem:key_missing]{key_missing()}} object. It is
actually an expression that is evaluated each time there is a cache miss.
See section Missing keys for more information.}

View File

@@ -4,7 +4,7 @@
\alias{downloadHandler}
\title{File Downloads}
\usage{
downloadHandler(filename, content, contentType = NA, outputArgs = list())
downloadHandler(filename, content, contentType = NULL, outputArgs = list())
}
\arguments{
\item{filename}{A string of the filename, including extension, that the
@@ -19,9 +19,9 @@ function.)}
\item{contentType}{A string of the download's
\href{https://en.wikipedia.org/wiki/Internet_media_type}{content type}, for
example \code{"text/csv"} or \code{"image/png"}. If \code{NULL} or
\code{NA}, the content type will be guessed based on the filename
extension, or \code{application/octet-stream} if the extension is unknown.}
example \code{"text/csv"} or \code{"image/png"}. If \code{NULL}, the content type
will be guessed based on the filename extension, or
\code{application/octet-stream} if the extension is unknown.}
\item{outputArgs}{A list of arguments to be passed through to the implicit
call to \code{\link[=downloadButton]{downloadButton()}} when \code{downloadHandler} is used

View File

@@ -28,7 +28,7 @@ This will be used as the lang in the \code{<html>} tag, as in \code{<html lang="
The default (NULL) results in an empty string.}
}
\value{
A UI defintion that can be passed to the \link{shinyUI} function.
A UI definition that can be passed to the \link{shinyUI} function.
}
\description{
Functions for creating fixed page layouts. A fixed page layout consists of

View File

@@ -29,7 +29,7 @@ This will be used as the lang in the \code{<html>} tag, as in \code{<html lang="
The default (NULL) results in an empty string.}
}
\value{
A UI defintion that can be passed to the \link{shinyUI} function.
A UI definition that can be passed to the \link{shinyUI} function.
}
\description{
Functions for creating fluid page layouts. A fluid page layout consists of

View File

@@ -15,7 +15,7 @@ loadSupport(
not supplied, the nearest enclosing directory that is a Shiny app, starting
with the current directory, is used.}
\item{renv}{The environmeny in which the files in the \verb{R/} directory should
\item{renv}{The environment in which the files in the \verb{R/} directory should
be evaluated.}
\item{globalrenv}{The environment in which \code{global.R} should be evaluated. If

View File

@@ -30,7 +30,7 @@ value of \code{evict}. Use \code{Inf} for no limit of number of items.}
when a cache pruning occurs. Currently, \code{"lru"} and \code{"fifo"} are supported.}
\item{missing}{A value to return when \code{get(key)} is called but the key is not
present in the cache. The default is a \code{\link[cachem:reexports]{key_missing()}} object. It is
present in the cache. The default is a \code{\link[cachem:key_missing]{key_missing()}} object. It is
actually an expression that is evaluated each time there is a cache miss.
See section Missing keys for more information.}

View File

@@ -89,7 +89,7 @@ is needed if you want to insert/remove or show/hide an entire
\item{icon}{Optional icon to appear on a \code{navbarMenu} tab.}
}
\value{
A UI defintion that can be passed to the \link{shinyUI} function.
A UI definition that can be passed to the \link{shinyUI} function.
}
\description{
Create a page that contains a top level navigation bar that can be used to

View File

@@ -14,7 +14,7 @@ pageWithSidebar(headerPanel, sidebarPanel, mainPanel)
\item{mainPanel}{The \link{mainPanel} containing outputs}
}
\value{
A UI defintion that can be passed to the \link{shinyUI} function
A UI definition that can be passed to the \link{shinyUI} function
}
\description{
\strong{DEPRECATED}: use \code{\link[=fluidPage]{fluidPage()}} and \code{\link[=sidebarLayout]{sidebarLayout()}} instead.

View File

@@ -52,6 +52,6 @@ below to see their documentation.
\describe{
\item{fastmap}{\code{\link[fastmap:key_missing]{is.key_missing}}, \code{\link[fastmap]{key_missing}}}
\item{htmltools}{\code{\link[htmltools]{HTML}}, \code{\link[htmltools:builder]{a}}, \code{\link[htmltools:builder]{br}}, \code{\link[htmltools:builder]{code}}, \code{\link[htmltools:builder]{div}}, \code{\link[htmltools:builder]{em}}, \code{\link[htmltools:builder]{h1}}, \code{\link[htmltools:builder]{h2}}, \code{\link[htmltools:builder]{h3}}, \code{\link[htmltools:builder]{h4}}, \code{\link[htmltools:builder]{h5}}, \code{\link[htmltools:builder]{h6}}, \code{\link[htmltools:builder]{hr}}, \code{\link[htmltools]{htmlTemplate}}, \code{\link[htmltools:builder]{img}}, \code{\link[htmltools:include]{includeCSS}}, \code{\link[htmltools:include]{includeHTML}}, \code{\link[htmltools:include]{includeMarkdown}}, \code{\link[htmltools:include]{includeScript}}, \code{\link[htmltools:include]{includeText}}, \code{\link[htmltools:singleton]{is.singleton}}, \code{\link[htmltools:builder]{p}}, \code{\link[htmltools:builder]{pre}}, \code{\link[htmltools]{singleton}}, \code{\link[htmltools:builder]{span}}, \code{\link[htmltools:builder]{strong}}, \code{\link[htmltools]{suppressDependencies}}, \code{\link[htmltools:builder]{tag}}, \code{\link[htmltools]{tagAppendAttributes}}, \code{\link[htmltools]{tagAppendChild}}, \code{\link[htmltools:tagAppendChild]{tagAppendChildren}}, \code{\link[htmltools:tagAppendAttributes]{tagGetAttribute}}, \code{\link[htmltools:tagAppendAttributes]{tagHasAttribute}}, \code{\link[htmltools]{tagList}}, \code{\link[htmltools:tagAppendChild]{tagSetChildren}}, \code{\link[htmltools:builder]{tags}}, \code{\link[htmltools]{validateCssUnit}}, \code{\link[htmltools]{withTags}}}
\item{htmltools}{\code{\link[htmltools:builder]{a}}, \code{\link[htmltools:builder]{br}}, \code{\link[htmltools:builder]{code}}, \code{\link[htmltools:builder]{div}}, \code{\link[htmltools:builder]{em}}, \code{\link[htmltools:builder]{h1}}, \code{\link[htmltools:builder]{h2}}, \code{\link[htmltools:builder]{h3}}, \code{\link[htmltools:builder]{h4}}, \code{\link[htmltools:builder]{h5}}, \code{\link[htmltools:builder]{h6}}, \code{\link[htmltools:builder]{hr}}, \code{\link[htmltools]{HTML}}, \code{\link[htmltools]{htmlTemplate}}, \code{\link[htmltools:builder]{img}}, \code{\link[htmltools:include]{includeCSS}}, \code{\link[htmltools:include]{includeHTML}}, \code{\link[htmltools:include]{includeMarkdown}}, \code{\link[htmltools:include]{includeScript}}, \code{\link[htmltools:include]{includeText}}, \code{\link[htmltools:singleton]{is.singleton}}, \code{\link[htmltools:builder]{p}}, \code{\link[htmltools:builder]{pre}}, \code{\link[htmltools]{singleton}}, \code{\link[htmltools:builder]{span}}, \code{\link[htmltools:builder]{strong}}, \code{\link[htmltools]{suppressDependencies}}, \code{\link[htmltools:builder]{tag}}, \code{\link[htmltools]{tagAppendAttributes}}, \code{\link[htmltools]{tagAppendChild}}, \code{\link[htmltools:tagAppendChild]{tagAppendChildren}}, \code{\link[htmltools:tagAppendAttributes]{tagGetAttribute}}, \code{\link[htmltools:tagAppendAttributes]{tagHasAttribute}}, \code{\link[htmltools]{tagList}}, \code{\link[htmltools:builder]{tags}}, \code{\link[htmltools:tagAppendChild]{tagSetChildren}}, \code{\link[htmltools]{validateCssUnit}}, \code{\link[htmltools]{withTags}}}
}}

View File

@@ -25,26 +25,30 @@ nor displayed in the Shiny app's UI).
\details{
The \code{req} function was designed to be used in one of two ways. The first
is to call it like a statement (ignoring its return value) before attempting
operations using the required values:\preformatted{rv <- reactiveValues(state = FALSE)
operations using the required values:
\if{html}{\out{<div class="sourceCode">}}\preformatted{rv <- reactiveValues(state = FALSE)
r <- reactive(\{
req(input$a, input$b, rv$state)
# Code that uses input$a, input$b, and/or rv$state...
\})
}
}\if{html}{\out{</div>}}
In this example, if \code{r()} is called and any of \code{input$a},
\code{input$b}, and \code{rv$state} are \code{NULL}, \code{FALSE}, \code{""},
etc., then the \code{req} call will trigger an error that propagates all the
way up to whatever render block or observer is executing.
The second is to use it to wrap an expression that must be truthy:\preformatted{output$plot <- renderPlot(\{
The second is to use it to wrap an expression that must be truthy:
\if{html}{\out{<div class="sourceCode">}}\preformatted{output$plot <- renderPlot(\{
if (req(input$plotType) == "histogram") \{
hist(dataset())
\} else if (input$plotType == "scatter") \{
qplot(dataset(), aes(x = x, y = y))
\}
\})
}
}\if{html}{\out{</div>}}
In this example, \code{req(input$plotType)} first checks that
\code{input$plotType} is truthy, and if so, returns it. This is a convenient

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ shinyAppTemplate(path = NULL, examples = "default", dryrun = FALSE)
\item{path}{Path to create new shiny application template.}
\item{examples}{Either one of "default", "ask", "all", or any combination of
"app", "rdir", "module", "shinytest", and "testthat". In an
"app", "rdir", "module", and "tests". In an
interactive session, "default" falls back to "ask"; in a non-interactive
session, "default" falls back to "all". With "ask", this function will
prompt the user to select which template items will be added to the new app
@@ -25,30 +25,31 @@ This function populates a directory with files for a Shiny application.
}
\details{
In an interactive R session, this function will, by default, prompt the user
to select which components to add to the application. Choices are\preformatted{1: All
to select which components to add to the application. Choices are
\if{html}{\out{<div class="sourceCode">}}\preformatted{1: All
2: app.R : Main application file
3: R/example.R : Helper file with R code
4: R/example-module.R : Example module
5: tests/shinytest/ : Tests using the shinytest package
6: tests/testthat/ : Tests using the testthat package
}
5: tests/testthat/ : Tests using the testthat and shinytest2 package
}\if{html}{\out{</div>}}
If option 1 is selected, the full example application including the
following files and directories is created:\preformatted{appdir/
following files and directories is created:
\if{html}{\out{<div class="sourceCode">}}\preformatted{appdir/
|- app.R
|- R
| |- example-module.R
| `- example.R
`- tests
|- shinytest.R
|- shinytest
| `- mytest.R
|- testthat.R
`- testthat
|- test-examplemodule.R
|- test-server.R
|- test-shinytest2.R
`- test-sort.R
}
}\if{html}{\out{</div>}}
Some notes about these files:
\itemize{
@@ -62,15 +63,15 @@ and the second contains code for module created by the
\item \verb{tests/} contains various tests for the application. You may
choose to use or remove any of them. They can be executed by the
\code{\link[=runTests]{runTests()}} function.
\item \code{tests/shinytest.R} is a test runner for test files in the
\verb{tests/shinytest/} directory.
\item \code{tests/shinytest/mytest.R} is a test that uses the
\href{https://rstudio.github.io/shinytest/}{shinytest} package to do
snapshot-based testing.
\item \code{tests/testthat.R} is a test runner for test files in the
\verb{tests/testthat/} directory using the \href{https://testthat.r-lib.org/}{testthat} package.
\verb{tests/testthat/} directory using the
\href{https://rstudio.github.io/shinytest2/reference/test_app.html}{shinytest2}
package.
\item \code{tests/testthat/test-examplemodule.R} is a test for an application's module server function.
\item \code{tests/testthat/test-server.R} is a test for the application's server code
\item \code{tests/testthat/test-shinytest2.R} is a test that uses the
\href{https://rstudio.github.io/shinytest2/}{shinytest2} package to do
snapshot-based testing.
\item \code{tests/testthat/test-sort.R} is a test for a supporting function in the \verb{R/} directory.
}
}

View File

@@ -73,6 +73,10 @@ then jQuery 3.6.0 is used.}
numbers to JSON format to send to the client web browser.}
\item{shiny.launch.browser (defaults to \code{interactive()})}{A boolean which controls the default behavior
when an app is run. See \code{\link[=runApp]{runApp()}} for more information.}
\item{shiny.mathjax.url (defaults to \code{"https://mathjax.rstudio.com/latest/MathJax.js"})}{
The URL that should be used to load MathJax, via \code{\link[=withMathJax]{withMathJax()}}.}
\item{shiny.mathjax.config (defaults to \code{"config=TeX-AMS-MML_HTMLorMML"})}{The querystring
used to load MathJax, via \code{\link[=withMathJax]{withMathJax()}}.}
\item{shiny.maxRequestSize (defaults to 5MB)}{This is a number which specifies the maximum
web request size, which serves as a size limit for file uploads.}
\item{shiny.minified (defaults to \code{TRUE})}{By default
@@ -104,6 +108,9 @@ should only be used for debugging or demonstrations of reactivity at the
console.}
\item{shiny.testmode (defaults to \code{FALSE})}{If \code{TRUE}, then various features for testing Shiny
applications are enabled.}
\item{shiny.snapshotsortc (defaults to \code{FALSE})}{If \code{TRUE}, test snapshot keys
for \pkg{shinytest} will be sorted consistently using the C locale. Snapshots
retrieved by \pkg{shinytest2} will always sort using the C locale.}
\item{shiny.trace (defaults to \code{FALSE})}{Print messages sent between the R server and the web
browser client to the R console. This is useful for debugging. Possible
values are \code{"send"} (only print messages sent to the client),

View File

@@ -33,17 +33,7 @@ types (such as logicals and numbers) will be coerced to strings.}
\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)}
\item{choiceNames}{List of names and values, respectively,
that are displayed to the user in the app and correspond to the each
choice (for this reason, \code{choiceNames} and \code{choiceValues}
must have the same length). If either of these arguments is
provided, then the other \emph{must} be provided and \code{choices}
\emph{must not} be provided. The advantage of using both of these over
a named list for \code{choices} is that \code{choiceNames} allows any
type of UI object to be passed through (tag objects, icons, HTML code,
...), instead of just simple text. See Examples.}
\item{choiceValues}{List of names and values, respectively,
\item{choiceNames, choiceValues}{List of names and values, respectively,
that are displayed to the user in the app and correspond to the each
choice (for this reason, \code{choiceNames} and \code{choiceValues}
must have the same length). If either of these arguments is

View File

@@ -35,16 +35,7 @@ use \code{character(0)}.}
\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)}
\item{choiceNames}{List of names and values, respectively, that
are displayed to the user in the app and correspond to the each choice (for
this reason, \code{choiceNames} and \code{choiceValues} must have the same length).
If either of these arguments is provided, then the other \emph{must} be provided
and \code{choices} \emph{must not} be provided. The advantage of using both of these
over a named list for \code{choices} is that \code{choiceNames} allows any type of UI
object to be passed through (tag objects, icons, HTML code, ...), instead
of just simple text. See Examples.}
\item{choiceValues}{List of names and values, respectively, that
\item{choiceNames, choiceValues}{List of names and values, respectively, that
are displayed to the user in the app and correspond to the each choice (for
this reason, \code{choiceNames} and \code{choiceValues} must have the same length).
If either of these arguments is provided, then the other \emph{must} be provided

View File

@@ -29,10 +29,7 @@ updateSliderInput(
create a regular slider; a length two vector will create a double-ended
range slider. Must lie between \code{min} and \code{max}.}
\item{min}{The minimum and maximum values (inclusive) that can be
selected.}
\item{max}{The minimum and maximum values (inclusive) that can be
\item{min, max}{The minimum and maximum values (inclusive) that can be
selected.}
\item{step}{Specifies the interval between each selectable value on the

View File

@@ -55,10 +55,12 @@ can create your own validation test functions. A passing test should return
to display to the user, or if the failure should happen silently, \code{FALSE}.
Alternatively you can use \code{validate()} within an \code{if} statement, which is
particularly useful for more complex conditions:\preformatted{if (input$x < 0 && input$choice == "positive") \{
particularly useful for more complex conditions:
\if{html}{\out{<div class="sourceCode">}}\preformatted{if (input$x < 0 && input$choice == "positive") \{
validate("If choice is positive then x must be greater than 0")
\}
}
}\if{html}{\out{</div>}}
}
\examples{

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ yarn add https://github.com/rstudio/shiny\#v1.7.0
, matching the GitHub tag to your current the Shiny CRAN release (ex: `v1.7.0`). If you are asked to select a version of `@types/jquery`, please select the closest matching version.
This will provide a global type defintion of `Shiny`, let your IDE know that `window.Shiny` is of type `Shiny`, and declare a globally available variable `Shiny` within your project. You **should not** need to import anything. Similar to `jQuery`, it should _Just Work_<sup>TM</sup>.
This will provide a global type definition of `Shiny`, let your IDE know that `window.Shiny` is of type `Shiny`, and declare a globally available variable `Shiny` within your project. You **should not** need to import anything. Similar to `jQuery`, it should _Just Work_<sup>TM</sup>.
When loading your compiled file, it should be loaded after `shiny.js` is loaded. If you are using an `htmlDependency()` to add your code to the page, your script will automatically be loaded after has been loaded.
@@ -192,11 +192,11 @@ Both JavaScript files will produce a sourcemap (`**.js.map`) that the browser wi
### Exported types
`./extras/globalShiny.ts` contains global declarations to define `window.Shiny`, a globally available `Shiny` variable, and a globally available `Shiny` type. This file is in a parallel folder to `./src` to avoid `Shiny` from being globally accessable within the source code. However, this file is the default type defintion when the Type definitions are installed by external developers.
`./extras/globalShiny.ts` contains global declarations to define `window.Shiny`, a globally available `Shiny` variable, and a globally available `Shiny` type. This file is in a parallel folder to `./src` to avoid `Shiny` from being globally accessable within the source code. However, this file is the default type definition when the Type definitions are installed by external developers.
### GitHub Actions
On push to the `master` branch or push to a Pull Request to the `master` branch, a GitHub Action will be run to make sure the bundled JavaScript code is up to date. If the source code does not compile to the exact same file, it will be committed an pushed back to the outdated branch. (This makes it so the full build tools are not necessary for small tweaks and comments. 🎉)
On push to the `main` branch or push to a Pull Request to the `main` branch, a GitHub Action will be run to make sure the bundled JavaScript code is up to date. If the source code does not compile to the exact same file, it will be committed an pushed back to the outdated branch. (This makes it so the full build tools are not necessary for small tweaks and comments. 🎉)
<!-- #### Auto build and browser refresh

View File

@@ -10,7 +10,7 @@ import babelPlugin from "esbuild-plugin-babel";
build({
bundle: true,
sourcemap: "inline",
sourcemap: true,
minify: true,
plugins: [babelPlugin()],
banner: banner,

View File

@@ -1,14 +1,13 @@
/* eslint-disable unicorn/filename-case */
let protocol = "ws:";
const protocol = (window.location.protocol === "https:") ? "wss:" : "ws:";
// Add trailing slash to path, if necessary, before appending "autoreload"
const defaultPath = window.location.pathname.replace(/\/?$/, "/") + "autoreload/";
const defaultUrl = `${protocol}//${window.location.host}${defaultPath}`;
if (window.location.protocol === "https:") protocol = "wss:";
let defaultPath = window.location.pathname;
if (!/\/$/.test(defaultPath)) defaultPath += "/";
defaultPath += "autoreload/";
const ws = new WebSocket(protocol + "//" + window.location.host + defaultPath);
// By default, use the defaultUrl. But if there's a data-ws-url attribute on our
// <script> tag, use that instead.
const wsUrl = document.currentScript.dataset.wsUrl || defaultUrl;
const ws = new WebSocket(wsUrl);
ws.onmessage = function (event) {
if (event.data === "autoreload") {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +0,0 @@
(function() {
var $ = jQuery;
var exports = window.Shiny = window.Shiny || {};
exports.version = "{{ VERSION }}"; // Version number inserted by Grunt
var origPushState = window.history.pushState;
window.history.pushState = function() {
var result = origPushState.apply(this, arguments);
$(document).trigger("pushstate");
return result;
};
$(document).on('submit', 'form:not([action])', function(e) {
e.preventDefault();
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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