diff --git a/.Rbuildignore b/.Rbuildignore index cc5cc94a..ac3ba4ae 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,5 +1,6 @@ ^renv$ ^renv\.lock$ +.lintr ^xportr\.Rproj$ ^\.Rproj\.user$ ^LICENSE\.md$ @@ -21,3 +22,6 @@ ^advs\.xpt$ ^advs_Define-Excel-Spec_match_admiral\.xlsx ^cran-comments\.md$ +^example_data_specs$ + +^\.devcontainer$ diff --git a/.Rprofile b/.Rprofile index 7b58783a..dc1176b9 100644 --- a/.Rprofile +++ b/.Rprofile @@ -1,5 +1,3 @@ -if (Sys.getenv("GITHUB_ACTIONS") == "") { - source("renv/activate.R") -} else { +if (Sys.getenv("GITHUB_ACTIONS") != "") { options(repos = c(CRAN = "https://cran.rstudio.com")) } diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..98a41b90 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +{ + "image": "mcr.microsoft.com/devcontainers/universal:2", +"features": { + "ghcr.io/rocker-org/devcontainer-features/r-rig:1": {}, + "ghcr.io/rocker-org/devcontainer-features/r-packages:1": { + "packages": "dplyr,purrr,stringr,magrittr,glue,rlang,cli,tidyselect,readr,janitor,tm,haven,lifecycle,testthat,withr,knitr,rmarkdown,readxl,DT,labelled,admiral,devtools,spelling,usethis,lintr,metacore" + } + } +} diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml new file mode 100644 index 00000000..230e521b --- /dev/null +++ b/.github/workflows/check-links.yml @@ -0,0 +1,36 @@ +name: Check URLs ๐Ÿ”— + +on: + push: + branches: [main] + pull_request: + branches: [main, devel] + +jobs: + links: + name: Validate Links ๐Ÿ•ธ๏ธ + runs-on: ubuntu-latest + if: > + !contains(github.event.commits[0].message, '[skip links]') + steps: + - uses: actions/checkout@v3 + + - name: Check URLs in docs ๐Ÿ“‘ + uses: lycheeverse/lychee-action@v1.5.1 + with: + fail: true + jobSummary: true + format: markdown + output: links-results.md + args: >- + --exclude-private + --exclude "https://github.com.*.git|lycheeverse.*" + --verbose + --no-progress + **/*.md + **/*.html + **/*.Rmd + **/*.yaml + **/*.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/check-standard.yaml b/.github/workflows/check-standard.yaml index 07026974..c9239381 100644 --- a/.github/workflows/check-standard.yaml +++ b/.github/workflows/check-standard.yaml @@ -1,13 +1,13 @@ # Workflow derived from https://github.com/r-lib/actions/tree/master/examples # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +name: R-CMD-check ๐Ÿ“ฆ + on: push: branches: [main, devel] pull_request: branches: [main, devel] -name: R-CMD-check - jobs: R-CMD-check: runs-on: ${{ matrix.config.os }} @@ -29,7 +29,7 @@ jobs: R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: r-lib/actions/setup-pandoc@v2 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..cee715dc --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,32 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +name: Check Lint ๐Ÿงน + +on: + push: + branches: [main] + pull_request: + branches: [main, devel] + +jobs: + lint: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::lintr, local::. + needs: lint + + - name: Lint + run: lintr::lint_package() + shell: Rscript {0} + env: + LINTR_ERROR_ON_LINT: true diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 0ebc7032..16404107 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -1,18 +1,18 @@ +name: Deploy pkgdown site ๐Ÿ“œ + on: push: branches: - main - master -name: pkgdown - jobs: pkgdown: runs-on: macOS-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: r-lib/actions/setup-r@v2 diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index d0b4f381..42c7e327 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -1,5 +1,4 @@ ---- -name: Spelling ๐Ÿ†Ž +name: Check Spelling ๐Ÿ†Ž on: workflow_dispatch: @@ -27,7 +26,7 @@ jobs: && github.event.pull_request.draft == false steps: - name: Checkout repo ๐Ÿ›Ž - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: persist-credentials: false fetch-depth: 0 @@ -62,5 +61,5 @@ jobs: - name: Run Spellcheck ๐Ÿ‘Ÿ uses: insightsengineering/r-spellcheck-action@v3 with: - exclude: data/*,**/*.Rd,**/*.Rmd,**/*.md,*.md + exclude: data/*,**/*.Rd,**/*.md,*.md additional_options: "" diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml new file mode 100644 index 00000000..fc779f37 --- /dev/null +++ b/.github/workflows/style.yml @@ -0,0 +1,45 @@ +name: Check Style ๐ŸŽจ + +on: + push: + branches: [main] + pull_request: + branches: [main, devel] + +concurrency: + group: style-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + style: + name: Check code style ๐Ÿง‘โ€๐ŸŽจ + runs-on: ubuntu-latest + if: > + !contains(github.event.commits[0].message, '[skip stylecheck]') + && github.event.pull_request.draft == false + + steps: + - uses: actions/checkout@v3 + with: + path: ${{ github.event.repository.name }} + fetch-depth: 0 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - name: Install styler ๐Ÿ–Œ๏ธ + run: install.packages(c("styler", "knitr", "roxygen2"), repos = "https://cloud.r-project.org") + shell: Rscript {0} + + - name: Run styler ๐Ÿ–ผ๏ธ + run: | + detect <- styler::style_pkg(dry = "on") + if (TRUE %in% detect$changed) { + problems <- subset(detect$file, detect$changed == T) + cat(paste("Styling errors found in", length(problems), "files\n")) + cat("Please run `styler::style_pkg()` to fix the style\n") + quit(status = 1) + } + shell: Rscript {0} + working-directory: ${{ github.event.repository.name }} diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index dfa62f5d..dadf5abe 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -1,3 +1,5 @@ +name: Check Test Coverage ๐Ÿงช + on: push: branches: @@ -8,15 +10,13 @@ on: - main - master -name: test-coverage - jobs: test-coverage: runs-on: macOS-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: r-lib/actions/setup-r@v2 diff --git a/.gitignore b/.gitignore index 7bda4869..3cc2ea3a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,19 +2,16 @@ .Rhistory .RData .Ruserdata -xportr.Rproj .gitattributes -xptr.Rproj /archive /.idea scratch_check.R inst/doc adsl.xpt demo.R -/doc/ /Meta/ -docs +docs/ xportr.Rcheck/ xportr*.tar.gz xportr*.tgz -docs/* \ No newline at end of file +local diff --git a/.lintr b/.lintr new file mode 100644 index 00000000..80754030 --- /dev/null +++ b/.lintr @@ -0,0 +1,8 @@ +linters: linters_with_defaults( + line_length_linter(120), + object_usage_linter = NULL, + object_name_linter = NULL, + trailing_whitespace_linter(allow_empty_lines = TRUE, allow_in_strings = TRUE) + ) +encoding: "UTF-8" +exclusions: list() diff --git a/DESCRIPTION b/DESCRIPTION index 773c0df7..75421e2d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: xportr Title: Utilities to Output CDISC SDTM/ADaM XPT Files -Version: 0.2.0 +Version: 0.3.0 Authors@R: c( person(given = "Eli", @@ -10,23 +10,39 @@ Authors@R: comment = c(ORCID = "0000-0002-2127-9456")), person(given = "Vignesh ", family = "Thanikachalam", - role = c("aut"), - email = "vignesh.x.thanikachalam@gsk.com"), + role = c("aut")), person(given = "Ben", family = "Straub", - email = "ben.x.straub@gsk.com", - role = "aut"), + role = ("aut")), person(given = "Ross", family = "Didenko", - email = "Ross.Didenko@AtorusResearch.com", - role = "aut"), + role = ("aut")), person(given = "Zelos", family = "Zhu", - email = "Zelos.Zhu@AtorusResearch.com", - role = "aut"), + role = ("aut")), + person(given = "Ethan", + family = "Brockmann", + role = ("aut")), + person(given = "Vedha", + family = "Viyash", + role = ("aut")), + person(given = "Andre", + family = "Verissimo", + role = ("aut")), + person(given = "Sophie", + family = "Shapcott", + role = ("aut")), + person(given = "Celine", + family = "Piraux", + role = ("aut")), + person(given = "Adrian", + family = "Chan", + role = ("aut")), + person(given = "Sadchla", + family = "Mascary", + role = ("aut")), person(given = "Atorus/GSK JPT", - role = "cph") - ) + role = "cph")) Description: Tools to build CDISC compliant data sets and check for CDISC compliance. URL: https://github.com/atorus-research/xportr BugReports: https://github.com/atorus-research/xportr/issues @@ -42,7 +58,8 @@ Imports: readr, janitor, tm, - haven (>= 2.5.0) + haven (>= 2.5.0), + lifecycle License: MIT + file LICENSE Encoding: UTF-8 LazyData: true @@ -61,6 +78,8 @@ Suggests: spelling, usethis, lintr, - styler + metacore Config/testthat/edition: 3 VignetteBuilder: knitr +Depends: + R (>= 3.5) diff --git a/NAMESPACE b/NAMESPACE index cf77d50a..9e7b061b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -10,11 +10,13 @@ export(xportr_format) export(xportr_label) export(xportr_length) export(xportr_logger) +export(xportr_metadata) export(xportr_order) export(xportr_type) export(xportr_write) import(haven) import(rlang) +importFrom(cli,cli_alert_danger) importFrom(cli,cli_alert_info) importFrom(cli,cli_alert_success) importFrom(cli,cli_div) @@ -27,6 +29,7 @@ importFrom(dplyr,distinct) importFrom(dplyr,everything) importFrom(dplyr,filter) importFrom(dplyr,group_by) +importFrom(dplyr,if_else) importFrom(dplyr,left_join) importFrom(dplyr,mutate) importFrom(dplyr,n) @@ -34,17 +37,21 @@ importFrom(dplyr,rename) importFrom(dplyr,rename_with) importFrom(dplyr,select) importFrom(dplyr,summarize) +importFrom(dplyr,tribble) importFrom(dplyr,ungroup) importFrom(glue,glue) importFrom(glue,glue_collapse) importFrom(graphics,stem) importFrom(janitor,make_clean_names) +importFrom(lifecycle,deprecated) importFrom(magrittr,"%>%") importFrom(magrittr,extract2) importFrom(purrr,map) importFrom(purrr,map2_chr) importFrom(purrr,map_chr) importFrom(purrr,map_dbl) +importFrom(purrr,pluck) +importFrom(purrr,walk) importFrom(purrr,walk2) importFrom(readr,parse_number) importFrom(stringr,str_detect) diff --git a/NEWS.md b/NEWS.md index 1525ffa5..c066e5e7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,12 +1,41 @@ +# xportr 0.3.0 + +## New Features and Bug Fixes + +* Fixed an issue where `xportr_type()` would overwrite column labels, widths, and "sas.formats" +* Fixed messaging of `xportr_order()`to give better visibility of the number of variables being reordered. +* Add new argument to `xportr_write()` to allow users to specify how xpt validation checks are handled. +* Fixed bug where character_types were case sensitive. They are now case insensitive (#77). +* Updated `xportr_type()` to make type coercion more explicit. +* `xpt_validate` updated to accept iso8601 date formats. (#76) +* Added function `xportr_metadata()` to explicitly set metadata at the start of a pipeline (#44) +* Metadata order columns are now coerced to numeric by default in `xportr_order()` to prevent character sorting (#149) +* Message is shown on `xportr_*` functions when the metadata being used has multiple variables with the same name in the same domain (#128) +* Fixed an issue with `xport_type()` where `DT`, `DTM` variables with a format specified in the metadata (e.g. date9., datetime20.) were being converted to numeric, which will cause a 10 year difference when reading it back by `read_xpt()`. SAS's uniform start date is 1960 whereas Linux's uniform start date is 1970 (#142). +* Fixed an issue with R's pipe `|>` that was causing functions to abort (#97) +* Removed `<` and `>` as illegal characters in variable and dataset labels (#98) + +## Documentation + +* Moved `{pkgdown}` site to bootswatch. Enabled search and linked slack icon (#122). +* Additional Deep Dive vignette showcasing functions and quality of life utilities for processing `xpts` created (#84) +* Get Started vignette spruced up. Messages are now displayed and link to Deep Dive vignette (#150) +* Increase test coverage to 100% (#82) + +## Deprecation and Breaking Changes + +* The `metacore` argument has been renamed to `metadata` in the following six xportr functions: `xportr_df_label()`, `xportr_format()`, `xportr_label()`, `xportr_length()`, `xportr_order()`, and `xportr_type()`. Please update your code to use the new `metadata` argument in place of `metacore`. + # xportr 0.2.0 + * Added a new validation test that errors when users pass invalid formats (#60 #64). Thanks to @zdz2101! * Fixed an issue where xportr_format could pass invalid formats to haven::write_xpt. # xportr 0.1.0 -Beta release for xportr +Beta release for xportr -* Added exported functions `xportr_varnames` and `xportr_tidy_rename` into `dev` folder found on GitHub Repostiory. Intention to move into packages after CRAN release. +* Added exported functions `xportr_varnames` and `xportr_tidy_rename` into `dev` folder found on GitHub Repostiory. Intention to move into packages after CRAN release. * Fixed xportr_format() bug * Using admiral ADSL dataset in examples diff --git a/R/data.R b/R/data.R new file mode 100644 index 00000000..96e24de2 --- /dev/null +++ b/R/data.R @@ -0,0 +1,84 @@ +#' Analysis Dataset Subject Level +#' +#' An example dataset containing subject level data +#' +#' @format ## `adsl` +#' A data frame with 254 rows and 48 columns: +#' \describe{ +#' \item{STUDYID}{Study Identifier} +#' \item{USUBJID}{Unique Subject Identifier} +#' \item{SUBJID}{Subject Identifier for the Study} +#' \item{SITEID}{Study Site Identifier} +#' \item{SITEGR1}{Pooled Site Group 1} +#' \item{ARM}{Description of Planned Arm} +#' \item{TRT01P}{Planned Treatment for Period 01} +#' \item{TRT01PN}{Planned Treatment for Period 01 (N)} +#' \item{TRT01A}{Actual Treatment for Period 01} +#' \item{TRT01AN}{Actual Treatment for Period 01 (N)} +#' \item{TRTSDT}{Date of First Exposure to Treatment} +#' \item{TRTEDT}{Date of Last Exposure to Treatment} +#' \item{TRTDUR}{Duration of Treatment (days)} +#' \item{AVGDD}{Avg Daily Dose (as planned)} +#' \item{CUMDOSE}{Cumulative Dose (as planned)} +#' \item{AGE}{Age} +#' \item{AGEGR1}{Pooled Age Group 1} +#' \item{AGEGR1N}{Pooled Age Group 1 (N)} +#' \item{AGEU}{Age Units} +#' \item{RACE}{Race} +#' \item{RACEN}{Race (N)} +#' \item{SEX}{Sex} +#' \item{ETHNIC}{Ethnicity} +#' \item{SAFFL}{Safety Population Flag} +#' \item{ITTFL}{Intent-To-Treat Population Flag} +#' \item{EFFFL}{Efficacy Population Flag} +#' \item{COMP8FL}{Completers of Week 8 Population Flag} +#' \item{COMP16FL}{Completers of Week 16 Population Flag} +#' \item{COMP24FL}{Completers of Week 24 Population Flag} +#' \item{DISCONFL}{Did the Subject Discontinue the Study} +#' \item{DSRAEFL}{Discontinued due to AE} +#' \item{DTHFL}{Subject Died} +#' \item{BMIBL}{Baseline BMI (kg/m^2)} +#' \item{BMIBLGR1}{Pooled Baseline BMI Group 1} +#' \item{HEIGHTBL}{Baseline Height (cm)} +#' \item{WEIGHTBL}{Baseline Weight (kg)} +#' \item{EDUCLVL}{Years of Education} +#' \item{DISONSDT}{Date of Onset of Disease} +#' \item{DURDIS}{Duration of Disease (Months)} +#' \item{DURDSGR1}{Pooled Disease Duration Group 1} +#' \item{VISIT1DT}{Date of Visit 1} +#' \item{RFSTDTC}{Subject Reference Start Date/Time} +#' \item{RFENDTC}{Subject Reference End Date/Time} +#' \item{VISNUMEN}{End of Trt Visit (Vis 12 or Early Term.)} +#' \item{RFENDT}{Date of Discontinuation/Completion} +#' \item{DCDECOD}{Standardized Disposition Term} +#' \item{DCREASCD}{Reason for Discontinuation} +#' \item{MMSETOT}{MMSE Total} +#' } +"adsl" + +#' Example Dataset Specification +#' +#' @format ## `var_spec` +#' A data frame with 216 rows and 19 columns: +#' \describe{ +#' \item{Order}{Order of variable} +#' \item{Dataset}{Dataset} +#' \item{Variable}{Variable} +#' \item{Label}{Variable Label} +#' \item{Data Type}{Data Type} +#' \item{Length}{Variable Length} +#' \item{Significant Digits}{Significant Digits} +#' \item{Format}{Variable Format} +#' \item{Mandatory}{Mandatory Variable Flag} +#' \item{Assigned Value}{Variable Assigned Value} +#' \item{Codelist}{Variable Codelist} +#' \item{Common}{Common Variable Flag} +#' \item{Origin}{Variable Origin} +#' \item{Pages}{Pages} +#' \item{Method}{Variable Method} +#' \item{Predecessor}{Variable Predecessor} +#' \item{Role}{Variable Role} +#' \item{Comment}{Comment} +#' \item{Developer Notes}{Developer Notes} +#' } +"var_spec" diff --git a/R/df_label.R b/R/df_label.R index 33afc03c..0b3b7194 100644 --- a/R/df_label.R +++ b/R/df_label.R @@ -1,15 +1,28 @@ #' Assign Dataset Label #' #' Assigns dataset label from a dataset level metadata to a given data frame. +#' This is stored in the 'label' attribute of the dataframe. #' -#' @param .df A data frame of CDISC standard. -#' @param metacore A data frame containing dataset level metadata. -#' @param domain A character value to subset the `.df`. If `NULL`(default), uses -#' `.df` value as a subset condition. +#' @param metadata A data frame containing dataset. See 'Metadata' section for +#' details. +#' @inheritParams xportr_length #' #' @return Data frame with label attributes. -#' @family metadata functions -#' @seealso [xportr_label()], [xportr_format()] and [xportr_length()] +#' +#' @section Metadata: The argument passed in the 'metadata' argument can either +#' be a metacore object, or a data.frame containing the data listed below. If +#' metacore is used, no changes to options are required. +#' +#' For data.frame 'metadata' arguments two columns must be present: +#' +#' 1) Domain Name - passed as the 'xportr.df_domain_name' option. Default: +#' "dataset". This is the column subset by the 'domain' argument in the +#' function. +#' +#' 2) Label Name - passed as the 'xportr.df_label' option. Default: +#' "format". Character values to update the 'format.sas' attribute of the +#' dataframe This is passed to `haven::write_xpt` to note the label. +#' #' @export #' #' @examples @@ -19,55 +32,58 @@ #' AGE = c(63, 35, 27), #' SEX = c("M", "F", "M") #' ) -#' -#' metacore <- data.frame( +#' +#' metadata <- data.frame( #' dataset = c("adsl", "adae"), #' label = c("Subject-Level Analysis", "Adverse Events Analysis") #' ) #' -#' adsl <- xportr_df_label(adsl, metacore) -xportr_df_label <- function(.df, metacore, domain = NULL) { - +#' adsl <- xportr_df_label(adsl, metadata) +xportr_df_label <- function(.df, + metadata = NULL, + domain = NULL, + metacore = deprecated()) { + if (!missing(metacore)) { + lifecycle::deprecate_warn( + when = "0.3.0", + what = "xportr_df_label(metacore = )", + with = "xportr_df_label(metadata = )" + ) + metadata <- metacore + } domain_name <- getOption("xportr.df_domain_name") label_name <- getOption("xportr.df_label") - - - df_arg <- as_name(enexpr(.df)) - - if (!is.null(attr(.df, "_xportr.df_arg_"))) df_arg <- attr(.df, "_xportr.df_arg_") - else if(identical(df_arg, ".")){ - attr(.df, "_xportr.df_arg_") <- get_pipe_call() - df_arg <- attr(.df, "_xportr.df_arg_") - } - - if (!is.null(domain) && !is.character(domain)) { - abort(c("`domain` must be a vector with type .", - x = glue("Instead, it has type <{typeof(domain)}>.")) - ) + + ## Common section to detect domain from argument or pipes + + df_arg <- tryCatch(as_name(enexpr(.df)), error = function(err) NULL) + domain <- get_domain(.df, df_arg, domain) + if (!is.null(domain)) attr(.df, "_xportr.df_arg_") <- domain + + ## End of common section + + ## Pull out correct metadata + metadata <- metadata %||% + attr(.df, "_xportr.df_metadata_") %||% + rlang::abort("Metadata must be set with `metadata` or `xportr_metadata()`") + + if (inherits(metadata, "Metacore")) { + metadata <- metadata$ds_spec } - - df_arg <- domain %||% df_arg - - if(!is.null(domain)) attr(.df, "_xportr.df_arg_") <- domain - - if (inherits(metacore, "Metacore")) - metacore <- metacore$ds_spec - - label <- metacore %>% - filter(!!sym(domain_name) == df_arg) %>% + + label <- metadata %>% + filter(!!sym(domain_name) == domain) %>% select(!!sym(label_name)) %>% # If a dataframe is used this will also be a dataframe, change to character. as.character() - + label_len <- nchar(label) - + if (label_len > 40) { abort("Length of dataset label must be 40 characters or less.") } - - + attr(.df, "label") <- label - + .df } - diff --git a/R/format.R b/R/format.R index 89941701..17e15183 100644 --- a/R/format.R +++ b/R/format.R @@ -1,17 +1,31 @@ #' Assign SAS Format #' -#' Assigns a SAS format from a variable level metadata to a given data frame. +#' Assigns a SAS format from a variable level metadata to a given data frame. If +#' no format is found for a given variable, it is set as an empty character +#' vector. This is stored in the format.sas attribute. #' -#' @param .df A data frame of CDISC standard. -#' @param metacore A data frame containing variable level metadata. -#' @param domain A character value to subset the `.df`. If `NULL`(default), uses -#' `.df` value as a subset condition. -#' @param verbose The action the function takes when a variable label isn't. -#' found. Options are 'stop', 'warn', 'message', and 'none' +#' @inheritParams xportr_length #' #' @return Data frame with `SASformat` attributes for each variable. -#' @family metadata functions -#' @seealso [xportr_label()], [xportr_df_label()] and [xportr_length()] +#' +#' @section Metadata: The argument passed in the 'metadata' argument can either +#' be a metacore object, or a data.frame containing the data listed below. If +#' metacore is used, no changes to options are required. +#' +#' For data.frame 'metadata' arguments three columns must be present: +#' +#' 1) Domain Name - passed as the 'xportr.domain_name' option. Default: +#' "dataset". This is the column subset by the 'domain' argument in the +#' function. +#' +#' 2) Format Name - passed as the 'xportr.format_name' option. +#' Default: "format". Character values to update the 'format.sas' attribute of +#' the column. This is passed to `haven::write` to note the format. +#' +#' 3) Variable Name - passed as the 'xportr.variable_name' option. Default: +#' "variable". This is used to match columns in '.df' argument and the +#' metadata. +#' #' @export #' #' @examples @@ -19,69 +33,71 @@ #' USUBJID = c(1001, 1002, 1003), #' BRTHDT = c(1, 1, 2) #' ) -#' -#' metacore <- data.frame( +#' +#' metadata <- data.frame( #' dataset = c("adsl", "adsl"), #' variable = c("USUBJID", "BRTHDT"), #' format = c(NA, "DATE9.") #' ) #' -#' adsl <- xportr_format(adsl, metacore) -xportr_format <- function(.df, metacore, domain = NULL, verbose = getOption("xportr.format_verbose", "none")) { - +#' adsl <- xportr_format(adsl, metadata) +xportr_format <- function(.df, + metadata = NULL, + domain = NULL, + metacore = deprecated()) { + if (!missing(metacore)) { + lifecycle::deprecate_warn( + when = "0.3.0", + what = "xportr_format(metacore = )", + with = "xportr_format(metadata = )" + ) + metadata <- metacore + } domain_name <- getOption("xportr.domain_name") format_name <- getOption("xportr.format_name") variable_name <- getOption("xportr.variable_name") - - - df_arg <- as_name(enexpr(.df)) - - if (!is.null(attr(.df, "_xportr.df_arg_"))) df_arg <- attr(.df, "_xportr.df_arg_") - else if (identical(df_arg, ".")) { - attr(.df, "_xportr.df_arg_") <- get_pipe_call() - df_arg <- attr(.df, "_xportr.df_arg_") - } - - if (!is.null(domain) && !is.character(domain)) { - abort(c("`domain` must be a vector with type .", - x = glue("Instead, it has type <{typeof(domain)}>.")) - ) - } - - df_arg <- domain %||% df_arg - + + ## Common section to detect domain from argument or pipes + + df_arg <- tryCatch(as_name(enexpr(.df)), error = function(err) NULL) + domain <- get_domain(.df, df_arg, domain) if (!is.null(domain)) attr(.df, "_xportr.df_arg_") <- domain - - if (inherits(metacore, "Metacore")) - metacore <- metacore$var_spec - - if (domain_name %in% names(metacore)) { - metadata <- metacore %>% - dplyr::filter(!!sym(domain_name) == df_arg & !is.na(!!sym(format_name))) + + ## End of common section + + metadata <- metadata %||% + attr(.df, "_xportr.df_metadata_") %||% + rlang::abort("Metadata must be set with `metadata` or `xportr_metadata()`") + + if (inherits(metadata, "Metacore")) { + metadata <- metadata$var_spec + } + + if (domain_name %in% names(metadata)) { + metadata <- metadata %>% + dplyr::filter(!!sym(domain_name) == domain & !is.na(!!sym(format_name))) } else { - metadata <- metacore + # Common check for multiple variables name + check_multiple_var_specs(metadata, variable_name) } - + filtered_metadata <- metadata %>% filter(!!sym(variable_name) %in% names(.df)) - format <- filtered_metadata %>% - select(!!sym(format_name)) %>% + select(!!sym(format_name)) %>% unlist() %>% toupper() names(format) <- filtered_metadata[[variable_name]] - - for (i in names(format)) { - attr(.df[[i]], "format.sas") <- format[[i]] - } - - # Convert NA formats to "" for haven + for (i in seq_len(ncol(.df))) { - if (is.na(attr(.df[[i]], "format.sas")) || is.null(attr(.df[[i]], "format.sas"))) - attr(.df[[i]], "format.sas") <- "" + format_sas <- purrr::pluck(format, colnames(.df)[i]) + if (is.na(format_sas) || is.null(format_sas)) { + format_sas <- "" + } + attr(.df[[i]], "format.sas") <- format_sas } - + .df -} \ No newline at end of file +} diff --git a/R/label.R b/R/label.R index c346823f..e412e9fc 100644 --- a/R/label.R +++ b/R/label.R @@ -1,17 +1,44 @@ #' Assign Variable Label #' #' Assigns variable label from a variable level metadata to a given data frame. +#' This function will give detect if a label is greater than +#' 40 characters which isn't allowed in XPT v5. If labels aren't present for the +#' variable it will be assigned an empty character value. Labels are stored in +#' the 'label' attribute of the column. +#' +#' @inheritParams xportr_length +#' +#' @section Messaging: `label_log()` is the primary messaging tool for +#' `xportr_label()`. If there are any columns present in the '.df' that are not +#' noted in the metadata, they cannot be assigned a label and a message will +#' be generated noting the number or variables that have not been assigned a +#' label. +#' +#' If variables were not found in the metadata and the value passed to the +#' 'verbose' argument is 'stop', 'warn', or 'message', a message will be +#' generated detailing the variables that were missing in metadata. +#' +#' @section Metadata: The argument passed in the 'metadata' argument can either +#' be a metacore object, or a data.frame containing the data listed below. If +#' metacore is used, no changes to options are required. +#' +#' For data.frame 'metadata' arguments three columns must be present: +#' +#' 1) Domain Name - passed as the 'xportr.domain_name' option. Default: +#' "dataset". This is the column subset by the 'domain' argument in the +#' function. +#' +#' 2) Variable Name - passed as the 'xportr.variable_name' option. +#' Default: "variable". This is used to match columns in '.df' argument and +#' the metadata. +#' +#' 3) Variable Label - passed as the 'xportr.label' option. +#' Default: "label". These character values to update the 'label' attribute of +#' the column. This is passed to `haven::write` to note the label. #' -#' @param .df A data frame of CDISC standard. -#' @param metacore A data frame containing variable level metadata. -#' @param domain A character value to subset the `.df`. If `NULL`(default), uses -#' `.df` value as a subset condition. -#' @param verbose The action the function takes when a variable length isn't -#' Found. Options are 'stop', 'warn', 'message', and 'none' #' #' @return Data frame with label attributes for each variable. -#' @family metadata functions -#' @seealso [xportr_df_label()], [xportr_format()] and [xportr_length()] +#' #' @export #' #' @examples @@ -21,73 +48,83 @@ #' AGE = c(63, 35, 27), #' SEX = c("M", "F", "M") #' ) -#' -#' metacore <- data.frame( +#' +#' metadata <- data.frame( #' dataset = "adsl", #' variable = c("USUBJID", "SITEID", "AGE", "SEX"), #' label = c("Unique Subject Identifier", "Study Site Identifier", "Age", "Sex") #' ) #' -#' adsl <- xportr_label(adsl, metacore) -xportr_label <- function(.df, metacore, domain = NULL, - verbose = getOption("xportr.label_verbose", "none")) { - +#' adsl <- xportr_label(adsl, metadata) +xportr_label <- function(.df, + metadata = NULL, + domain = NULL, + verbose = getOption("xportr.label_verbose", "none"), + metacore = deprecated()) { + if (!missing(metacore)) { + lifecycle::deprecate_warn( + when = "0.3.0", + what = "xportr_label(metacore = )", + with = "xportr_label(metadata = )" + ) + metadata <- metacore + } domain_name <- getOption("xportr.domain_name") variable_name <- getOption("xportr.variable_name") variable_label <- getOption("xportr.label") - - df_arg <- as_name(enexpr(.df)) - - if (!is.null(attr(.df, "_xportr.df_arg_"))) df_arg <- attr(.df, "_xportr.df_arg_") - else if (identical(df_arg, ".")) { - attr(.df, "_xportr.df_arg_") <- get_pipe_call() - df_arg <- attr(.df, "_xportr.df_arg_") - } - - if (!is.null(domain) && !is.character(domain)) { - abort(c("`domain` must be a vector with type .", - x = glue("Instead, it has type <{typeof(domain)}>.")) - ) - } - - df_arg <- domain %||% df_arg - + + ## Common section to detect domain from argument or pipes + + df_arg <- tryCatch(as_name(enexpr(.df)), error = function(err) NULL) + domain <- get_domain(.df, df_arg, domain) if (!is.null(domain)) attr(.df, "_xportr.df_arg_") <- domain - - if (inherits(metacore, "Metacore")) - metacore <- metacore$var_spec - - if (domain_name %in% names(metacore)) { - metadata <- metacore %>% - dplyr::filter(!!sym(domain_name) == df_arg) + + ## End of common section + + metadata <- metadata %||% + attr(.df, "_xportr.df_metadata_") %||% + rlang::abort("Metadata must be set with `metadata` or `xportr_metadata()`") + + if (inherits(metadata, "Metacore")) { + metadata <- metadata$var_spec + } + + if (domain_name %in% names(metadata)) { + metadata <- metadata %>% + dplyr::filter(!!sym(domain_name) == domain) } else { - metadata <- metacore + # Common check for multiple variables name + check_multiple_var_specs(metadata, variable_name) } - + # Check any variables missed in metadata but present in input data --- miss_vars <- setdiff(names(.df), metadata[[variable_name]]) label_log(miss_vars, verbose) - + label <- metadata[[variable_label]] names(label) <- metadata[[variable_name]] - + # Check any variable label have more than 40 characters --- label_len <- lapply(label, nchar) - err_len <- which(label_len > 40) %>% names - + err_len <- which(label_len > 40) %>% names() + if (length(err_len) > 0) { warn( c("Length of variable label must be 40 characters or less.", - x = glue("Problem with {encode_vars(err_len)}.")) + x = glue("Problem with {encode_vars(err_len)}.") + ) ) } - + for (i in names(.df)) { - if (i %in% miss_vars) attr(.df[[i]], "label") <- "" - else attr(.df[[i]], "label") <- label[[i]] + if (i %in% miss_vars) { + attr(.df[[i]], "label") <- "" + } else { + attr(.df[[i]], "label") <- label[[i]] + } } - + .df -} \ No newline at end of file +} diff --git a/R/length.R b/R/length.R index 2ed45027..17627268 100644 --- a/R/length.R +++ b/R/length.R @@ -1,17 +1,53 @@ #' Assign SAS Length #' -#' Assigns SAS length from a variable level metadata to a given data frame. +#' Assigns SAS length from a metadata object to a given data frame. If a +#' length isn't present for a variable the length value is set to 200 for +#' character columns, and 8 for non-character columns. This value is stored in +#' the 'width' attribute of the column. #' #' @param .df A data frame of CDISC standard. -#' @param metacore A data frame containing variable level metadata. -#' @param domain A character value to subset the `.df`. If `NULL`(default), uses -#' `.df` value as a subset condition. -#' @param verbose The action the function takes when a length isn't found in -#' metadata. Options are 'stop', 'warn', 'message', and 'none' +#' @param metadata A data frame containing variable level metadata. See +#' 'Metadata' section for details. +#' @param domain Appropriate CDSIC dataset name, e.g. ADAE, DM. Used to subset +#' the metadata object. If none is passed, then name of the dataset passed as +#' .df will be used. +#' @param verbose The action this function takes when an action is taken on the +#' dataset or function validation finds an issue. See 'Messaging' section for +#' details. Options are 'stop', 'warn', 'message', and 'none' +#' @param metacore `r lifecycle::badge("deprecated")` Previously used to pass +#' metadata now renamed with `metadata` +#' +#' @section Messaging: `length_log` is the primary messaging tool for +#' `xportr_length`. If there are any columns present in the '.df' that are not +#' noted in the metadata, they cannot be assigned a length and a message will +#' be generated noting the number or variables that have not been assigned a +#' length. +#' +#' If variables were not found in the metadata and the value passed to the +#' 'verbose' argument is 'stop', 'warn', or 'message', a message will be +#' generated detailing the variables that were missing in the metadata. +#' +#' @section Metadata: The argument passed in the 'metadata' argument can either +#' be a `{metacore}` object, or a data.frame containing the data listed below. If +#' metacore is used, no changes to options are required. +#' +#' For data.frame 'metadata' arguments three columns must be present: +#' +#' 1) Domain Name - passed as the 'xportr.domain_name' option. Default: +#' "dataset". This is the column subset by the 'domain' argument in the +#' function. +#' +#' 2) Variable Name - passed as the 'xportr.variable_name' option. +#' Default: "variable". This is used to match columns in '.df' argument and +#' the metadata. +#' +#' 3) Variable Label - passed as the 'xportr.length' option. +#' Default: "length". These numeric values to update the 'width' attribute of +#' the column. This is passed to `haven::write` to note the variable length. +#' #' #' @return Data frame with `SASlength` attributes for each variable. -#' @family metadata functions -#' @seealso [xportr_label()], [xportr_df_label()] and [xportr_format()] +#' #' @export #' #' @examples @@ -20,72 +56,80 @@ #' BRTHDT = c(1, 1, 2) #' ) #' -#' metacore <- data.frame( +#' metadata <- data.frame( #' dataset = c("adsl", "adsl"), #' variable = c("USUBJID", "BRTHDT"), #' length = c(10, 8) #' ) -#' -#' adsl <- xportr_length(adsl, metacore) -xportr_length <- function(.df, metacore, domain = NULL, - verbose = getOption("xportr.length_verbose", "none")) { - +#' +#' adsl <- xportr_length(adsl, metadata) +xportr_length <- function(.df, + metadata = NULL, + domain = NULL, + verbose = getOption("xportr.length_verbose", "none"), + metacore = deprecated()) { + if (!missing(metacore)) { + lifecycle::deprecate_warn( + when = "0.3.0", + what = "xportr_length(metacore = )", + with = "xportr_length(metadata = )" + ) + metadata <- metacore + } domain_name <- getOption("xportr.domain_name") variable_length <- getOption("xportr.length") variable_name <- getOption("xportr.variable_name") - - df_arg <- as_name(enexpr(.df)) - - if (!is.null(attr(.df, "_xportr.df_arg_"))) df_arg <- attr(.df, "_xportr.df_arg_") - else if (identical(df_arg, ".")) { - attr(.df, "_xportr.df_arg_") <- get_pipe_call() - df_arg <- attr(.df, "_xportr.df_arg_") - } - - if (!is.null(domain) && !is.character(domain)) { - abort(c("`domain` must be a vector with type .", - x = glue("Instead, it has type <{typeof(domain)}>.")) - ) - } - - df_arg <- domain %||% df_arg - + + ## Common section to detect domain from argument or pipes + + df_arg <- tryCatch(as_name(enexpr(.df)), error = function(err) NULL) + domain <- get_domain(.df, df_arg, domain) if (!is.null(domain)) attr(.df, "_xportr.df_arg_") <- domain - - if (inherits(metacore, "Metacore")) - metacore <- metacore$var_spec - - if (domain_name %in% names(metacore)) { - metadata <- metacore %>% - dplyr::filter(!!sym(domain_name) == df_arg) + + ## End of common section + + metadata <- metadata %||% + attr(.df, "_xportr.df_metadata_") %||% + rlang::abort("Metadata must be set with `metadata` or `xportr_metadata()`") + + if (inherits(metadata, "Metacore")) { + metadata <- metadata$var_spec + } + + if (domain_name %in% names(metadata)) { + metadata <- metadata %>% + filter(!!sym(domain_name) == domain) } else { - metadata <- metacore + # Common check for multiple variables name + check_multiple_var_specs(metadata, variable_name) } - + # Check any variables missed in metadata but present in input data --- miss_vars <- setdiff(names(.df), metadata[[variable_name]]) - + length_log(miss_vars, verbose) - + length <- metadata[[variable_length]] names(length) <- metadata[[variable_name]] - + for (i in names(.df)) { if (i %in% miss_vars) { attr(.df[[i]], "width") <- impute_length(.df[[i]]) } else { attr(.df[[i]], "width") <- length[[i]] } - } - + .df } impute_length <- function(col) { characterTypes <- getOption("xportr.character_types") # first_class will collapse to character if it is the option - if (first_class(col) %in% "character") 200 - else 8 + if (first_class(col) %in% "character") { + 200 + } else { + 8 + } } diff --git a/R/messages.R b/R/messages.R index 2dae3051..6c4e21c0 100644 --- a/R/messages.R +++ b/R/messages.R @@ -1,5 +1,5 @@ #' Utility Logging Function -#' +#' #' Functions to output user messages, usually relating to differences #' found between dataframe and the metacore/metadata object #' @@ -10,15 +10,14 @@ #' @return Output to Console #' @export xportr_logger <- function(message, type = "none", ...) { - log_fun <- switch(type, - stop = abort, - warn = warn, - message = inform, - return()) - - do.call(log_fun, list(message, ...)) + stop = abort, + warn = warn, + message = inform, + return() + ) + do.call(log_fun, list(message, ...)) } #' Utility for Renaming Variables @@ -28,32 +27,43 @@ xportr_logger <- function(message, type = "none", ...) { #' #' @return Output to Console #' @export -var_names_log <- function(tidy_names_df, verbose){ - - +var_names_log <- function(tidy_names_df, verbose) { only_renames <- tidy_names_df %>% filter(original_varname != renamed_var) %>% - mutate(renamed_msg = paste0("Var ", col_pos, ": '", original_varname, - "' was renamed to '", renamed_var, "'")) - + mutate( + renamed_msg = glue( + "Var {col_pos} : '{original_varname}' was renamed to 'renamed_var'" + ) + ) + # Message regarding number of variables that were renamed/ modified num_renamed <- nrow(only_renames) tot_num_vars <- nrow(tidy_names_df) - message("\n") - cli::cli_h2(paste0( num_renamed, " of ", tot_num_vars, " (", - round(100*(num_renamed/tot_num_vars), 1), "%) variables were renamed")) - + + cli_h2(glue( + .sep = " ", + "{num_renamed} of {tot_num_vars}", + "({round(100*(num_renamed/tot_num_vars), 1)}%)", + "variables were renamed" + )) + # Message stating any renamed variables each original variable and it's new name - if (nrow(only_renames) > 0) message(paste0(paste(only_renames$renamed_msg, collapse = "\n"), "\n")) - + if (nrow(only_renames) > 0) { + purrr::walk(only_renames$renamed_msg, ~ xportr_logger(.x, verbose)) + } + # Message checking for duplicate variable names after renamed (Pretty sure # this is impossible) but good to have a check none-the-less. dups <- tidy_names_df %>% filter(renamed_n > 1) if (nrow(dups) != 0) { cli::cli_alert_danger( - paste("Duplicate renamed term(s) were created. Consider creating dictionary terms for:", - paste(unique(dups$renamed_var), collapse = ", ") - )) + glue( + .sep = " ", + "Duplicate renamed term(s) were created.", + "Consider creating dictionary terms for:", + encode_vars(unique(dups$renamed_var)) + ) + ) } } @@ -65,40 +75,37 @@ var_names_log <- function(tidy_names_df, verbose){ #' #' @return Output to Console #' @export -type_log <- function(meta_ordered, type_mismatch_ind, verbose){ - +type_log <- function(meta_ordered, type_mismatch_ind, verbose) { if (length(type_mismatch_ind) > 0) { - + cli_h2("Variable type mismatches found.") + cli_alert_success("{ length(type_mismatch_ind) } variables coerced") + message <- glue( "Variable type(s) in dataframe don't match metadata: ", - paste0(glue("{encode_vars(meta_ordered[type_mismatch_ind, 'variable'])}"), - collapse = "", sep = " ") + "{encode_vars(meta_ordered[type_mismatch_ind, 'variable'])}" ) xportr_logger(message, verbose) - - cli_h2("Variable type mismatches found.") - cli_alert_success("{ length(type_mismatch_ind) } variables coerced") } } #' Utility for Lengths #' -#' @param miss_vars Variables missing from metatdata +#' @param miss_vars Variables missing from metadata #' @param verbose Provides additional messaging for user #' #' @return Output to Console #' @export length_log <- function(miss_vars, verbose) { - if (length(miss_vars) > 0) { - cli_h2("Variable lengths missing from metadata.") cli_alert_success("{ length(miss_vars) } lengths resolved") - + xportr_logger( - c("Variable(s) present in dataframe but doesn't exist in `metadata`.", - x = glue("Problem with {encode_vars(miss_vars)}")), + glue( + "Variable(s) present in dataframe but doesn't exist in `metadata`.", + "Problem with {encode_vars(miss_vars)}" + ), type = verbose ) } @@ -111,40 +118,46 @@ length_log <- function(miss_vars, verbose) { #' #' @return Output to Console #' @export -label_log <- function(miss_vars, verbose){ +label_log <- function(miss_vars, verbose) { if (length(miss_vars) > 0) { - cli_h2("Variable labels missing from metadata.") cli_alert_success("{ length(miss_vars) } labels skipped") - + xportr_logger( c("Variable(s) present in dataframe but doesn't exist in `metadata`.", - x = glue("Problem with {encode_vars(miss_vars)}")), + x = glue("Problem with {encode_vars(miss_vars)}") + ), type = verbose ) } } - #' Utility for Ordering #' -#' @param moved_vars Variables moved in the dataset +#' @param reordered_vars Number of variables reordered +#' @param moved_vars Number of variables moved in the dataset #' @param verbose Provides additional messaging for user #' #' @return Output to Console #' @export -var_ord_msg <- function(moved_vars, verbose){ - - if (moved_vars > 0) { +var_ord_msg <- function(reordered_vars, moved_vars, verbose) { + if (length(moved_vars) > 0) { cli_h2("{ length(moved_vars) } variables not in spec and moved to end") message <- glue( - "Variable reordered in `.df`: ", - paste0(glue("{ encode_vars(moved_vars) }"), - collapse = "", sep = " ") + "Variable moved to end in `.df`: { encode_vars(moved_vars) }" ) xportr_logger(message, verbose) } else { cli_h2("All variables in specification file are in dataset") } + if (length(reordered_vars) > 0) { + cli_h2("{ length(reordered_vars) } reordered in dataset") + message <- glue( + "Variable reordered in `.df`: { encode_vars(reordered_vars) }" + ) + xportr_logger(message, verbose) + } else { + cli_h2("All variables in dataset are ordered") + } } diff --git a/R/metadata.R b/R/metadata.R new file mode 100644 index 00000000..1fdabc28 --- /dev/null +++ b/R/metadata.R @@ -0,0 +1,50 @@ +#' Set variable specifications and domain +#' +#' Sets metadata for a dataset in a way that can be accessed by other xportr +#' functions. If used at the start of an xportr pipeline, it removes the need to +#' set metadata and domain at each step individually. For details on the format +#' of the metadata, see the 'Metadata' section for each function in question. +#' +#' @inheritParams xportr_length +#' +#' @return `.df` dataset with metadata and domain attributes set +#' @export +#' +#' @examples +#' +#' metadata <- data.frame( +#' dataset = "test", +#' variable = c("Subj", "Param", "Val", "NotUsed"), +#' type = c("numeric", "character", "numeric", "character"), +#' format = NA, +#' order = c(1, 3, 4, 2) +#' ) +#' +#' adlb <- data.frame( +#' Subj = as.character(123, 456, 789), +#' Different = c("a", "b", "c"), +#' Val = c("1", "2", "3"), +#' Param = c("param1", "param2", "param3") +#' ) +#' +#' xportr_metadata(adlb, metadata, "test") +#' +#' if (rlang::is_installed("magrittr")) { +#' library(magrittr) +#' +#' adlb %>% +#' xportr_metadata(metadata, "test") %>% +#' xportr_type() %>% +#' xportr_order() +#' } +xportr_metadata <- function(.df, metadata, domain = NULL) { + ## Common section to detect domain from argument or pipes + + df_arg <- tryCatch(as_name(enexpr(.df)), error = function(err) NULL) + domain <- get_domain(.df, df_arg, domain) + if (!is.null(domain)) attr(.df, "_xportr.df_arg_") <- domain + + ## End of common section + + structure(.df, `_xportr.df_metadata_` = metadata) +} diff --git a/R/order.R b/R/order.R index ee1c056b..0f7e1b30 100644 --- a/R/order.R +++ b/R/order.R @@ -1,78 +1,129 @@ #' Order variables of a dataset according to Spec #' -#' @param .df A data frame of CDISC standard. -#' @param metacore A data frame containing variable level metadata. -#' @param domain A character value to subset the `.df`. If `NULL`(default), uses -#' `.df` value as a subset condition. -#' @param verbose Option for messaging order results -#' +#' The `dplyr::arrange()` function is used to order the columns of the dataframe. +#' Any variables that are missing an order value are appended to the end of the dataframe +#' after all of the variables that have an order. +#' +#' @inheritParams xportr_length +#' #' @export +#' +#' @section Messaging: `var_ord_msg()` is the primary messaging tool for +#' `xportr_order()`. There are two primary messages that are output from +#' `var_ord_msg()`. The first is the "moved" variables. These are the variables +#' that were not found in the metadata file and moved to the end of the +#' dataset. A message will be generated noting the number, if any, of +#' variables that were moved to the end of the dataset. If any variables were +#' moved, and the 'verbose' argument is 'stop', 'warn', or 'message', a +#' message will be generated detailing the variables that were moved. +#' +#' The second primary message is the number of variables that were in the +#' dataset, but not in the correct order. A message will be generated noting +#' the number, if any, of variables that have been reordered. If any variables +#' were reordered, and the 'verbose' argument is 'stop', 'warn', or 'message', +#' a message will be generated detailing the variables that were reordered. +#' +#' @section Metadata: The argument passed in the 'metadata' argument can either +#' be a metacore object, or a data.frame containing the data listed below. If +#' metacore is used, no changes to options are required. +#' +#' For data.frame 'metadata' arguments three columns must be present: +#' +#' 1) Domain Name - passed as the 'xportr.domain_name' option. Default: +#' "dataset". This is the column subset by the 'domain' argument in the +#' function. +#' +#' 2) Variable Name - passed as the 'xportr.variable_name' option. +#' Default: "variable". This is used to match columns in '.df' argument and +#' the metadata. +#' +#' 3) Variable Order - passed as the 'xportr.order_name' option. +#' Default: "order". These values used to arrange the order of the variables. +#' If the values of order metadata are not numeric, they will be corsersed to +#' prevent alphabetical sorting of numberic values. +#' #' @return Dataframe that has been re-ordered according to spec -#' -xportr_order <- function(.df, metacore, domain = NULL, verbose = getOption("xportr.order_verbose", "none")) { - +#' +#' @examples +#' adsl <- data.frame( +#' BRTHDT = c(1, 1, 2), +#' STUDYID = c("mid987650", "mid987650", "mid987650"), +#' TRT01A = c("Active", "Active", "Placebo"), +#' USUBJID = c(1001, 1002, 1003) +#' ) +#' +#' metadata <- data.frame( +#' dataset = c("adsl", "adsl", "adsl", "adsl"), +#' variable = c("STUDYID", "USUBJID", "TRT01A", "BRTHDT"), +#' order = 1:4 +#' ) +#' +#' adsl <- xportr_order(adsl, metadata) +xportr_order <- function(.df, + metadata = NULL, + domain = NULL, + verbose = getOption("xportr.order_verbose", "none"), + metacore = deprecated()) { + if (!missing(metacore)) { + lifecycle::deprecate_warn( + when = "0.3.0", + what = "xportr_order(metacore = )", + with = "xportr_order(metadata = )" + ) + metadata <- metacore + } domain_name <- getOption("xportr.domain_name") order_name <- getOption("xportr.order_name") variable_name <- getOption("xportr.variable_name") - - - df_arg <- as_name(enexpr(.df)) - - if (!is.null(attr(.df, "_xportr.df_arg_"))) df_arg <- attr(.df, "_xportr.df_arg_") - else if (identical(df_arg, ".")) { - attr(.df, "_xportr.df_arg_") <- get_pipe_call() - df_arg <- attr(.df, "_xportr.df_arg_") - } - - if (!is.null(domain) && !is.character(domain)) { - abort(c("`domain` must be a vector with type .", - x = glue("Instead, it has type <{typeof(domain)}>.")) - ) - } - - df_arg <- domain %||% df_arg - + + ## Common section to detect domain from argument or pipes + + df_arg <- tryCatch(as_name(enexpr(.df)), error = function(err) NULL) + domain <- get_domain(.df, df_arg, domain) if (!is.null(domain)) attr(.df, "_xportr.df_arg_") <- domain - - if (inherits(metacore, "Metacore")) - metacore <- metacore$ds_vars - - if (domain_name %in% names(metacore)) { - metadata <- metacore %>% - dplyr::filter(!!sym(domain_name) == df_arg & !is.na(!!sym(order_name))) + + ## End of common section + + metadata <- metadata %||% + attr(.df, "_xportr.df_metadata_") %||% + rlang::abort("Metadata must be set with `metadata` or `xportr_metadata()`") + + if (inherits(metadata, "Metacore")) { + metadata <- metadata$ds_vars + } + + if (domain_name %in% names(metadata)) { + metadata <- metadata %>% + dplyr::filter(!!sym(domain_name) == domain & !is.na(!!sym(order_name))) } else { - metadata <- metacore %>% + metadata <- metadata %>% dplyr::filter(!is.na(!!sym(order_name))) + # Common check for multiple variables name + check_multiple_var_specs(metadata, variable_name) } - + # Grabs vars from Spec and inputted dataset vars_in_spec_ds <- metadata[, c(variable_name, order_name)] %>% + mutate(!!sym(order_name) := as.numeric(!!sym(order_name))) %>% arrange(!!sym(order_name)) %>% extract2(variable_name) - + vars_in_spec_ds <- vars_in_spec_ds[!is.na(vars_in_spec_ds)] # Grabs all variables from Spec file and orders accordingly - ord_vars <- .df %>% + ord_vars <- .df %>% select(any_of(vars_in_spec_ds)) - + # Variables not in Spec file - will be moved to the end - drop_vars <- .df %>% + drop_vars <- .df %>% select(!any_of(vars_in_spec_ds)) - - # Used in warning message for how many vars have been moved - moved_vars <- ncol(drop_vars) - ordered_vars <- ncol(ord_vars) - - df_re_ord <- bind_cols(ord_vars, drop_vars) - - # Function is located in messages.R - var_ord_msg(moved_vars, verbose) - - return(df_re_ord) -} - - + df_re_ord <- bind_cols(ord_vars, drop_vars) + # Used in warning message for how many vars have been moved + reorder_vars <- names(df_re_ord)[names(df_re_ord) != names(.df)] + # Function is located in messages.R + var_ord_msg(reorder_vars, names(drop_vars), verbose) + df_re_ord +} diff --git a/R/support-test.R b/R/support-test.R new file mode 100644 index 00000000..fa6e1c4f --- /dev/null +++ b/R/support-test.R @@ -0,0 +1,184 @@ +#' Custom expect function to test result of xportr_length +#' +#' @param result data.frame with `width` attribute on its columns. +#' @param metadata_length vector of numeric with expected lengths for each +#' column width. +#' +#' @return The first argument, invisibly. +#' @keywords internal +expect_attr_width <- function(result, metadata_length) { + test_widths <- map( + colnames(result), ~ attributes(result[[.x]]) %>% pluck("width") + ) %>% + unlist() == metadata_length + + test_widths %>% + all() %>% + testthat::expect_true() + invisible(result) +} + + +#' Minimal data frame mock of a valid ADaM dataset +#' +#' This function is only used in tests. +#' +#' @param n_rows Numeric value that indicates the number of rows of the data +#' frame +#' @param cols Vector of characters that indicates which columns to return. +#' By default only `x` and `y` are returned with numeric contents. +#' +#' @return A data.frame mimicking a valid ADaM dataset. +#' @keywords internal +minimal_table <- function(n_rows = 3, cols = c("x", "y")) { + data.frame( + x = sample(1000 + seq(n_rows * 100), size = n_rows), + y = sample(c(0, 1, 2), size = n_rows, replace = TRUE), + z = 3, + a = 4, + b = sample( + c("Recusandae", "vero", "nihil", "velit", "omnis"), + size = n_rows, + replace = TRUE + ), + c = sample( + Sys.time() - 3600 * c(1, 10, 100, 1000), + size = n_rows, + replace = TRUE + ), + d = sample(Sys.Date() + c(1, -1, 10, -10), size = n_rows, replace = TRUE) + ) %>% + select(all_of(cols)) +} + +#' Minimal metadata data frame mock for a ADaM dataset +#' +#' @param dataset Flag that indicates that the `dataset` column should +#' be included. +#' @param length Flag that indicates that the `length` column should +#' be included. +#' @param label Flag that indicates that the `label` column should +#' be included. +#' @param type Flag that indicates that the `type` column should +#' be included. +#' @param format Flag that indicates that the `format` column should +#' be included. +#' @param order Flag that indicates that the `order` column should +#' be included. +#' @param dataset_name String with name of domain. +#' @param var_names Character vector that defines which variables (rows) +#' to keep +#' +#' @return A metadata data.frame +#' @keywords internal +minimal_metadata <- function(dataset = FALSE, + length = FALSE, + label = FALSE, + type = FALSE, + format = FALSE, + order = FALSE, + dataset_name = "adsl", + var_names = NULL) { + cols_logical <- c(dataset, TRUE, label, length, type, format, order) + cols <- c( + "dataset", "variable", "label", "length", "type", "format", "order" + )[cols_logical] + + metadata <- tribble( + ~dataset, ~variable, ~label, ~length, ~type, ~format, ~order, + "adsl", "x", "Lorem", 8, "numeric", NA, 1, + "adsl", "y", "Ipsum", 200, "numeric", NA, 2, + "adsl", "z", "Dolor", 8, "numeric", NA, 3, + "adsl", "a", "Sit", 8, "numeric", NA, 4, + "adsl", "b", "Amet", 200, "character", NA, 5, + "adsl", "c", "Consectetur", 200, "character", "datetime20.", 6, + "adsl", "d", "Adipiscing", 200, "date", "date9.", 7 + ) + + if (!is.null(var_names)) { + metadata <- metadata %>% + filter(.data$variable %in% var_names) + } + + metadata %>% select(all_of(cols)) +} + + +#' Local function to remove extra spaces and format by cli +#' +#' Groups together multiple calls instead of being spread out in code +#' +#' @param `[environment(1)]`\cr Attach exit handlers to this environment. Typically, this should +#' be either the current environment or a parent frame +#' (accessed through parent.frame()). +#' @keywords internal +local_cli_theme <- function(.local_envir = parent.frame()) { + cli_theme_tests <- list( + h2 = list(`margin-top` = 0, `margin-bottom` = 0, fmt = function(x) x), + h1 = list(`margin-top` = 0, `margin-bottom` = 0, fmt = function(x) x), + `.alert` = list(before = NULL), + `.alert-danger` = list(before = NULL), + `.alert-success` = list(before = NULL) + ) + + withr::local_options(list(cli.user_theme = cli_theme_tests), .local_envir = .local_envir) + withr::local_envvar(list(NO_COLOR = "yes"), .local_envir = .local_envir) + app <- cli::start_app(output = "message", .auto_close = FALSE) + withr::defer(cli::stop_app(app), envir = .local_envir) +} + +#' Test if multiple vars in spec will result in warning message +#' @keywords internal +multiple_vars_in_spec_helper <- function(FUN) { + adsl <- minimal_table(30) + metadata <- minimal_metadata( + dataset = TRUE, + order = TRUE, + length = TRUE, + type = TRUE, + format = TRUE, + label = TRUE, + var_names = colnames(adsl) + ) + + metadata <- metadata %>% + mutate(dataset = "adtte") %>% + dplyr::bind_rows(metadata) %>% + dplyr::rename(Dataset = "dataset") + + withr::local_options(list(xportr.length_verbose = "message")) + # Setup temporary options with active verbose and Remove empty lines in cli theme + local_cli_theme() + + adsl %>% + FUN(metadata) %>% + testthat::expect_message("There are multiple specs for the same variable name") +} + +#' Test if multiple vars in spec with appropriate +#' @keywords internal +multiple_vars_in_spec_helper2 <- function(FUN) { + adsl <- minimal_table(30) + metadata <- minimal_metadata( + dataset = TRUE, + order = TRUE, + length = TRUE, + type = TRUE, + format = TRUE, + label = TRUE, + var_names = colnames(adsl) + ) + + metadata <- metadata %>% + mutate(dataset = "adtte") %>% + dplyr::bind_rows(metadata) %>% + dplyr::rename(Dataset = "dataset") + + withr::local_options(list(xportr.length_verbose = "message", xportr.domain_name = "Dataset")) + # Setup temporary options with active verbose and Remove empty lines in cli theme + local_cli_theme() + + adsl %>% + FUN(metadata) %>% + testthat::expect_no_message(message = "There are multiple specs for the same variable name") +} diff --git a/R/type.R b/R/type.R index b3c98740..0114309c 100644 --- a/R/type.R +++ b/R/type.R @@ -1,106 +1,181 @@ #' Coerce variable type #' -#' Current assumptions: -#' columns_meta is a data.frame with names "Variables", "Type" -#' -#' @param .df An R object with columns that can be coerced -#' @param metacore Either a data.frame that has the names of all possible columns -#' and their types, or a `Metacore` object from the `Metacore` package. Required -#' column names are dataset, variables, type -#' @param domain Name of the dataset. Ex ADAE/DM. This will be used to subset -#' the metacore object. If none is passed it is assumed to be the name of the -#' dataset passed in `.df`. -#' @param verbose The action the function takes when a variable isn't typed -#' properly. Options are 'stop', 'warn', 'message', and 'none' +#' XPT v5 datasets only have data types of character and numeric. `xportr_type` +#' attempts to collapse R classes to those two XPT types. The +#' 'xportr.character_types' option is used to explicitly collapse the class of a +#' column to character using `as.character`. Similarly, 'xportr.numeric_types' +#' will collapse a column to a numeric type. If no type is passed for a +#' variable and it isn't identifed as a timing variable, it is assumed to be numeric and coerced with `as.numeric`. +#' +#' Certain care should be taken when using timing variables. R serializes dates +#' based on a reference date of 01/01/1970 where XPT uses 01/01/1960. This can +#' result in dates being 10 years off when outputting from R to XPT if you're +#' using a date class. For this reason, `xportr` will try to determine what +#' should happen with variables that appear to be used to denote time. +#' +#' For variables that end in DT, DTM, or, TM, if they are not explicitly noted +#' in 'xportr.numeric_types' or 'xportr.character_types', they are coerced to +#' numeric results. +#' +#' @inheritParams xportr_length +#' +#' @section Messaging: `type_log()` is the primary messaging tool for +#' `xportr_type()`. The number of column types that mismatch the reported type +#' in the metadata, if any, is reported by by `xportr_type()`. If there are any +#' type mismatches, and the 'verbose' argument is 'stop', 'warn', or +#' 'message', each mismatch will be detailed with the actual type in the data +#' and the type noted in the metadata. +#' +#' @section Metadata: The argument passed in the 'metadata' argument can either +#' be a metacore object, or a data.frame containing the data listed below. If +#' metacore is used, no changes to options are required. +#' +#' For data.frame 'metadata' arguments four columns must be present: +#' +#' 1) Domain Name - passed as the 'xportr.domain_name' option. Default: +#' "dataset". This is the column subset by the 'domain' argument in the +#' function. +#' +#' 2) Format Name - passed as the 'xportr.format_name' option. Default: +#' "format". Character values to update the 'format.sas' attribute of the +#' column. This is passed to `haven::write` to note the format. +#' +#' 3) Variable Name - passed as the 'xportr.variable_name' option. Default: +#' "variable". This is used to match columns in '.df' argument and the +#' metadata. +#' +#' 4) Variable Type - passed as the 'xportr.type_name'. Default: "type". This +#' is used to note the XPT variable "type" options are numeric or character. +#' +#' 5) (Option only) Character Types - The list of classes that should be +#' explicitly coerced to a XPT Character type. Default: c( "character", +#' "char", "text", "date", "posixct", "posixt", "datetime", "time", +#' "partialdate", "partialtime", "partialdatetime", "incompletedatetime", +#' "durationdatetime", "intervaldatetime") +#' +#' 6) (Option only) Numeric Types - The list of classes that should be +#' explicitly coerced to a XPT numeric type. Default: c("integer", "numeric", +#' "num", "float") #' #' @return Returns the modified table. #' @export #' #' @examples -#' metacore <- data.frame( +#' metadata <- data.frame( #' dataset = "test", #' variable = c("Subj", "Param", "Val", "NotUsed"), -#' type = c("numeric", "character", "numeric", "character") +#' type = c("numeric", "character", "numeric", "character"), +#' format = NA #' ) #' #' .df <- data.frame( -#' Subj = as.character(123, 456, 789), -#' Different = c("a", "b", "c"), -#' Val = c("1", "2", "3"), -#' Param = c("param1", "param2", "param3") +#' Subj = as.character(123, 456, 789), +#' Different = c("a", "b", "c"), +#' Val = c("1", "2", "3"), +#' Param = c("param1", "param2", "param3") #' ) #' -#' df2 <- xportr_type(.df, metacore, "test") -xportr_type <- function(.df, metacore, domain = NULL, - verbose = getOption('xportr.type_verbose', 'none')){ - +#' df2 <- xportr_type(.df, metadata, "test") +xportr_type <- function(.df, + metadata = NULL, + domain = NULL, + verbose = getOption("xportr.type_verbose", "none"), + metacore = deprecated()) { + if (!missing(metacore)) { + lifecycle::deprecate_warn( + when = "0.3.0", + what = "xportr_type(metacore = )", + with = "xportr_type(metadata = )" + ) + metadata <- metacore + } # Name of the columns for working with metadata domain_name <- getOption("xportr.domain_name") variable_name <- getOption("xportr.variable_name") type_name <- getOption("xportr.type_name") - characterTypes <- getOption("xportr.character_types") - - if (!is.null(domain) && !is.character(domain)) { - abort(c("`domain` must be a vector with type .", - x = glue("Instead, it has type <{typeof(domain)}>.")) - ) - } - - df_arg <- as_name(enexpr(.df)) - - if (!is.null(attr(.df, "_xportr.df_arg_"))) df_arg <- attr(.df, "_xportr.df_arg_") - else if (identical(df_arg, ".")) { - attr(.df, "_xportr.df_arg_") <- get_pipe_call() - df_arg <- attr(.df, "_xportr.df_arg_") - } - - domain <- domain %||% df_arg - + characterTypes <- c(getOption("xportr.character_types"), "_character") + numericTypes <- c(getOption("xportr.numeric_types"), "_numeric") + format_name <- getOption("xportr.format_name") + + ## Common section to detect domain from argument or pipes + + df_arg <- tryCatch(as_name(enexpr(.df)), error = function(err) NULL) + domain <- get_domain(.df, df_arg, domain) if (!is.null(domain)) attr(.df, "_xportr.df_arg_") <- domain - + + ## End of common section + ## Pull out correct metadata - if ("Metacore" %in% class(metacore)) metacore <- metacore$var_spec - - if (domain_name %in% names(metacore)) { - metacore <- metacore %>% + metadata <- metadata %||% + attr(.df, "_xportr.df_metadata_") %||% + rlang::abort("Metadata must be set with `metadata` or `xportr_metadata()`") + + if (inherits(metadata, "Metacore")) { + metadata <- metadata$var_spec + } + + if (domain_name %in% names(metadata)) { + metadata <- metadata %>% filter(!!sym(domain_name) == domain) } - metacore <- metacore %>% - select(!!sym(variable_name), !!sym(type_name)) - + + metacore <- metadata %>% + select(!!sym(variable_name), !!sym(type_name), !!sym(format_name)) + + # Common check for multiple variables name + check_multiple_var_specs(metadata, variable_name) + # Current class of table variables table_cols_types <- map(.df, first_class) - + # Produces a data.frame with Variables, Type.x(Table), and Type.y(metadata) meta_ordered <- left_join( data.frame(variable = names(.df), type = unlist(table_cols_types)), - metacore, + metadata, by = "variable" - ) - + ) %>% + mutate( + # _character is used here as a mask of character, in case someone doesn't + # want 'character' coerced to character + type.x = if_else(type.x %in% characterTypes, "_character", type.x), + type.x = if_else(type.x %in% numericTypes | (grepl("DT$|DTM$|TM$", variable) & !is.na(format)), + "_numeric", + type.x + ), + type.y = if_else(is.na(type.y), type.x, type.y), + type.y = tolower(type.y), + type.y = if_else(type.y %in% characterTypes | (grepl("DTC$", variable) & is.na(format)), "_character", type.y), + type.y = if_else(type.y %in% numericTypes, "_numeric", type.y) + ) + # It is possible that a variable exists in the table that isn't in the metadata # it will be silently ignored here. This may happen depending on what a user # passes and the options they choose. The check_core function is the place # where this should be caught. type_mismatch_ind <- which(meta_ordered$type.x != meta_ordered$type.y) type_log(meta_ordered, type_mismatch_ind, verbose) - - + # Check if variable types match is_correct <- sapply(meta_ordered[["type.x"]] == meta_ordered[["type.y"]], isTRUE) # Use the original variable iff metadata is missing that variable correct_type <- ifelse(is.na(meta_ordered[["type.y"]]), meta_ordered[["type.x"]], meta_ordered[["type.y"]]) - + # Walk along the columns and coerce the variables. Modifying the columns # Directly instead of something like map_dfc to preserve any attributes. - walk2(correct_type, seq_along(correct_type), - function(x, i, is_correct) { - if (!is_correct[i]) { - if (correct_type[i] %in% characterTypes) - .df[[i]] <<- as.character(.df[[i]]) - else .df[[i]] <<- as.numeric(.df[[i]]) - } - }, is_correct) - + walk2( + correct_type, seq_along(correct_type), + function(x, i, is_correct) { + if (!is_correct[i]) { + orig_attributes <- attributes(.df[[i]]) + orig_attributes$class <- NULL + if (correct_type[i] %in% characterTypes) { + .df[[i]] <<- as.character(.df[[i]]) + } else { + .df[[i]] <<- as.numeric(.df[[i]]) + } + attributes(.df[[i]]) <<- orig_attributes + } + }, is_correct + ) .df } diff --git a/R/utils-xportr.R b/R/utils-xportr.R index 2c39698b..91f2cb15 100644 --- a/R/utils-xportr.R +++ b/R/utils-xportr.R @@ -8,10 +8,12 @@ extract_attr <- function(data, attr = c("label", "format.sas", "SAStype", "SASlength")) { attr <- match.arg(attr) out <- lapply(data, function(.x) attr(.x, attr)) - out <- vapply(out, - function(.x) ifelse(is.null(.x), "", .x), - character(1L), USE.NAMES = FALSE) - names(out) <- names(data) + out <- vapply(out, + function(.x) ifelse(is.null(.x), "", .x), + character(1L), + USE.NAMES = FALSE + ) + names(out) <- names(data) out } @@ -29,13 +31,13 @@ ntext <- function(n, msg1, msg2) { #' Assign Commas and Oxford Comma to a Series of Words in Text #' -#' @param x Character Vector, usually a series of column names +#' @param x Character Vector, usually a series of column names #' -#' @return String of text where words are separated by commas and final +#' @return String of text where words are separated by commas and final #' oxford comma ", and" convention #' @noRd fmt_comma <- function(x) { - glue_collapse(x, sep = ", ", last = if (length(x) <= 2) " and " else ", and ") + glue_collapse(x, sep = ", ", last = if (length(x) <= 2) " and " else ", and ") } #' Encode String of Variables in Tick Marks @@ -49,8 +51,8 @@ encode_vars <- function(x) { if (is.character(x)) { x <- encodeString(x, quote = "`") } - - fmt_comma(x) + + fmt_comma(x) } #' Encode String of Values in Quotation Marks @@ -64,7 +66,7 @@ encode_vals <- function(x) { if (is.character(x)) { x <- encodeString(x, quote = "'") } - + fmt_comma(x) } @@ -104,63 +106,69 @@ fmt_fmts <- function(x) { #' Check Variable Names Before Exporting to xpt #' -#' @param varnames Column names of data -#' +#' @param varnames Column names of data +#' #' @param list_vars_first Logical value to toggle where to list out column names -#' in error message -#' +#' in error message +#' #' @param err_cnd Character vector to initialize message -#' +#' #' @details Prior to exporting xpt file, check that column names meet appropriate -#' conditions like character limits, capitalization, and other naming conventions. -#' +#' conditions like character limits, capitalization, and other naming conventions. +#' #' @return An error message if incompatible variable names were used. #' @noRd xpt_validate_var_names <- function(varnames, list_vars_first = TRUE, err_cnd = character()) { - # 1.1 Check length -- chk_varlen <- varnames[nchar(varnames) > 8] - + if (length(chk_varlen) > 0) { err_cnd <- c(err_cnd, ifelse(list_vars_first, - glue("{fmt_vars(chk_varlen)} must be 8 characters or less."), - glue(" - Must be 8 characters or less: {fmt_vars(chk_varlen)}."))) + glue("{fmt_vars(chk_varlen)} must be 8 characters or less."), + glue(" + Must be 8 characters or less: {fmt_vars(chk_varlen)}.") + )) } - + # 1.2 Check first character -- - chk_first_chr <- varnames[stringr::str_detect(stringr::str_sub(varnames, 1, 1), - "[^[:alpha:]]")] - + chk_first_chr <- varnames[stringr::str_detect( + stringr::str_sub(varnames, 1, 1), + "[^[:alpha:]]" + )] + if (length(chk_first_chr) > 0) { err_cnd <- c(err_cnd, ifelse(list_vars_first, - glue("{fmt_vars(chk_first_chr)} must start with a letter."), - glue(" - Must start with a letter: {fmt_vars(chk_first_chr)}."))) + glue("{fmt_vars(chk_first_chr)} must start with a letter."), + glue(" + Must start with a letter: {fmt_vars(chk_first_chr)}.") + )) } - + # 1.3 Check Non-ASCII and underscore characters -- chk_alnum <- varnames[stringr::str_detect(varnames, "[^a-zA-Z0-9]")] - + if (length(chk_alnum) > 0) { err_cnd <- c(err_cnd, ifelse(list_vars_first, - glue("{fmt_vars(chk_alnum)} cannot contain any non-ASCII, symbol or underscore characters."), - glue(" - Cannot contain any non-ASCII, symbol or underscore characters: {fmt_vars(chk_alnum)}."))) + glue("{fmt_vars(chk_alnum)} cannot contain any non-ASCII, symbol or underscore characters."), + glue(" + Cannot contain any non-ASCII, symbol or underscore characters: {fmt_vars(chk_alnum)}.") + )) } - + # 1.4 Check for any lowercase letters - or not all uppercase chk_lower <- varnames[!stringr::str_detect( - stringr::str_replace_all(varnames, "[:digit:]", ""), - "^[[:upper:]]+$")] - + stringr::str_replace_all(varnames, "[:digit:]", ""), + "^[[:upper:]]+$" + )] + if (length(chk_lower) > 0) { err_cnd <- c(err_cnd, ifelse(list_vars_first, - glue("{fmt_vars(chk_lower)} cannot contain any lowercase characters."), - glue(" - Cannot contain any lowercase characters {fmt_vars(chk_lower)}."))) + glue("{fmt_vars(chk_lower)} cannot contain any lowercase characters."), + glue(" + Cannot contain any lowercase characters {fmt_vars(chk_lower)}.") + )) } return(err_cnd) } @@ -172,81 +180,153 @@ xpt_validate_var_names <- function(varnames, #' @return xpt file #' @noRd xpt_validate <- function(data) { - err_cnd <- character() - + # 1.0 VARIABLES ---- varnames <- names(data) err_cnd <- xpt_validate_var_names(varnames = varnames, err_cnd = err_cnd) - - + + # 2.0 LABELS ---- labels <- extract_attr(data, attr = "label") - + # 2.1 Check length -- chk_label_len <- labels[nchar(labels) > 40] - + if (length(chk_label_len) > 0) { - err_cnd <- c(err_cnd, - glue("{fmt_labs(chk_label_len)} must be 40 characters or less.")) + err_cnd <- c( + err_cnd, + glue("{fmt_labs(chk_label_len)} must be 40 characters or less.") + ) } - + # 2.2 Check Non-ASCII and special characters - chk_spl_chr <- labels[stringr::str_detect(labels, "[<>]|[^[:ascii:]]")] - + chk_spl_chr <- labels[stringr::str_detect(labels, "[^[:ascii:]]")] + if (length(chk_spl_chr) > 0) { - err_cnd <- c(err_cnd, - glue("{fmt_labs(chk_spl_chr)} cannot contain any non-ASCII, symbol or special characters.")) + err_cnd <- c( + err_cnd, + glue("{fmt_labs(chk_spl_chr)} cannot contain any non-ASCII, symbol or special characters.") + ) } - + # 3.0 VARIABLE TYPES ---- types <- tolower(extract_attr(data, attr = "SAStype")) - expected_types <- c('', 'text', 'integer', 'float', 'datetime', 'date', 'time', - 'partialdate', 'partialtime', 'partialdatetime', - 'incompletedatetime', 'durationdatetime', 'intervaldatetime') - + + expected_types <- c( + "", "text", "integer", "float", "datetime", "date", "time", + "partialdate", "partialtime", "partialdatetime", + "incompletedatetime", "durationdatetime", "intervaldatetime" + ) + # 3.1 Invalid types -- chk_types <- types[which(!types %in% expected_types)] - + if (length(chk_types) > 0) { - err_cnd <- c(err_cnd, - glue("{fmt_vars(names(types))} must have a valid type.")) + err_cnd <- c( + err_cnd, + glue("{fmt_vars(names(types))} must have a valid type.") + ) } - + # 4.0 Format Types ---- - formats <- tolower(extract_attr(data, attr = "format.sas")) - + formats <- extract_attr(data, attr = "format.sas") + ## The usual expected formats in clinical trials: characters, dates - expected_formats <- c(NA, - '', - paste("$", 1:200, ".", sep = ""), - paste("date", 5:11, ".", sep = ""), - paste("time", 2:20, ".", sep = ""), - paste("datetime", 7:40, ".", sep = ""), - paste("yymmdd", 2:10, ".", sep = ""), - paste("mmddyy", 2:10, ".", sep = ""), - paste("ddmmyy", 2:10, ".", sep = "")) - - chk_formats <- formats[which(!formats %in% expected_formats)] - + # Formats: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/leforinforref/n0zwce550r32van1fdd5yoixrk4d.htm + expected_formats <- c( + NA, + "", + paste("$", 1:200, ".", sep = ""), + paste("date", 5:11, ".", sep = ""), + paste("time", 2:20, ".", sep = ""), + paste("datetime", 7:40, ".", sep = ""), + paste("yymmdd", 2:10, ".", sep = ""), + paste("mmddyy", 2:10, ".", sep = ""), + paste("ddmmyy", 2:10, ".", sep = ""), + "E8601DA.", + "E8601DA10.", + "E8601DN.", + "E8601DN10.", + "E8601TM.", + paste0("E8601TM", 8:15, "."), + paste0("E8601TM", 8:15, ".", 0:6), + "E8601TZ.", + paste("E8601TZ", 9:20, "."), + paste("E8601TZ", 9:20, ".", 0:6), + "E8601TX.", + paste0("E8601TX", 9:20, "."), + "E8601DT.", + paste0("E8601DT", 16:26, "."), + paste0("E8601DT", 16:26, ".", 0:6), + "E8601LX.", + paste0("E8601LX", 20:35, "."), + "E8601LZ.", + paste0("E8601LZ", 9:20, "."), + "E8601DX.", + paste0("E8601DX", 20:35, "."), + "B8601DT.", + paste0("B8601DT", 15:26, "."), + paste0("B8601DT", 15:26, ".", 0:6), + "IS8601DA.", + "B8601DA.", + paste0("B8601DA", 8:10, "."), + "weekdate.", + paste0("weekdate", 3:37, "."), + "mmddyy.", + "ddmmyy.", + "yymmdd.", + "date.", + "time.", + "hhmm.", + "IS8601TM.", + "E8601TM.", + "B8601TM." + ) + format_regex <- "^([1-9]|[12][0-9]|3[0-2])\\.$|^([1-9]|[12][0-9]|3[0-2])\\.([1-9]|[12][0-9]|3[0-1])$" + + + # 3.1 Invalid types + is_valid <- toupper(formats) %in% toupper(expected_formats) | + purrr::map_lgl(formats, stringr::str_detect, format_regex) + + chk_formats <- formats[!is_valid] ## Remove the correctly numerically formatted variables - chk_formats <- chk_formats[which(!str_detect(chk_formats, - "^([1-9]|[12][0-9]|3[0-2])\\.$|^([1-9]|[12][0-9]|3[0-2])\\.([1-9]|[12][0-9]|3[0-1])$"))] - if(length(chk_formats) > 0) { - err_cnd <- c(err_cnd, - glue("{fmt_fmts(names(chk_formats))} must have a valid format.")) + if (length(chk_formats) > 0) { + err_cnd <- c( + err_cnd, + glue("{fmt_fmts(names(chk_formats))} must have a valid format.") + ) } return(err_cnd) } +#' Get the domain from argument or from magrittr's pipe (`%>%`) +#' +#' @return A string representing the domain +#' @noRd +get_domain <- function(.df, df_arg, domain) { + if (!is.null(domain) && !is.character(domain)) { + abort(c("`domain` must be a vector with type .", + x = glue("Instead, it has type <{typeof(domain)}>.") + )) + } + + if (identical(df_arg, ".")) { + df_arg <- get_pipe_call() + } + result <- domain %||% attr(.df, "_xportr.df_arg_") %||% df_arg + result +} + #' Get Origin Object of a Series of Pipes #' #' @return The R Object at the top of a pipe stack #' @noRd get_pipe_call <- function() { - call_strs <- map_chr(sys.calls(), as_label) - top_call <- min(which(str_detect(call_strs, "%>%"))) - call_str <- as_label(sys.calls()[[top_call]]) + call_strs <- map(as.list(sys.calls()), ~ deparse1(.x, nlines = 1)) + top_call <- max(which(str_detect(call_strs, "%>%"))) + call_str <- call_strs[[top_call]] trimws(strsplit(call_str, "%>%", fixed = TRUE)[[1]][[1]]) } @@ -259,6 +339,35 @@ get_pipe_call <- function() { first_class <- function(x) { characterTypes <- getOption("xportr.character_types") class_ <- tolower(class(x)[1]) - if (class_ %in% characterTypes) "character" - else class_ + if (class_ %in% characterTypes) { + "character" + } else { + class_ + } +} + +#' Check for multiple var name specs +#' +#' Detects cases where the domain name is not correctly defined and the full +#' specification is used. +#' This can lead to multiple warnings for the same variable. For instance, in +#' the FDA pilot 3 submission the column has variable name with uppercase +#' `Variable`, where the defaults for xportr is for lowercase `variable`. +#' +#' @param metadata A data frame containing variable level metadata. +#' @param variable_name string with `getOption('xportr.variable_name')` +#' @noRd +check_multiple_var_specs <- function(metadata, + variable_name = getOption("xportr.variable_name")) { + variable_len <- pluck(metadata, variable_name) %||% c() + if (NROW(variable_len) != NROW(unique(variable_len))) { + cli_alert_info( + glue( + .sep = " ", + "There are multiple specs for the same variable name.", + "Check the metadata and variable name option", + "`getOption('xportr.variable_name')`" + ) + ) + } } diff --git a/R/write.R b/R/write.R index b60d2c49..57367fc2 100644 --- a/R/write.R +++ b/R/write.R @@ -7,22 +7,40 @@ #' @param .df A data frame to write. #' @param path Path where transport file will be written. File name sans will be #' used as `xpt` name. -#' @param label Dataset label. It must be<=40 characters. +#' @param label Dataset label. It must be <=40 characters. +#' @param strict_checks If TRUE, xpt validation will report errors and not write +#' out the dataset. If FALSE, xpt validation will report warnings and continue +#' with writing out the dataset. Defaults to FALSE +#' #' @details #' * Variable and dataset labels are stored in the "label" attribute. -#' +#' #' * SAS length are stored in the "SASlength" attribute. -#' +#' #' * SAS format are stored in the "SASformat" attribute. -#' +#' #' * SAS type are stored in the "SAStype" attribute. #' #' @return A data frame. `xportr_write()` returns the input data invisibly. #' @export -xportr_write <- function(.df, path, label = NULL) { - +#' +#' @examples +#' adsl <- data.frame( +#' Subj = as.character(123, 456, 789), +#' Different = c("a", "b", "c"), +#' Val = c("1", "2", "3"), +#' Param = c("param1", "param2", "param3") +#' ) +#' +#' xportr_write(adsl, +#' path = paste0(tempdir(), "/adsl.xpt"), +#' label = "Subject-Level Analysis", +#' strict_checks = FALSE +#' ) +#' +xportr_write <- function(.df, path, label = NULL, strict_checks = FALSE) { path <- normalizePath(path, mustWork = FALSE) - + name <- tools::file_path_sans_ext(basename(path)) if (nchar(name) > 8) { @@ -34,25 +52,40 @@ xportr_write <- function(.df, path, label = NULL) { } if (!is.null(label)) { - - if (nchar(label) > 40) + if (nchar(label) > 40) { abort("`label` must be 40 characters or less.") + } - if (stringr::str_detect(label, "[<>]|[^[:ascii:]]")) + if (stringr::str_detect(label, "[^[:ascii:]]")) { abort("`label` cannot contain any non-ASCII, symbol or special characters.") + } attr(.df, "label") <- label } - + checks <- xpt_validate(.df) if (length(checks) > 0) { - abort(c("The following validation failed:", checks)) + if (!strict_checks) { + warn(c("The following validation checks failed:", checks)) + } else { + abort(c("The following validation checks failed:", checks)) + } } data <- as.data.frame(.df) - write_xpt(data, path = path, version = 5, name = name) + tryCatch( + write_xpt(data, path = path, version = 5, name = name), + error = function(err) { + rlang::abort( + paste0( + "Error reported by haven::write_xpt, error was: \n", + err + ) + ) + } + ) invisible(data) } diff --git a/R/xportr-package.R b/R/xportr-package.R index 6dae369c..c3804b20 100644 --- a/R/xportr-package.R +++ b/R/xportr-package.R @@ -1,34 +1,128 @@ #' The `xportr` package #' -#' Package Info here +#' `xportr` is designed to be a clinical workflow friendly method for outputting +#' CDISC complaint data sets in R, to XPT version 5 files. It was designed with +#' options in mind to allow for flexible setting of options while allowing +#' projects and system administrators to set sensible defaults for their +#' orginziations workflows. Below are a list of options that can be set to +#' customize how `xportr` works in your environment. +#' +#' @section xportr options: +#' +#' \itemize{ +#' \item{ +#' xportr.df_domain_name - The name of the domain "name" column in dataset +#' metadata. Default: "dataset" +#' } +#' \item { +#' xportr.df_label - The column noting the dataset label in dataset metadata. +#' Default: "label" +#' } +#' \item{ +#' xportr.domain_name - The name of the domain "name" column in variable +#' metadata. Default: "dataset" +#' } +#' \item{ +#' xportr.variable_name - The name of the variable "name" in variable +#' metadata. Default: "variable" +#' } +#' \item{ +#' xportr.type_name - The name of the variable type column in variable +#' metadata. Default: "type" +#' } +#' \item{ +#' xportr.label - The name of the variable label column in variable metadata. +#' Default: "label" +#' } +#' \item{ +#' xportr.length - The name of the variable length column in variable +#' metadata. Default: "length" +#' } +#' \item{ +#' xportr.format_name - The name of the variable format column in variable +#' metadata. Default: "format" +#' } +#' \item{ +#' xportr.order_name - The name of the variable order column in variable +#' metadata. Default: "order" +#' } +#' \item{ +#' xportr.format_verbose - The default argument for the 'verbose' argument for +#' `xportr_format`. Default: "none" +#' } +#' \item{ +#' xportr.label_verbose - The default argument for the 'verbose' argument for +#' `xportr_label`. Default: "none" +#' } +#' \item{ +#' xportr.length_verbose - The default argument for the 'verbose' argument for +#' `xportr_length`. Default: "none" +#' } +#' \item{ +#' xportr.type_verbose - The default argument for the 'verbose' argument for +#' `xportr_type`. Default: "none" +#' } +#' \item{ +#' xportr.character_types - The default character vector used to explicitly +#' coerce R classes to character XPT types. Default: c("character", "char", +#' "text", "date", "posixct", "posixt", "datetime", "time", "partialdate", +#' "partialtime", "partialdatetime", "incompletedatetime", "durationdatetime", +#' "intervaldatetime") +#' } +#' \item{ +#' xportr.numeric_types - The default character vector used to explicitly +#' coerce R classes to numeric XPT types. Default: c("integer", "numeric", +#' "num", "float") +#' } +#' } +#' +#' @section Updating Options: +#' \itemize{ +#' \item{For a single session, an option can be changed by +#' `option( = )`} +#' \item{To change an option for a single projects across sessions in that +#' projects, place the options update in the `.Rprofile` in that project +#' directory.} +#' \item{To change an option for a user across all sessions, place the options +#' update in the `.Rprofile` file in the users home directory.} +#' \item{To change an option for all users in an R environment, place the +#' options update in the `.Rprofile.site` file in the R home directory.} +#' } +#' +#' See [Managing R with .Rprofile, .Renviron, Rprofile.site, Renviron.site, rsession.conf, and repos.conf](https://support.posit.co/hc/en-us/articles/360047157094-Managing-R-with-Rprofile-Renviron-Rprofile-site-Renviron-site-rsession-conf-and-repos-conf) # nolint +#' #' #' @keywords internal #' #' @import rlang haven -#' @importFrom purrr map_chr walk2 map map_dbl #' @importFrom dplyr left_join bind_cols filter select rename rename_with n #' everything arrange group_by summarize mutate ungroup case_when distinct +#' tribble if_else #' @importFrom glue glue glue_collapse -#' @importFrom cli cli_alert_info cli_h2 cli_alert_success cli_alert_info -#' cli_div cli_alert_success cli_text cli_h2 +#' @importFrom cli cli_alert_info cli_h2 cli_alert_success cli_div cli_text +#' cli_alert_danger #' @importFrom tidyselect all_of any_of #' @importFrom utils capture.output str tail packageVersion #' @importFrom stringr str_detect str_extract str_replace str_replace_all #' @importFrom readr parse_number -#' @importFrom purrr map_chr map2_chr +#' @importFrom purrr map_chr map2_chr walk walk2 map map_dbl pluck #' @importFrom janitor make_clean_names #' @importFrom tm stemDocument #' @importFrom graphics stem #' @importFrom magrittr %>% extract2 -#' +#' "_PACKAGE" -globalVariables(c("abbr_parsed", "abbr_stem", "adj_orig", "adj_parsed", "col_pos", "dict_varname", - "lower_original_varname", "my_minlength", "num_st_ind", "original_varname", - "renamed_n", "renamed_var", "use_bundle", "viable_start")) +globalVariables(c( + "abbr_parsed", "abbr_stem", "adj_orig", "adj_parsed", "col_pos", "dict_varname", + "lower_original_varname", "my_minlength", "num_st_ind", "original_varname", + "renamed_n", "renamed_var", "use_bundle", "viable_start", "type.x", "type.y", + "variable" +)) # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start +#' @importFrom lifecycle deprecated ## usethis namespace: end NULL diff --git a/R/zzz.R b/R/zzz.R index 35eca269..88b877b5 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -3,7 +3,6 @@ op.devtools <- list( xportr.df_domain_name = "dataset", xportr.df_label = "label", - xportr.coerse = "none", xportr.domain_name = "dataset", xportr.variable_name = "variable", xportr.type_name = "type", @@ -14,11 +13,18 @@ xportr.label_verbose = "none", xportr.length_verbose = "none", xportr.type_verbose = "none", - xportr.character_types = c("character", "char", "text", "date", "posixct", "posixt"), + xportr.character_types = c( + "character", "char", "text", "date", "posixct", + "posixt", "datetime", "time", "partialdate", + "partialtime", "partialdatetime", + "incompletedatetime", "durationdatetime", + "intervaldatetime" + ), + xportr.numeric_types = c("integer", "numeric", "num", "float"), xportr.order_name = "order" ) toset <- !(names(op.devtools) %in% names(op)) if (any(toset)) options(op.devtools[toset]) - + invisible() } diff --git a/README.Rmd b/README.Rmd index da78786c..d7d92e30 100644 --- a/README.Rmd +++ b/README.Rmd @@ -82,7 +82,7 @@ data sets (โ‰ค 200) - Coerces variables to only numeric or character types - Display format support for numeric float and date/time values - Variables names are โ‰ค 8 characters. -- Variable labels are โ‰ค 200 characters. +- Variable labels are โ‰ค 40 characters. - Data set labels are โ‰ค 40 characters. - Presence of non-ASCII characters in Variable Names, Labels or data set labels. @@ -103,7 +103,7 @@ To do this we will need to do the following: - Apply a dataset label - Write out a version 5 xpt file -All of which can be done using a well-defined specification file and the `xportr` package! +All of which can be done using a well-defined specification file and the `{xportr}` package! First we will start with our `ADSL` dataset created in R. This example `ADSL` dataset is taken from the [`{admiral}`](https://pharmaverse.github.io/admiral/index.html) package. The script that generates this `ADSL` dataset can be created by using this command `admiral::use_ad_template("adsl")`. This `ADSL` dataset has 306 observations and 48 variables. @@ -123,18 +123,30 @@ spec_path <- system.file(paste0("specs/", "ADaM_admiral_spec.xlsx"), package = " var_spec <- readxl::read_xlsx(spec_path, sheet = "Variables") %>% dplyr::rename(type = "Data Type") %>% rlang::set_names(tolower) +``` + +Each `xportr_` function has been written in a way to take in a part of the specification file and apply that piece to the dataset. Setting `verbose = "warn"` will send appropriate warning message to the console. We have suppressed the warning for the sake of brevity. +```{r, warning = FALSE, message=FALSE, eval=TRUE} +adsl %>% + xportr_type(var_spec, "ADSL", verbose = "warn") %>% + xportr_length(var_spec, "ADSL", verbose = "warn") %>% + xportr_label(var_spec, "ADSL", verbose = "warn") %>% + xportr_order(var_spec, "ADSL", verbose = "warn") %>% + xportr_format(var_spec, "ADSL") %>% + xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset") ``` -Each `xportr_` function has been written in a way to take in a part of the specification file and apply that piece to the dataset. +The `xportr_metadata()` function can reduce duplication by setting the variable specification and domain explicitly at the top of a pipeline. If you would like to use the `verbose` argument, you will need to set in each function call. ```{r, message=FALSE, eval=FALSE} -adsl %>% - xportr_type(var_spec, "ADSL") %>% - xportr_length(var_spec, "ADSL") %>% - xportr_label(var_spec, "ADSL") %>% - xportr_order(var_spec, "ADSL") %>% - xportr_format(var_spec, "ADSL") %>% +adsl %>% + xportr_metadata(var_spec, "ADSL") %>% + xportr_type() %>% + xportr_length() %>% + xportr_label() %>% + xportr_order() %>% + xportr_format() %>% xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset") ``` @@ -146,4 +158,4 @@ We are in talks with other Pharma companies involved with the [`{pharmaverse}`](
-This package was developed jointly by [GSK](https://us.gsk.com/en-us/home/) and [Atorus](https://www.atorusresearch.com/). \ No newline at end of file +This package was developed jointly by [GSK](https://us.gsk.com/en-us/home/) and [Atorus](https://www.atorusresearch.com/). diff --git a/README.md b/README.md index e4106681..03e9cf00 100644 --- a/README.md +++ b/README.md @@ -67,19 +67,19 @@ to any validators or data reviewers.
-- Variable names must start with a letter (not an underscore), be - comprised of only uppercase letters (A-Z), numerals (0-9) and be - free of non-ASCII characters, symbols, and underscores. -- Allotted length for each column containing character (text) data - should be set to the maximum length of the variable used across all - data sets (โ‰ค 200) -- Coerces variables to only numeric or character types -- Display format support for numeric float and date/time values -- Variables names are โ‰ค 8 characters. -- Variable labels are โ‰ค 200 characters. -- Data set labels are โ‰ค 40 characters. -- Presence of non-ASCII characters in Variable Names, Labels or data - set labels. +- Variable names must start with a letter (not an underscore), be + comprised of only uppercase letters (A-Z), numerals (0-9) and be free + of non-ASCII characters, symbols, and underscores. +- Allotted length for each column containing character (text) data + should be set to the maximum length of the variable used across all + data sets (โ‰ค 200) +- Coerces variables to only numeric or character types +- Display format support for numeric float and date/time values +- Variables names are โ‰ค 8 characters. +- Variable labels are โ‰ค 40 characters. +- Data set labels are โ‰ค 40 characters. +- Presence of non-ASCII characters in Variable Names, Labels or data set + labels. **NOTE:** Each check has associated messages and warning. @@ -90,16 +90,16 @@ developed using R. To do this we will need to do the following: -- Apply types -- Apply lengths -- Apply variable labels -- Apply formats -- Re-order the variables -- Apply a dataset label -- Write out a version 5 xpt file +- Apply types +- Apply lengths +- Apply variable labels +- Apply formats +- Re-order the variables +- Apply a dataset label +- Write out a version 5 xpt file All of which can be done using a well-defined specification file and the -`xportr` package! +`{xportr}` package! First we will start with our `ADSL` dataset created in R. This example `ADSL` dataset is taken from the @@ -131,15 +131,33 @@ var_spec <- readxl::read_xlsx(spec_path, sheet = "Variables") %>% ``` Each `xportr_` function has been written in a way to take in a part of -the specification file and apply that piece to the dataset. +the specification file and apply that piece to the dataset. Setting +`verbose = "warn"` will send appropriate warning message to the console. +We have suppressed the warning for the sake of brevity. ``` r -adsl %>% - xportr_type(var_spec, "ADSL") %>% - xportr_length(var_spec, "ADSL") %>% - xportr_label(var_spec, "ADSL") %>% - xportr_order(var_spec, "ADSL") %>% - xportr_format(var_spec, "ADSL") %>% +adsl %>% + xportr_type(var_spec, "ADSL", verbose = "warn") %>% + xportr_length(var_spec, "ADSL", verbose = "warn") %>% + xportr_label(var_spec, "ADSL", verbose = "warn") %>% + xportr_order(var_spec, "ADSL", verbose = "warn") %>% + xportr_format(var_spec, "ADSL", verbose = "warn") %>% + xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset") +``` + +The `xportr_metadata()` function can reduce duplication by setting the +variable specification and domain explicitly at the top of a pipeline. +If you would like to use the `verbose` argument, you will need to set in +each function call. + +``` r +adsl %>% + xportr_metadata(var_spec, "ADSL") %>% + xportr_type() %>% + xportr_length() %>% + xportr_label() %>% + xportr_order() %>% + xportr_format() %>% xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset") ``` diff --git a/_pkgdown.yml b/_pkgdown.yml index f679d7a1..6c035b09 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -1,35 +1,52 @@ -destination: docs +url: https://atorus-research.github.io/xportr template: + bootstrap: 5 params: - bootswatch: yeti - opengraph: - image: - src: man/figures/xportr_rev.png - alt: "xportr Hex Sticker" + bootswatch: sandstone +search: + exclude: ["news/index.html"] +news: + cran_dates: true + +navbar: + structure: + right: [slack, github] + components: + slack: + icon: fa-slack + href: https://pharmaverse.slack.com/archives/C030EB2M4GM + aria-label: slack reference: -- title: The six core xportr functions -- contents: - - xportr_type - - xportr_length - - xportr_label - - xportr_write - - xportr_format - - xportr_order - -- title: xportr helper functions -- contents: - - label_log - - length_log - - type_log - - var_names_log - - var_ord_msg - - xportr_logger - - xportr_df_label + - title: Core xportr functions + - contents: + - xportr_type + - xportr_length + - xportr_label + - xportr_write + - xportr_format + - xportr_order + - xportr_df_label + - xportr_metadata + + - title: xportr helper functions + desc: Utility functions called within core xportr functions + - contents: + - label_log + - length_log + - type_log + - var_names_log + - var_ord_msg + - xportr_logger -- title: xportr - navbar: ~ - contents: - - xportr + - title: xportr example datasets and specification files + - contents: + - adsl + - var_spec +articles: + - title: ~ + navbar: ~ + contents: + - deepdive diff --git a/azure_pipeline.yaml b/azure_pipeline.yaml deleted file mode 100644 index 8488a471..00000000 --- a/azure_pipeline.yaml +++ /dev/null @@ -1,15 +0,0 @@ -trigger: none - -pool: - vmImage: 'ubuntu-latest' - -container: 'rocker/tidyverse:latest' - -steps: - -- script: sudo Rscript -e 'devtools::check(cran = FALSE)' - displayName: 'Package Check' - continueOnError: true - -- script: Rscript -e 'sessionInfo()' - displayName: 'R Version' diff --git a/cran-comments.md b/cran-comments.md index 55ff521f..72663610 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,22 +1,17 @@ -## xportr 0.1.0 Submission 2 - -Per comments from Gregor Seyer, the optoins setting in the xportr.Rmd was removed. A grep was also run to check for other instances. - +## xportr 0.3.0 Check Results: - No Errors or warnings + 1 NOTE ### Notes: - - New Submission - - Possibly misspelled words in DESCRIPTION. - -All words in description are common accronyms in industry: +Found the following (possibly) invalid URLs: + URL: https://support.posit.co/hc/en-us/articles/360047157094-Managing-R-with-Rprofile-Renviron-Rprofile-site-Renviron-site-rsession-conf-and-repos-conf + From: man/xportr-package.Rd + Status: 403 + Message: Forbidden - - ADaM - Analysis Dataset Model - - CDISC - Clinical Data Interchange Standards Consortium - - SDTM - Standard Data Tabulation Model - - XPT - SAS Transport File +This is a valid URL that is failing due to the website not allowing the site to be scraped by pipelines/robots. ### Tested on: diff --git a/data/adsl.rda b/data/adsl.rda new file mode 100644 index 00000000..21ae127f Binary files /dev/null and b/data/adsl.rda differ diff --git a/data/var_spec.rda b/data/var_spec.rda new file mode 100644 index 00000000..759f9a1f Binary files /dev/null and b/data/var_spec.rda differ diff --git a/docs/404.html b/docs/404.html deleted file mode 100644 index 398e0a4f..00000000 --- a/docs/404.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - -Page not found (404) โ€ข xportr - - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - -Content not found. Please use links in the navbar. - -
- - - -
- - - -
- -
-

-

Site built with pkgdown 2.0.3.

-
- -
-
- - - - - - - - diff --git a/docs/LICENSE-text.html b/docs/LICENSE-text.html deleted file mode 100644 index 771e868f..00000000 --- a/docs/LICENSE-text.html +++ /dev/null @@ -1,82 +0,0 @@ - -License โ€ข xportr - - -
-
- - - -
-
- - -
YEAR: 2021
-COPYRIGHT HOLDER: Atorus/GSK JPT
-
- -
- - - -
- - - -
- -
-

Site built with pkgdown 2.0.3.

-
- -
- - - - - - - - diff --git a/docs/LICENSE.html b/docs/LICENSE.html deleted file mode 100644 index 11098502..00000000 --- a/docs/LICENSE.html +++ /dev/null @@ -1,86 +0,0 @@ - -MIT License โ€ข xportr - - -
-
- - - -
-
- - -
- -

Copyright (c) 2021 Atorus/GSK JPT

-

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the โ€œSoftwareโ€), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

-

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

-

THE SOFTWARE IS PROVIDED โ€œAS ISโ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

-
- -
- - - -
- - - -
- -
-

Site built with pkgdown 2.0.3.

-
- -
- - - - - - - - diff --git a/docs/apple-touch-icon-120x120.png b/docs/apple-touch-icon-120x120.png deleted file mode 100644 index 3bbb7cde..00000000 Binary files a/docs/apple-touch-icon-120x120.png and /dev/null differ diff --git a/docs/apple-touch-icon-152x152.png b/docs/apple-touch-icon-152x152.png deleted file mode 100644 index f0807d53..00000000 Binary files a/docs/apple-touch-icon-152x152.png and /dev/null differ diff --git a/docs/apple-touch-icon-180x180.png b/docs/apple-touch-icon-180x180.png deleted file mode 100644 index 516fed45..00000000 Binary files a/docs/apple-touch-icon-180x180.png and /dev/null differ diff --git a/docs/apple-touch-icon-60x60.png b/docs/apple-touch-icon-60x60.png deleted file mode 100644 index 4fea33cc..00000000 Binary files a/docs/apple-touch-icon-60x60.png and /dev/null differ diff --git a/docs/apple-touch-icon-76x76.png b/docs/apple-touch-icon-76x76.png deleted file mode 100644 index 2384749d..00000000 Binary files a/docs/apple-touch-icon-76x76.png and /dev/null differ diff --git a/docs/apple-touch-icon.png b/docs/apple-touch-icon.png deleted file mode 100644 index b37e8b68..00000000 Binary files a/docs/apple-touch-icon.png and /dev/null differ diff --git a/docs/articles/Vignette.html b/docs/articles/Vignette.html deleted file mode 100644 index b3e856bb..00000000 --- a/docs/articles/Vignette.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - - -Variables' Core Categories โ€ข xportr - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - - - -

Three categories are specified in the โ€œCoreโ€ column in the SDTM domain models:

-
    -
  • A Required variable is any variable that is basic to the identification of a data record (i.e., essential key variables and a topic variable) or is necessary to make the record meaningful. Required variables must always be included in the dataset and cannot be null for any record.

  • -
  • An Expected variable is any variable necessary to make a record useful in the context of a specific domain. Expected variables may contain some null values, but in most cases will not contain null values for every record. When the study does not include the data item for an expected variable, however, a null column must still be included in the dataset, and a comment must be included in the Define-XML document to state that the study does not include the data item.

  • -
  • -

    A Permissible variable should be used in an SDTM dataset wherever appropriate. Although domain specification tables list only some of the Identifier, Timing, and general observation class variables listed in the SDTM, all are permissible unless specifically restricted in this implementation guide (see Section 2.7, SDTM Variables Not Allowed in SDTMIG) or by specific domain assumptions.

    -
      -
    • Domain assumptions that say a Permissible variable is โ€œgenerally not usedโ€ do not prohibit use of the variable.
    • -
    • If a study includes a data item that would be represented in a Permissible variable, then that variable must be included in the SDTM dataset, even if null. Indicate no data were available for that variable in the Define-XML document.
    • -
    • If a study did not include a data item that would be represented in a Permissible variable, then that variable should not be included in the SDTM dataset and should not be declared in the Define-XML document.
    • -
    -
  • -
-

Conditionally required variables in ADaM (Cond) - must be included in the dataset in certain circumstances.

-
- - - -
- - - -
- -
-

Site built with pkgdown 1.6.1.

-
- -
-
- - - - - - diff --git a/docs/articles/Vignette_files/accessible-code-block-0.0.1/empty-anchor.js b/docs/articles/Vignette_files/accessible-code-block-0.0.1/empty-anchor.js deleted file mode 100644 index ca349fd6..00000000 --- a/docs/articles/Vignette_files/accessible-code-block-0.0.1/empty-anchor.js +++ /dev/null @@ -1,15 +0,0 @@ -// Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> -// v0.0.1 -// Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. - -document.addEventListener('DOMContentLoaded', function() { - const codeList = document.getElementsByClassName("sourceCode"); - for (var i = 0; i < codeList.length; i++) { - var linkList = codeList[i].getElementsByTagName('a'); - for (var j = 0; j < linkList.length; j++) { - if (linkList[j].innerHTML === "") { - linkList[j].setAttribute('aria-hidden', 'true'); - } - } - } -}); diff --git a/docs/articles/drilling.html b/docs/articles/drilling.html deleted file mode 100644 index 94f1b4fa..00000000 --- a/docs/articles/drilling.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - -The nitty gritty on the xportr checks โ€ข xportr - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - - - -

Fancy stuff goes here

-
- - - -
- - - -
- -
-

Site built with pkgdown 1.6.1.

-
- -
-
- - - - - - diff --git a/docs/articles/drilling_files/accessible-code-block-0.0.1/empty-anchor.js b/docs/articles/drilling_files/accessible-code-block-0.0.1/empty-anchor.js deleted file mode 100644 index ca349fd6..00000000 --- a/docs/articles/drilling_files/accessible-code-block-0.0.1/empty-anchor.js +++ /dev/null @@ -1,15 +0,0 @@ -// Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> -// v0.0.1 -// Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. - -document.addEventListener('DOMContentLoaded', function() { - const codeList = document.getElementsByClassName("sourceCode"); - for (var i = 0; i < codeList.length; i++) { - var linkList = codeList[i].getElementsByTagName('a'); - for (var j = 0; j < linkList.length; j++) { - if (linkList[j].innerHTML === "") { - linkList[j].setAttribute('aria-hidden', 'true'); - } - } - } -}); diff --git a/docs/articles/getting_started.html b/docs/articles/getting_started.html deleted file mode 100644 index 766c33a2..00000000 --- a/docs/articles/getting_started.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - -Getting Started โ€ข xportr - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - - - -
-adsl <- haven::read_sas("inst/extdata/adsl.sas7bdat")
-
-var_spec <- readxl::read_xlsx("inst/specs/ADaM_spec.xlsx", sheet = "Variables") %>%
-  dplyr::rename(type = "Data Type") %>%
-  rlang::set_names(tolower)
-  
-data_spec <- readxl::read_xlsx("inst/specs/ADaM_spec.xlsx", sheet = "Datasets") %>%
-  rlang::set_names(tolower) %>%
-  dplyr::rename(label = "description")
-  
-adsl %>%
-  xportr_type(var_spec, "ADSL", "message") %>%
-  xportr_length(var_spec, "ADSL", "message") %>%
-  xportr_label(var_spec, "ADSL", "message") %>%
-  xportr_df_label(data_spec, "ADSL") %>%
-  xportr_write("adsl.xpt")
-
- - - -
- - - -
- -
-

Site built with pkgdown 1.6.1.

-
- -
-
- - - - - - diff --git a/docs/articles/getting_started_files/accessible-code-block-0.0.1/empty-anchor.js b/docs/articles/getting_started_files/accessible-code-block-0.0.1/empty-anchor.js deleted file mode 100644 index ca349fd6..00000000 --- a/docs/articles/getting_started_files/accessible-code-block-0.0.1/empty-anchor.js +++ /dev/null @@ -1,15 +0,0 @@ -// Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> -// v0.0.1 -// Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. - -document.addEventListener('DOMContentLoaded', function() { - const codeList = document.getElementsByClassName("sourceCode"); - for (var i = 0; i < codeList.length; i++) { - var linkList = codeList[i].getElementsByTagName('a'); - for (var j = 0; j < linkList.length; j++) { - if (linkList[j].innerHTML === "") { - linkList[j].setAttribute('aria-hidden', 'true'); - } - } - } -}); diff --git a/docs/articles/index.html b/docs/articles/index.html deleted file mode 100644 index 352bbcec..00000000 --- a/docs/articles/index.html +++ /dev/null @@ -1,79 +0,0 @@ - -Articles โ€ข xportr - - -
-
- - - -
-
- - -
-

All vignettes

-

- -
Getting Started
-
-
-
-
- - -
- -
-

Site built with pkgdown 2.0.3.

-
- -
- - - - - - - - diff --git a/docs/articles/metacore.html b/docs/articles/metacore.html deleted file mode 100644 index 9c3dadea..00000000 --- a/docs/articles/metacore.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - -Interfacing with metacore Package โ€ข xportr - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - - - -

Fancy stuff goes here

-
- - - -
- - - -
- -
-

Site built with pkgdown 1.6.1.

-
- -
-
- - - - - - diff --git a/docs/articles/metacore_files/accessible-code-block-0.0.1/empty-anchor.js b/docs/articles/metacore_files/accessible-code-block-0.0.1/empty-anchor.js deleted file mode 100644 index ca349fd6..00000000 --- a/docs/articles/metacore_files/accessible-code-block-0.0.1/empty-anchor.js +++ /dev/null @@ -1,15 +0,0 @@ -// Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> -// v0.0.1 -// Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. - -document.addEventListener('DOMContentLoaded', function() { - const codeList = document.getElementsByClassName("sourceCode"); - for (var i = 0; i < codeList.length; i++) { - var linkList = codeList[i].getElementsByTagName('a'); - for (var j = 0; j < linkList.length; j++) { - if (linkList[j].innerHTML === "") { - linkList[j].setAttribute('aria-hidden', 'true'); - } - } - } -}); diff --git a/docs/articles/metadata.html b/docs/articles/metadata.html deleted file mode 100644 index 2b987086..00000000 --- a/docs/articles/metadata.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - -Applying and Checking Metadata โ€ข xportr - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - - - - -
- - - -
- - - -
- -
-

Site built with pkgdown 1.6.1.

-
- -
-
- - - - - - diff --git a/docs/articles/metadata_files/accessible-code-block-0.0.1/empty-anchor.js b/docs/articles/metadata_files/accessible-code-block-0.0.1/empty-anchor.js deleted file mode 100644 index ca349fd6..00000000 --- a/docs/articles/metadata_files/accessible-code-block-0.0.1/empty-anchor.js +++ /dev/null @@ -1,15 +0,0 @@ -// Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> -// v0.0.1 -// Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. - -document.addEventListener('DOMContentLoaded', function() { - const codeList = document.getElementsByClassName("sourceCode"); - for (var i = 0; i < codeList.length; i++) { - var linkList = codeList[i].getElementsByTagName('a'); - for (var j = 0; j < linkList.length; j++) { - if (linkList[j].innerHTML === "") { - linkList[j].setAttribute('aria-hidden', 'true'); - } - } - } -}); diff --git a/docs/articles/moving.html b/docs/articles/moving.html deleted file mode 100644 index f288b3fc..00000000 --- a/docs/articles/moving.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - -Beyond the haven and SASxport packages โ€ข xportr - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - - - -

Fancy stuff goes here

-
- - - -
- - - -
- -
-

Site built with pkgdown 1.6.1.

-
- -
-
- - - - - - diff --git a/docs/articles/moving_files/accessible-code-block-0.0.1/empty-anchor.js b/docs/articles/moving_files/accessible-code-block-0.0.1/empty-anchor.js deleted file mode 100644 index ca349fd6..00000000 --- a/docs/articles/moving_files/accessible-code-block-0.0.1/empty-anchor.js +++ /dev/null @@ -1,15 +0,0 @@ -// Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> -// v0.0.1 -// Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. - -document.addEventListener('DOMContentLoaded', function() { - const codeList = document.getElementsByClassName("sourceCode"); - for (var i = 0; i < codeList.length; i++) { - var linkList = codeList[i].getElementsByTagName('a'); - for (var j = 0; j < linkList.length; j++) { - if (linkList[j].innerHTML === "") { - linkList[j].setAttribute('aria-hidden', 'true'); - } - } - } -}); diff --git a/docs/articles/readme.html b/docs/articles/readme.html deleted file mode 100644 index 18b88769..00000000 --- a/docs/articles/readme.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - - -README โ€ข xportr - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - - - -
-

-Tplyr Version 0.1.1

-

Welcome to Tplyr! This is the first full and stable release of our package. With this release comes a number of new enhancements, loads of new documentation, and our complete package qualification document.

-

If youโ€™ve been keeping up, here are the things that weโ€™ve added since the Beta release in July:

-
    -
  • Bug Fixes/Enhancements -
      -
    • Count layers were re-factored to improve the execution efficiency
    • -
    • Auto-precision now works without a by variable
    • -
    • Several new assertions have been added to give clearer error messages
    • -
    • Treatment groups within the population data will produce columns in the resulting build, even if no records exist for that treatment group in the target dataset
    • -
    • Risk difference variable names will now populate properly when a cols argument is used
    • -
    • Data frame attributes are cleaned prior to processing to prevent any merge/bind warnings during processing
    • -
    • Total values within count layers are properly filled when the resulting count is 0 (largely impacts risk-difference calculations)
    • -
    -
  • -
  • Feature additions -
      -
    • Shift layers are here!
    • -
    • Flexibility when filling missing values has been enhanced for descriptive statistic layers
    • -
    • Layers can now be named, and those names can be used in get_numeric_data and the new function get_statistics_data to get risk difference raw numbers. Data may also be filtered directly from both functions.
    • -
    • Default formats can now be set via options or at the table level, which allows you to eliminate a great deal of redundant code
    • -
    -
  • -
-

As always, we welcome your feedback. If you spot a bug, would like to see a new feature, or if any documentation is unclear - submit an issue through GitHub right here.

-
-
-

-What is Tplyr?

-

dplyr from tidyverse is a grammar of data manipulation. So what does that allow you to do? It gives you, as a data analyst, the capability to easily and intuitively approach the problem of manipulating your data into an analysis ready form. dplyr conceptually breaks things down into verbs that allow you to focus on what you want to do more than how you have to do it.

-

Tplyr is designed around a similar concept, but its focus is on building summary tables within the clinical world. In the pharmaceutical industry, a great deal of the data presented in the outputs we create are very similar. For the most part, most of these tables can be broken down into a few categories:

-
    -
  • Counting for event based variables or categories
  • -
  • Shifting, which is just counting a change in state with a โ€˜fromโ€™ and a โ€˜toโ€™
  • -
  • Generating descriptive statistics around some continuous variable.
  • -
-

For many of the tables that go into a clinical submission, at least when considering safety outputs, the tables are made up of a combination of these approaches. Consider a demographics table - and letโ€™s use an example from the PHUSE project Standard Analyses & Code Sharing - Analyses & Displays Associated with Demographics, Disposition, and Medications in Phase 2-4 Clinical Trials and Integrated Summary Documents.

-
-

Demographics Table

-
-

When you look at this table, you can begin breaking this output down into smaller, redundant, components. These components can be viewed as โ€˜layersโ€™, and the table as a whole is constructed by stacking the layers. The boxes in the image above represent how you can begin to conceptualize this.

-
    -
  • First we have Sex, which is made up of n (%) counts.
  • -
  • Next we have Age as a continuous variable, where we have a number of descriptive statistics, including n, mean, standard deviation, median, quartile 1, quartile 3, min, max, and missing values.
  • -
  • After that we have age, but broken into categories - so this is once again n (%) values.
  • -
  • Race - more counting,
  • -
  • Ethnicity - more counting
  • -
  • Weight - and weโ€™re back to descriptive statistics.
  • -
-

So we have one table, with 6 summaries (7 including the next page, not shown) - but only 2 different approaches to summaries being performed. In the same way that dplyr is a grammar of data manipulation, Tplyr aims to be a grammar of data summary. The goal of Tplyr is to allow you to program a summary table like you see it on the page, by breaking a larger problem into smaller โ€˜layersโ€™, and combining them together like you see on the page.

-

Enough talking - letโ€™s see some code. In these examples, we will be using data from the PHUSE Test Data Factory based on the original pilot project submission package. Note: You can see our replication of the CDISC pilot using the PHUSE Test Data Factory data here.

-
-
-tplyr_table(adsl, TRT01P, where = SAFFL == "Y") %>% 
-  add_layer(
-    group_desc(AGE, by = "Age (years)")
-  ) %>% 
-  add_layer(
-    group_count(AGEGR1, by = "Age Categories n (%)")
-  ) %>% 
-  build()
-
-

-The TL;DR

-

Here are some of the high level benefits of using Tplyr:

-
    -
  • Easy construction of table data using an intuitive syntax
  • -
  • Smart string formatting for your numbers thatโ€™s easily specified by the user
  • -
  • A great deal of flexibility in what is performed and how itโ€™s presented, without specifying hundreds of parameters
  • -
  • Extensibility (in the futureโ€ฆ) - weโ€™re going to open doors to allow you some level of customization.
  • -
-
-
-

-Where Next

-

-
-
-
- - - -
- - - -
- -
-

Site built with pkgdown 1.6.1.

-
- -
-
- - - - - - diff --git a/docs/articles/readme_files/accessible-code-block-0.0.1/empty-anchor.js b/docs/articles/readme_files/accessible-code-block-0.0.1/empty-anchor.js deleted file mode 100644 index ca349fd6..00000000 --- a/docs/articles/readme_files/accessible-code-block-0.0.1/empty-anchor.js +++ /dev/null @@ -1,15 +0,0 @@ -// Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> -// v0.0.1 -// Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. - -document.addEventListener('DOMContentLoaded', function() { - const codeList = document.getElementsByClassName("sourceCode"); - for (var i = 0; i < codeList.length; i++) { - var linkList = codeList[i].getElementsByTagName('a'); - for (var j = 0; j < linkList.length; j++) { - if (linkList[j].innerHTML === "") { - linkList[j].setAttribute('aria-hidden', 'true'); - } - } - } -}); diff --git a/docs/articles/xportr.html b/docs/articles/xportr.html deleted file mode 100644 index 830db069..00000000 --- a/docs/articles/xportr.html +++ /dev/null @@ -1,723 +0,0 @@ - - - - - - - -Getting Started โ€ข xportr - - - - - - - - - - - - - - - - - - - - -
-
- - - - -
- - - -
-
- - - - -
-

Getting Started with xportr -

-

The demo will make use of a small ADSL data set that is -apart of the {admiral} -package. The script that generates this ADSL dataset can be -created by using this command -admiral::use_ad_template("adsl").

-

The ADSL has the following features:

-
    -
  • 306 observations
  • -
  • 48 variables
  • -
  • Data types other than character and numeric
  • -
  • Missing labels on variables
  • -
  • Missing label for data set
  • -
  • Order of varibles not following specification file
  • -
  • Formats missing
  • -
-

To create a fully compliant v5 xpt ADSL dataset, that -was developed using R, we will need to apply the 6 main functions within -the xportr package:

- -
-# Loading packages
-library(dplyr)
-library(labelled)
-library(xportr)
-library(admiral)
-
-# Loading in our example data
-adsl <- admiral::admiral_adsl
-


-
-


-

NOTE: Dataset can be created by using this command -admiral::use_ad_template("adsl").

-
-
-

Preparing your Specification Files -

-


-

In order to make use of the functions within xportr you -will need to create an R data frame that contains your specification -file. You will most likely need to do some pre-processing of your spec -sheets after loading in the spec files for them to work appropriately -with the xportr functions. Please see our example spec -sheets in -system.file(paste0("specs/", "ADaM_admiral_spec.xlsx"), package = "xportr") -to see how xportr expects the specification sheets.

-


-
-var_spec <- readxl::read_xlsx(
-  system.file(paste0("specs/", "ADaM_admiral_spec.xlsx"), package = "xportr"), sheet = "Variables") %>%
-  dplyr::rename(type = "Data Type") %>%
-  rlang::set_names(tolower) 
-  
-


-

Below is a quick snapshot of the specification file pertaining to the -ADSL data set, which we will make use of in the 6 -xportr function calls below. Take note of the order, label, -type, length and format columns.

-


-
-


-
-
-

xportr_type() -

-


-

In order to be compliant with transport v5 specifications an -xpt file can only have two data types: character and -numeric/dbl. Currently the ADSL data set has chr, dbl, -time, factor and date.

-
look_for(adsl, details = TRUE)
-   pos variable label                      col_type values                    
-   1   STUDYID  Study Identifier           chr      range: CDISCPILOT01 - CDI~
-   2   USUBJID  Unique Subject Identifier  chr      range: 01-701-1015 - 01-7~
-   3   SUBJID   Subject Identifier for th~ chr      range: 1001 - 1448        
-   4   RFSTDTC  Subject Reference Start D~ chr      range: 2012-07-09 - 2014-~
-   5   RFENDTC  Subject Reference End Dat~ chr      range: 2012-09-01 - 2015-~
-   6   RFXSTDTC Date/Time of First Study ~ chr      range: 2012-07-09 - 2014-~
-   7   RFXENDTC Date/Time of Last Study T~ chr      range: 2012-08-28 - 2015-~
-   8   RFICDTC  Date/Time of Informed Con~ chr      range:                    
-   9   RFPENDTC Date/Time of End of Parti~ chr      range: 2012-08-13 - 2015-~
-   10  DTHDTC   Date/Time of Death         chr      range: 2013-01-14 - 2014-~
-   11  DTHFL    Subject Death Flag         chr      range: Y - Y              
-   12  SITEID   Study Site Identifier      chr      range: 701 - 718          
-   13  AGE      Age                        dbl      range: 50 - 89            
-   14  AGEU     Age Units                  chr      range: YEARS - YEARS      
-   15  SEX      Sex                        chr      range: F - M              
-   16  RACE     Race                       chr      range: AMERICAN INDIAN OR~
-   17  ETHNIC   Ethnicity                  chr      range: HISPANIC OR LATINO~
-   18  ARMCD    Planned Arm Code           chr      range: Pbo - Xan_Lo       
-   19  ARM      Description of Planned Arm chr      range: Placebo - Xanomeli~
-   20  ACTARMCD Actual Arm Code            chr      range: Pbo - Xan_Lo       
-   21  ACTARM   Description of Actual Arm  chr      range: Placebo - Xanomeli~
-   22  COUNTRY  Country                    chr      range: USA - USA          
-   23  DMDTC    Date/Time of Collection    chr      range: 2012-07-06 - 2014-~
-   24  DMDY     Study Day of Collection    dbl      range: -37 - -2           
-   25  TRT01P   Description of Planned Arm chr      range: Placebo - Xanomeli~
-   26  TRT01A   Description of Actual Arm  chr      range: Placebo - Xanomeli~
-   27  TRTSDTM  โ€”                          dttm     range: 2012-07-09 - 2014-~
-   28  TRTEDTM  โ€”                          dttm     range: 2012-08-28 23:59:5~
-   29  TRTSDT   โ€”                          date     range: 2012-07-09 - 2014-~
-   30  TRTEDT   โ€”                          date     range: 2012-08-28 - 2015-~
-   31  TRTDURD  โ€”                          dbl      range: 1 - 212            
-   32  SCRFDT   โ€”                          date     range: 2012-08-13 - 2014-~
-   33  EOSDT    โ€”                          date     range: 2012-09-01 - 2015-~
-   34  EOSSTT   โ€”                          chr      range: COMPLETED - DISCON~
-   35  FRVDT    โ€”                          date     range: 2013-02-18 - 2014-~
-   36  DTHDT    โ€”                          date     range: 2013-01-14 - 2014-~
-   37  DTHDTF   โ€”                          chr      range:                    
-   38  DTHADY   โ€”                          dbl      range: 12 - 175           
-   39  LDDTHELD โ€”                          dbl      range: 0 - 2              
-   40  LSTALVDT โ€”                          date     range: 2012-09-01 - 2015-~
-   41  AGEGR1   โ€”                          fct      <18                       
-                                                    18-64                     
-                                                    >=65                      
-   42  SAFFL    โ€”                          chr      range: Y - Y              
-   43  RACEGR1  โ€”                          chr      range: Non-white - White  
-   44  REGION1  โ€”                          chr      range: NA - NA            
-   45  LDDTHGR1 โ€”                          chr      range: <= 30 - <= 30      
-   46  DTH30FL  โ€”                          chr      range: Y - Y              
-   47  DTHA30FL โ€”                          chr      range:                    
-   48  DTHB30FL โ€”                          chr      range: Y - Y
-


-

Using xport_type and the supplied specification file, we -can coerce the variables in the ADSL set to be -either numeric or character.

-


-
-adsl_type <- xportr_type(adsl, var_spec, domain = "ADSL", verbose = "message") 
-


Now all appropriate types have been applied to the dataset as -seen below.

-
look_for(adsl_type, details = TRUE)
-   pos variable label col_type values                                          
-   1   STUDYID  โ€”     chr      range: CDISCPILOT01 - CDISCPILOT01              
-   2   USUBJID  โ€”     chr      range: 01-701-1015 - 01-718-1427                
-   3   SUBJID   โ€”     chr      range: 1001 - 1448                              
-   4   RFSTDTC  โ€”     chr      range: 2012-07-09 - 2014-09-02                  
-   5   RFENDTC  โ€”     chr      range: 2012-09-01 - 2015-03-05                  
-   6   RFXSTDTC โ€”     chr      range: 2012-07-09 - 2014-09-02                  
-   7   RFXENDTC โ€”     chr      range: 2012-08-28 - 2015-03-05                  
-   8   RFICDTC  โ€”     chr      range:                                          
-   9   RFPENDTC โ€”     chr      range: 2012-08-13 - 2015-03-05T14:40            
-   10  DTHDTC   โ€”     chr      range: 2013-01-14 - 2014-11-01                  
-   11  DTHFL    โ€”     chr      range: Y - Y                                    
-   12  SITEID   โ€”     chr      range: 701 - 718                                
-   13  AGE      โ€”     dbl      range: 50 - 89                                  
-   14  AGEU     โ€”     chr      range: YEARS - YEARS                            
-   15  SEX      โ€”     chr      range: F - M                                    
-   16  RACE     โ€”     chr      range: AMERICAN INDIAN OR ALASKA NATIVE - WHITE 
-   17  ETHNIC   โ€”     chr      range: HISPANIC OR LATINO - NOT HISPANIC OR LAT~
-   18  ARMCD    โ€”     chr      range: Pbo - Xan_Lo                             
-   19  ARM      โ€”     chr      range: Placebo - Xanomeline Low Dose            
-   20  ACTARMCD โ€”     chr      range: Pbo - Xan_Lo                             
-   21  ACTARM   โ€”     chr      range: Placebo - Xanomeline Low Dose            
-   22  COUNTRY  โ€”     chr      range: USA - USA                                
-   23  DMDTC    โ€”     chr      range: 2012-07-06 - 2014-08-29                  
-   24  DMDY     โ€”     dbl      range: -37 - -2                                 
-   25  TRT01P   โ€”     chr      range: Placebo - Xanomeline Low Dose            
-   26  TRT01A   โ€”     chr      range: Placebo - Xanomeline Low Dose            
-   27  TRTSDTM  โ€”     dbl      range: 1341792000 - 1409616000                  
-   28  TRTEDTM  โ€”     dbl      range: 1346198399 - 1425599999                  
-   29  TRTSDT   โ€”     dbl      range: 15530 - 16315                            
-   30  TRTEDT   โ€”     dbl      range: 15580 - 16499                            
-   31  TRTDURD  โ€”     dbl      range: 1 - 212                                  
-   32  SCRFDT   โ€”     dbl      range: 15565 - 16181                            
-   33  EOSDT    โ€”     dbl      range: 15584 - 16499                            
-   34  EOSSTT   โ€”     chr      range: COMPLETED - DISCONTINUED                 
-   35  FRVDT    โ€”     dbl      range: 15754 - 16389                            
-   36  DTHDT    โ€”     dbl      range: 15719 - 16375                            
-   37  DTHDTF   โ€”     chr      range:                                          
-   38  DTHADY   โ€”     dbl      range: 12 - 175                                 
-   39  LDDTHELD โ€”     dbl      range: 0 - 2                                    
-   40  LSTALVDT โ€”     dbl      range: 15584 - 16499                            
-   41  AGEGR1   โ€”     chr      range: >=65 - 18-64                             
-   42  SAFFL    โ€”     chr      range: Y - Y                                    
-   43  RACEGR1  โ€”     chr      range: Non-white - White                        
-   44  REGION1  โ€”     chr      range: NA - NA                                  
-   45  LDDTHGR1 โ€”     chr      range: <= 30 - <= 30                            
-   46  DTH30FL  โ€”     chr      range: Y - Y                                    
-   47  DTHA30FL โ€”     chr      range:                                          
-   48  DTHB30FL โ€”     chr      range: Y - Y
-
-
-

xportr_length() -

-


-

Next we can apply the lengths from a variable level specification -file to the data frame. xportr_length will identify -variables that are missing from your specification file. The function -will also alert you to how many lengths have been applied successfully. -Before we apply the lengths lets verify that no lengths have been -applied to the original dataframe.

-


-
-str(adsl)
-
  tibble [306 ร— 48] (S3: tbl_df/tbl/data.frame)
-   $ STUDYID : chr [1:306] "CDISCPILOT01" "CDISCPILOT01" "CDISCPILOT01" "CDISCPILOT01" ...
-    ..- attr(*, "label")= chr "Study Identifier"
-   $ USUBJID : chr [1:306] "01-701-1015" "01-701-1023" "01-701-1028" "01-701-1033" ...
-    ..- attr(*, "label")= chr "Unique Subject Identifier"
-   $ SUBJID  : chr [1:306] "1015" "1023" "1028" "1033" ...
-    ..- attr(*, "label")= chr "Subject Identifier for the Study"
-   $ RFSTDTC : chr [1:306] "2014-01-02" "2012-08-05" "2013-07-19" "2014-03-18" ...
-    ..- attr(*, "label")= chr "Subject Reference Start Date/Time"
-   $ RFENDTC : chr [1:306] "2014-07-02" "2012-09-02" "2014-01-14" "2014-04-14" ...
-    ..- attr(*, "label")= chr "Subject Reference End Date/Time"
-   $ RFXSTDTC: chr [1:306] "2014-01-02" "2012-08-05" "2013-07-19" "2014-03-18" ...
-    ..- attr(*, "label")= chr "Date/Time of First Study Treatment"
-   $ RFXENDTC: chr [1:306] "2014-07-02" "2012-09-01" "2014-01-14" "2014-03-31" ...
-    ..- attr(*, "label")= chr "Date/Time of Last Study Treatment"
-   $ RFICDTC : chr [1:306] NA NA NA NA ...
-    ..- attr(*, "label")= chr "Date/Time of Informed Consent"
-   $ RFPENDTC: chr [1:306] "2014-07-02T11:45" "2013-02-18" "2014-01-14T11:10" "2014-09-15" ...
-    ..- attr(*, "label")= chr "Date/Time of End of Participation"
-   $ DTHDTC  : chr [1:306] NA NA NA NA ...
-    ..- attr(*, "label")= chr "Date/Time of Death"
-   $ DTHFL   : chr [1:306] NA NA NA NA ...
-    ..- attr(*, "label")= chr "Subject Death Flag"
-   $ SITEID  : chr [1:306] "701" "701" "701" "701" ...
-    ..- attr(*, "label")= chr "Study Site Identifier"
-   $ AGE     : num [1:306] 63 64 71 74 77 85 59 68 81 84 ...
-    ..- attr(*, "label")= chr "Age"
-   $ AGEU    : chr [1:306] "YEARS" "YEARS" "YEARS" "YEARS" ...
-    ..- attr(*, "label")= chr "Age Units"
-   $ SEX     : chr [1:306] "F" "M" "M" "M" ...
-    ..- attr(*, "label")= chr "Sex"
-   $ RACE    : chr [1:306] "WHITE" "WHITE" "WHITE" "WHITE" ...
-    ..- attr(*, "label")= chr "Race"
-   $ ETHNIC  : chr [1:306] "HISPANIC OR LATINO" "HISPANIC OR LATINO" "NOT HISPANIC OR LATINO" "NOT HISPANIC OR LATINO" ...
-    ..- attr(*, "label")= chr "Ethnicity"
-   $ ARMCD   : chr [1:306] "Pbo" "Pbo" "Xan_Hi" "Xan_Lo" ...
-    ..- attr(*, "label")= chr "Planned Arm Code"
-   $ ARM     : chr [1:306] "Placebo" "Placebo" "Xanomeline High Dose" "Xanomeline Low Dose" ...
-    ..- attr(*, "label")= chr "Description of Planned Arm"
-   $ ACTARMCD: chr [1:306] "Pbo" "Pbo" "Xan_Hi" "Xan_Lo" ...
-    ..- attr(*, "label")= chr "Actual Arm Code"
-   $ ACTARM  : chr [1:306] "Placebo" "Placebo" "Xanomeline High Dose" "Xanomeline Low Dose" ...
-    ..- attr(*, "label")= chr "Description of Actual Arm"
-   $ COUNTRY : chr [1:306] "USA" "USA" "USA" "USA" ...
-    ..- attr(*, "label")= chr "Country"
-   $ DMDTC   : chr [1:306] "2013-12-26" "2012-07-22" "2013-07-11" "2014-03-10" ...
-    ..- attr(*, "label")= chr "Date/Time of Collection"
-   $ DMDY    : num [1:306] -7 -14 -8 -8 -7 -21 NA -9 -13 -7 ...
-    ..- attr(*, "label")= chr "Study Day of Collection"
-   $ TRT01P  : chr [1:306] "Placebo" "Placebo" "Xanomeline High Dose" "Xanomeline Low Dose" ...
-    ..- attr(*, "label")= chr "Description of Planned Arm"
-   $ TRT01A  : chr [1:306] "Placebo" "Placebo" "Xanomeline High Dose" "Xanomeline Low Dose" ...
-    ..- attr(*, "label")= chr "Description of Actual Arm"
-   $ TRTSDTM : iso_dtm[1:306], format: "2014-01-02" "2012-08-05" ...
-   $ TRTEDTM : iso_dtm[1:306], format: "2014-07-02 23:59:59" "2012-09-01 23:59:59" ...
-   $ TRTSDT  : Date[1:306], format: "2014-01-02" "2012-08-05" ...
-   $ TRTEDT  : Date[1:306], format: "2014-07-02" "2012-09-01" ...
-   $ TRTDURD : num [1:306] 182 28 180 14 183 26 NA 190 10 55 ...
-   $ SCRFDT  : Date[1:306], format: NA NA ...
-   $ EOSDT   : Date[1:306], format: "2014-07-02" "2012-09-02" ...
-   $ EOSSTT  : chr [1:306] "COMPLETED" "DISCONTINUED" "COMPLETED" "DISCONTINUED" ...
-   $ FRVDT   : Date[1:306], format: NA "2013-02-18" ...
-   $ DTHDT   : Date[1:306], format: NA NA ...
-   $ DTHDTF  : chr [1:306] NA NA NA NA ...
-   $ DTHADY  : num [1:306] NA NA NA NA NA NA NA NA NA NA ...
-   $ LDDTHELD: num [1:306] NA NA NA NA NA NA NA NA NA NA ...
-   $ LSTALVDT: Date[1:306], format: "2014-07-02" "2012-09-02" ...
-   $ AGEGR1  : Factor w/ 3 levels "<18","18-64",..: 2 2 3 3 3 3 2 3 3 3 ...
-   $ SAFFL   : chr [1:306] "Y" "Y" "Y" "Y" ...
-   $ RACEGR1 : chr [1:306] "White" "White" "White" "White" ...
-   $ REGION1 : chr [1:306] "NA" "NA" "NA" "NA" ...
-   $ LDDTHGR1: chr [1:306] NA NA NA NA ...
-   $ DTH30FL : chr [1:306] NA NA NA NA ...
-   $ DTHA30FL: chr [1:306] NA NA NA NA ...
-   $ DTHB30FL: chr [1:306] NA NA NA NA ...
-


-

No lengths have been applied to the variables as seen in the printout -- the lengths would be in the attr part of each variables. -Letโ€™s now use xportr_length to apply our lengths from the -specification file.

-
-adsl_length <- adsl %>% xportr_length(var_spec, domain = "ADSL", "message")
-


-
-str(adsl_length)
-
  tibble [306 ร— 48] (S3: tbl_df/tbl/data.frame)
-   $ STUDYID : chr [1:306] "CDISCPILOT01" "CDISCPILOT01" "CDISCPILOT01" "CDISCPILOT01" ...
-    ..- attr(*, "label")= chr "Study Identifier"
-    ..- attr(*, "width")= num 21
-   $ USUBJID : chr [1:306] "01-701-1015" "01-701-1023" "01-701-1028" "01-701-1033" ...
-    ..- attr(*, "label")= chr "Unique Subject Identifier"
-    ..- attr(*, "width")= num 30
-   $ SUBJID  : chr [1:306] "1015" "1023" "1028" "1033" ...
-    ..- attr(*, "label")= chr "Subject Identifier for the Study"
-    ..- attr(*, "width")= num 8
-   $ RFSTDTC : chr [1:306] "2014-01-02" "2012-08-05" "2013-07-19" "2014-03-18" ...
-    ..- attr(*, "label")= chr "Subject Reference Start Date/Time"
-    ..- attr(*, "width")= num 19
-   $ RFENDTC : chr [1:306] "2014-07-02" "2012-09-02" "2014-01-14" "2014-04-14" ...
-    ..- attr(*, "label")= chr "Subject Reference End Date/Time"
-    ..- attr(*, "width")= num 19
-   $ RFXSTDTC: chr [1:306] "2014-01-02" "2012-08-05" "2013-07-19" "2014-03-18" ...
-    ..- attr(*, "label")= chr "Date/Time of First Study Treatment"
-    ..- attr(*, "width")= num 19
-   $ RFXENDTC: chr [1:306] "2014-07-02" "2012-09-01" "2014-01-14" "2014-03-31" ...
-    ..- attr(*, "label")= chr "Date/Time of Last Study Treatment"
-    ..- attr(*, "width")= num 19
-   $ RFICDTC : chr [1:306] NA NA NA NA ...
-    ..- attr(*, "label")= chr "Date/Time of Informed Consent"
-    ..- attr(*, "width")= num 19
-   $ RFPENDTC: chr [1:306] "2014-07-02T11:45" "2013-02-18" "2014-01-14T11:10" "2014-09-15" ...
-    ..- attr(*, "label")= chr "Date/Time of End of Participation"
-    ..- attr(*, "width")= num 19
-   $ DTHDTC  : chr [1:306] NA NA NA NA ...
-    ..- attr(*, "label")= chr "Date/Time of Death"
-    ..- attr(*, "width")= num 19
-   $ DTHFL   : chr [1:306] NA NA NA NA ...
-    ..- attr(*, "label")= chr "Subject Death Flag"
-    ..- attr(*, "width")= num 2
-   $ SITEID  : chr [1:306] "701" "701" "701" "701" ...
-    ..- attr(*, "label")= chr "Study Site Identifier"
-    ..- attr(*, "width")= num 5
-   $ AGE     : num [1:306] 63 64 71 74 77 85 59 68 81 84 ...
-    ..- attr(*, "label")= chr "Age"
-    ..- attr(*, "width")= num 8
-   $ AGEU    : chr [1:306] "YEARS" "YEARS" "YEARS" "YEARS" ...
-    ..- attr(*, "label")= chr "Age Units"
-    ..- attr(*, "width")= num 10
-   $ SEX     : chr [1:306] "F" "M" "M" "M" ...
-    ..- attr(*, "label")= chr "Sex"
-    ..- attr(*, "width")= num 1
-   $ RACE    : chr [1:306] "WHITE" "WHITE" "WHITE" "WHITE" ...
-    ..- attr(*, "label")= chr "Race"
-    ..- attr(*, "width")= num 60
-   $ ETHNIC  : chr [1:306] "HISPANIC OR LATINO" "HISPANIC OR LATINO" "NOT HISPANIC OR LATINO" "NOT HISPANIC OR LATINO" ...
-    ..- attr(*, "label")= chr "Ethnicity"
-    ..- attr(*, "width")= num 100
-   $ ARMCD   : chr [1:306] "Pbo" "Pbo" "Xan_Hi" "Xan_Lo" ...
-    ..- attr(*, "label")= chr "Planned Arm Code"
-    ..- attr(*, "width")= num 20
-   $ ARM     : chr [1:306] "Placebo" "Placebo" "Xanomeline High Dose" "Xanomeline Low Dose" ...
-    ..- attr(*, "label")= chr "Description of Planned Arm"
-    ..- attr(*, "width")= num 200
-   $ ACTARMCD: chr [1:306] "Pbo" "Pbo" "Xan_Hi" "Xan_Lo" ...
-    ..- attr(*, "label")= chr "Actual Arm Code"
-    ..- attr(*, "width")= num 20
-   $ ACTARM  : chr [1:306] "Placebo" "Placebo" "Xanomeline High Dose" "Xanomeline Low Dose" ...
-    ..- attr(*, "label")= chr "Description of Actual Arm"
-    ..- attr(*, "width")= num 200
-   $ COUNTRY : chr [1:306] "USA" "USA" "USA" "USA" ...
-    ..- attr(*, "label")= chr "Country"
-    ..- attr(*, "width")= num 3
-   $ DMDTC   : chr [1:306] "2013-12-26" "2012-07-22" "2013-07-11" "2014-03-10" ...
-    ..- attr(*, "label")= chr "Date/Time of Collection"
-    ..- attr(*, "width")= num 19
-   $ DMDY    : num [1:306] -7 -14 -8 -8 -7 -21 NA -9 -13 -7 ...
-    ..- attr(*, "label")= chr "Study Day of Collection"
-    ..- attr(*, "width")= num 8
-   $ TRT01P  : chr [1:306] "Placebo" "Placebo" "Xanomeline High Dose" "Xanomeline Low Dose" ...
-    ..- attr(*, "label")= chr "Description of Planned Arm"
-    ..- attr(*, "width")= num 40
-   $ TRT01A  : chr [1:306] "Placebo" "Placebo" "Xanomeline High Dose" "Xanomeline Low Dose" ...
-    ..- attr(*, "label")= chr "Description of Actual Arm"
-    ..- attr(*, "width")= num 40
-   $ TRTSDTM : iso_dtm[1:306], format: "2014-01-02" "2012-08-05" ...
-   $ TRTEDTM : iso_dtm[1:306], format: "2014-07-02 23:59:59" "2012-09-01 23:59:59" ...
-   $ TRTSDT  : Date[1:306], format: "2014-01-02" "2012-08-05" ...
-   $ TRTEDT  : Date[1:306], format: "2014-07-02" "2012-09-01" ...
-   $ TRTDURD : num [1:306] 182 28 180 14 183 26 NA 190 10 55 ...
-    ..- attr(*, "width")= num 8
-   $ SCRFDT  : Date[1:306], format: NA NA ...
-   $ EOSDT   : Date[1:306], format: "2014-07-02" "2012-09-02" ...
-   $ EOSSTT  : chr [1:306] "COMPLETED" "DISCONTINUED" "COMPLETED" "DISCONTINUED" ...
-    ..- attr(*, "width")= num 200
-   $ FRVDT   : Date[1:306], format: NA "2013-02-18" ...
-   $ DTHDT   : Date[1:306], format: NA NA ...
-   $ DTHDTF  : chr [1:306] NA NA NA NA ...
-    ..- attr(*, "width")= num 2
-   $ DTHADY  : num [1:306] NA NA NA NA NA NA NA NA NA NA ...
-    ..- attr(*, "width")= num 8
-   $ LDDTHELD: num [1:306] NA NA NA NA NA NA NA NA NA NA ...
-    ..- attr(*, "width")= num 8
-   $ LSTALVDT: Date[1:306], format: "2014-07-02" "2012-09-02" ...
-   $ AGEGR1  : Factor w/ 3 levels "<18","18-64",..: 2 2 3 3 3 3 2 3 3 3 ...
-    ..- attr(*, "width")= num 20
-   $ SAFFL   : chr [1:306] "Y" "Y" "Y" "Y" ...
-    ..- attr(*, "width")= num 2
-   $ RACEGR1 : chr [1:306] "White" "White" "White" "White" ...
-    ..- attr(*, "width")= num 200
-   $ REGION1 : chr [1:306] "NA" "NA" "NA" "NA" ...
-    ..- attr(*, "width")= num 80
-   $ LDDTHGR1: chr [1:306] NA NA NA NA ...
-    ..- attr(*, "width")= num 200
-   $ DTH30FL : chr [1:306] NA NA NA NA ...
-    ..- attr(*, "width")= num 200
-   $ DTHA30FL: chr [1:306] NA NA NA NA ...
-    ..- attr(*, "width")= num 200
-   $ DTHB30FL: chr [1:306] NA NA NA NA ...
-    ..- attr(*, "width")= num 200
-   - attr(*, "_xportr.df_arg_")= chr "ADSL"
-

Note the additional attr(*, "width")= after each -variable with the width. These have been directly applied from the -specification file that we loaded above!

-
-
-

xportr_order() -

-

Please note that the order of the ADSL variables, see -above, does not match specification file order column. We can quickly -remedy this with a call to xportr_order(). Note that the -variable SITEID has been moved as well as many others to -match the specification file order column.

-
-adsl_order <- xportr_order(adsl,var_spec, domain = "ADSL", verbose = "message") 
-
- -
-
-

xportr_format() -

-

Now we apply formats to the dataset. These will typically be -DATE9., DATETIME20 or TIME5, but -many others can be used. Notice that 8 Date/Time variables are missing a -format in our ADSL dataset. Here we just take a peak at a -few TRT variables, which have a NULL -format.

-
-attr(adsl$TRTSDT, "format.sas")
-  NULL
-attr(adsl$TRTEDT, "format.sas")
-  NULL
-attr(adsl$TRTSDTM, "format.sas")
-  NULL
-attr(adsl$TRTEDTM, "format.sas")
-  NULL
-

Using our xportr_format() we apply our formats.

-
-adsl_fmt <- adsl %>% xportr_format(var_spec, domain = "ADSL", "message")
-
attr(adsl_fmt$TRTSDT, "format.sas")
-  [1] "DATE9."
-attr(adsl_fmt$TRTEDT, "format.sas")
-  [1] "DATE9."
-attr(adsl_fmt$TRTSDTM, "format.sas")
-  [1] "DATETIME20."
-attr(adsl_fmt$TRTEDTM, "format.sas")
-  [1] "DATETIME20."
-
-
-

xportr_label() -

-


-

Please observe that our ADSL dataset is missing many -variable labels. Sometimes these labels can be lost while using Rโ€™s -function. However, A CDISC compliant data set needs to have each -variable with a variable label.

-
look_for(adsl, details = FALSE)
-   pos variable label                             
-    1  STUDYID  Study Identifier                  
-    2  USUBJID  Unique Subject Identifier         
-    3  SUBJID   Subject Identifier for the Study  
-    4  RFSTDTC  Subject Reference Start Date/Time 
-    5  RFENDTC  Subject Reference End Date/Time   
-    6  RFXSTDTC Date/Time of First Study Treatment
-    7  RFXENDTC Date/Time of Last Study Treatment 
-    8  RFICDTC  Date/Time of Informed Consent     
-    9  RFPENDTC Date/Time of End of Participation 
-   10  DTHDTC   Date/Time of Death                
-   11  DTHFL    Subject Death Flag                
-   12  SITEID   Study Site Identifier             
-   13  AGE      Age                               
-   14  AGEU     Age Units                         
-   15  SEX      Sex                               
-   16  RACE     Race                              
-   17  ETHNIC   Ethnicity                         
-   18  ARMCD    Planned Arm Code                  
-   19  ARM      Description of Planned Arm        
-   20  ACTARMCD Actual Arm Code                   
-   21  ACTARM   Description of Actual Arm         
-   22  COUNTRY  Country                           
-   23  DMDTC    Date/Time of Collection           
-   24  DMDY     Study Day of Collection           
-   25  TRT01P   Description of Planned Arm        
-   26  TRT01A   Description of Actual Arm         
-   27  TRTSDTM  โ€”                                 
-   28  TRTEDTM  โ€”                                 
-   29  TRTSDT   โ€”                                 
-   30  TRTEDT   โ€”                                 
-   31  TRTDURD  โ€”                                 
-   32  SCRFDT   โ€”                                 
-   33  EOSDT    โ€”                                 
-   34  EOSSTT   โ€”                                 
-   35  FRVDT    โ€”                                 
-   36  DTHDT    โ€”                                 
-   37  DTHDTF   โ€”                                 
-   38  DTHADY   โ€”                                 
-   39  LDDTHELD โ€”                                 
-   40  LSTALVDT โ€”                                 
-   41  AGEGR1   โ€”                                 
-   42  SAFFL    โ€”                                 
-   43  RACEGR1  โ€”                                 
-   44  REGION1  โ€”                                 
-   45  LDDTHGR1 โ€”                                 
-   46  DTH30FL  โ€”                                 
-   47  DTHA30FL โ€”                                 
-   48  DTHB30FL โ€”
-


-

Using the xport_label function we can take the -specifications file and label all the variables available. -xportr_label will produce a warning message if you the -variable in the data set is not in the specification file.

-


-
-adsl_update <- adsl %>% xportr_label(var_spec, domain = "ADSL", "message")
-
look_for(adsl_update, details = FALSE)
-   pos variable label                                  
-    1  STUDYID  Study Identifier                       
-    2  USUBJID  Unique Subject Identifier              
-    3  SUBJID   Subject Identifier for the Study       
-    4  RFSTDTC  Subject Reference Start Date/Time      
-    5  RFENDTC  Subject Reference End Date/Time        
-    6  RFXSTDTC Date/Time of First Study Treatment     
-    7  RFXENDTC Date/Time of Last Study Treatment      
-    8  RFICDTC  Date/Time of Informed Consent          
-    9  RFPENDTC Date/Time of End of Participation      
-   10  DTHDTC   Date / Time of Death                   
-   11  DTHFL    Subject Death Flag                     
-   12  SITEID   Study Site Identifier                  
-   13  AGE      Age                                    
-   14  AGEU     Age Units                              
-   15  SEX      Sex                                    
-   16  RACE     Race                                   
-   17  ETHNIC   Ethnicity                              
-   18  ARMCD    Planned Arm Code                       
-   19  ARM      Description of Planned Arm             
-   20  ACTARMCD Actual Arm Code                        
-   21  ACTARM   Description of Actual Arm              
-   22  COUNTRY  Country                                
-   23  DMDTC    Date/Time of Collection                
-   24  DMDY     Study Day of Collection                
-   25  TRT01P   Planned Treatment for Period 01        
-   26  TRT01A   Actual Treatment for Period 01         
-   27  TRTSDTM  Datetime of First Exposure to Treatment
-   28  TRTEDTM  Datetime of Last Exposure to Treatment 
-   29  TRTSDT   Date of First Exposure to Treatment    
-   30  TRTEDT   Date of Last Exposure to Treatment     
-   31  TRTDURD  Total Duration of Trt  (days)          
-   32  SCRFDT   Screen Failure Date                    
-   33  EOSDT    End of Study Date                      
-   34  EOSSTT   End of Study Status                    
-   35  FRVDT    Final Retrievel Visit Date             
-   36  DTHDT    Death Date                             
-   37  DTHDTF   Date of Death Imputation Flag          
-   38  DTHADY   Relative Day of Death                  
-   39  LDDTHELD Elapsed Days from Last Dose to Death   
-   40  LSTALVDT Date Last Known Alive                  
-   41  AGEGR1   Pooled Age Group 1                     
-   42  SAFFL    Safety Population Flag                 
-   43  RACEGR1  Pooled Race Group 1                    
-   44  REGION1  Geographic Region 1                    
-   45  LDDTHGR1 Last Does to Death Group               
-   46  DTH30FL  Under 30  Group                        
-   47  DTHA30FL Over 30  Group                         
-   48  DTHB30FL Over 30 plus 30 days Group
-
-
-

xportr_write() -

-


-

Finally, we arrive at exporting the R data frame object as a xpt file -with the function xportr_write(). The xpt file will be -written directly to your current working directory. To make it more -interesting, we have put together all six functions with the magrittr -pipe, %>%. A user can now apply types, length, variable -labels, formats, data set label and write out their final xpt file in -one pipe! Appropriate warnings and messages will be supplied to a user -to the console for any potential issues before sending off to standard -clinical data set validator application or data reviewers.

-
-adsl %>%
-  xportr_type(var_spec, "ADSL", "message") %>%
-  xportr_length(var_spec, "ADSL", "message") %>%
-  xportr_label(var_spec, "ADSL", "message") %>%
-  xportr_order(var_spec, "ADSL", "message") %>% 
-  xportr_format(var_spec, "ADSL", "message") %>% 
-  xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset")
-

Thatโ€™s it! We now have a xpt file created in R with all appropriate -types, lengths, labels, ordering and formats from our specification -file.

-

As always, we welcome your feedback. If you spot a bug, would like to -see a new feature, or if any documentation is unclear - submit an issue -on xportrโ€™s -Github page.

-
-
- - - -
- - - -
- -
-

-

Site built with pkgdown 2.0.3.

-
- -
-
- - - - - - - - diff --git a/docs/articles/xportr_files/accessible-code-block-0.0.1/empty-anchor.js b/docs/articles/xportr_files/accessible-code-block-0.0.1/empty-anchor.js deleted file mode 100644 index ca349fd6..00000000 --- a/docs/articles/xportr_files/accessible-code-block-0.0.1/empty-anchor.js +++ /dev/null @@ -1,15 +0,0 @@ -// Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> -// v0.0.1 -// Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. - -document.addEventListener('DOMContentLoaded', function() { - const codeList = document.getElementsByClassName("sourceCode"); - for (var i = 0; i < codeList.length; i++) { - var linkList = codeList[i].getElementsByTagName('a'); - for (var j = 0; j < linkList.length; j++) { - if (linkList[j].innerHTML === "") { - linkList[j].setAttribute('aria-hidden', 'true'); - } - } - } -}); diff --git a/docs/articles/xportr_files/crosstalk-1.1.1/css/crosstalk.css b/docs/articles/xportr_files/crosstalk-1.1.1/css/crosstalk.css deleted file mode 100644 index 46befd2e..00000000 --- a/docs/articles/xportr_files/crosstalk-1.1.1/css/crosstalk.css +++ /dev/null @@ -1,27 +0,0 @@ -/* Adjust margins outwards, so column contents line up with the edges of the - parent of container-fluid. */ -.container-fluid.crosstalk-bscols { - margin-left: -30px; - margin-right: -30px; - white-space: normal; -} - -/* But don't adjust the margins outwards if we're directly under the body, - i.e. we were the top-level of something at the console. */ -body > .container-fluid.crosstalk-bscols { - margin-left: auto; - margin-right: auto; -} - -.crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column { - display: inline-block; - padding-right: 12px; - vertical-align: top; -} - -@media only screen and (max-width:480px) { - .crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column { - display: block; - padding-right: inherit; - } -} diff --git a/docs/articles/xportr_files/crosstalk-1.1.1/js/crosstalk.js b/docs/articles/xportr_files/crosstalk-1.1.1/js/crosstalk.js deleted file mode 100644 index fd9eb53d..00000000 --- a/docs/articles/xportr_files/crosstalk-1.1.1/js/crosstalk.js +++ /dev/null @@ -1,1474 +0,0 @@ -(function(){function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o b) { - return 1; - } -} - -/** - * @private - */ - -var FilterSet = function () { - function FilterSet() { - _classCallCheck(this, FilterSet); - - this.reset(); - } - - _createClass(FilterSet, [{ - key: "reset", - value: function reset() { - // Key: handle ID, Value: array of selected keys, or null - this._handles = {}; - // Key: key string, Value: count of handles that include it - this._keys = {}; - this._value = null; - this._activeHandles = 0; - } - }, { - key: "update", - value: function update(handleId, keys) { - if (keys !== null) { - keys = keys.slice(0); // clone before sorting - keys.sort(naturalComparator); - } - - var _diffSortedLists = (0, _util.diffSortedLists)(this._handles[handleId], keys), - added = _diffSortedLists.added, - removed = _diffSortedLists.removed; - - this._handles[handleId] = keys; - - for (var i = 0; i < added.length; i++) { - this._keys[added[i]] = (this._keys[added[i]] || 0) + 1; - } - for (var _i = 0; _i < removed.length; _i++) { - this._keys[removed[_i]]--; - } - - this._updateValue(keys); - } - - /** - * @param {string[]} keys Sorted array of strings that indicate - * a superset of possible keys. - * @private - */ - - }, { - key: "_updateValue", - value: function _updateValue() { - var keys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this._allKeys; - - var handleCount = Object.keys(this._handles).length; - if (handleCount === 0) { - this._value = null; - } else { - this._value = []; - for (var i = 0; i < keys.length; i++) { - var count = this._keys[keys[i]]; - if (count === handleCount) { - this._value.push(keys[i]); - } - } - } - } - }, { - key: "clear", - value: function clear(handleId) { - if (typeof this._handles[handleId] === "undefined") { - return; - } - - var keys = this._handles[handleId]; - if (!keys) { - keys = []; - } - - for (var i = 0; i < keys.length; i++) { - this._keys[keys[i]]--; - } - delete this._handles[handleId]; - - this._updateValue(); - } - }, { - key: "value", - get: function get() { - return this._value; - } - }, { - key: "_allKeys", - get: function get() { - var allKeys = Object.keys(this._keys); - allKeys.sort(naturalComparator); - return allKeys; - } - }]); - - return FilterSet; -}(); - -exports.default = FilterSet; - -},{"./util":11}],4:[function(require,module,exports){ -(function (global){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -exports.default = group; - -var _var2 = require("./var"); - -var _var3 = _interopRequireDefault(_var2); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -// Use a global so that multiple copies of crosstalk.js can be loaded and still -// have groups behave as singletons across all copies. -global.__crosstalk_groups = global.__crosstalk_groups || {}; -var groups = global.__crosstalk_groups; - -function group(groupName) { - if (groupName && typeof groupName === "string") { - if (!groups.hasOwnProperty(groupName)) { - groups[groupName] = new Group(groupName); - } - return groups[groupName]; - } else if ((typeof groupName === "undefined" ? "undefined" : _typeof(groupName)) === "object" && groupName._vars && groupName.var) { - // Appears to already be a group object - return groupName; - } else if (Array.isArray(groupName) && groupName.length == 1 && typeof groupName[0] === "string") { - return group(groupName[0]); - } else { - throw new Error("Invalid groupName argument"); - } -} - -var Group = function () { - function Group(name) { - _classCallCheck(this, Group); - - this.name = name; - this._vars = {}; - } - - _createClass(Group, [{ - key: "var", - value: function _var(name) { - if (!name || typeof name !== "string") { - throw new Error("Invalid var name"); - } - - if (!this._vars.hasOwnProperty(name)) this._vars[name] = new _var3.default(this, name); - return this._vars[name]; - } - }, { - key: "has", - value: function has(name) { - if (!name || typeof name !== "string") { - throw new Error("Invalid var name"); - } - - return this._vars.hasOwnProperty(name); - } - }]); - - return Group; -}(); - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - -},{"./var":12}],5:[function(require,module,exports){ -(function (global){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _group = require("./group"); - -var _group2 = _interopRequireDefault(_group); - -var _selection = require("./selection"); - -var _filter = require("./filter"); - -var _input = require("./input"); - -require("./input_selectize"); - -require("./input_checkboxgroup"); - -require("./input_slider"); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var defaultGroup = (0, _group2.default)("default"); - -function var_(name) { - return defaultGroup.var(name); -} - -function has(name) { - return defaultGroup.has(name); -} - -if (global.Shiny) { - global.Shiny.addCustomMessageHandler("update-client-value", function (message) { - if (typeof message.group === "string") { - (0, _group2.default)(message.group).var(message.name).set(message.value); - } else { - var_(message.name).set(message.value); - } - }); -} - -var crosstalk = { - group: _group2.default, - var: var_, - has: has, - SelectionHandle: _selection.SelectionHandle, - FilterHandle: _filter.FilterHandle, - bind: _input.bind -}; - -/** - * @namespace crosstalk - */ -exports.default = crosstalk; - -global.crosstalk = crosstalk; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - -},{"./filter":2,"./group":4,"./input":6,"./input_checkboxgroup":7,"./input_selectize":8,"./input_slider":9,"./selection":10}],6:[function(require,module,exports){ -(function (global){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.register = register; -exports.bind = bind; -var $ = global.jQuery; - -var bindings = {}; - -function register(reg) { - bindings[reg.className] = reg; - if (global.document && global.document.readyState !== "complete") { - $(function () { - bind(); - }); - } else if (global.document) { - setTimeout(bind, 100); - } -} - -function bind() { - Object.keys(bindings).forEach(function (className) { - var binding = bindings[className]; - $("." + binding.className).not(".crosstalk-input-bound").each(function (i, el) { - bindInstance(binding, el); - }); - }); -} - -// Escape jQuery identifier -function $escape(val) { - return val.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1"); -} - -function bindEl(el) { - var $el = $(el); - Object.keys(bindings).forEach(function (className) { - if ($el.hasClass(className) && !$el.hasClass("crosstalk-input-bound")) { - var binding = bindings[className]; - bindInstance(binding, el); - } - }); -} - -function bindInstance(binding, el) { - var jsonEl = $(el).find("script[type='application/json'][data-for='" + $escape(el.id) + "']"); - var data = JSON.parse(jsonEl[0].innerText); - - var instance = binding.factory(el, data); - $(el).data("crosstalk-instance", instance); - $(el).addClass("crosstalk-input-bound"); -} - -if (global.Shiny) { - var inputBinding = new global.Shiny.InputBinding(); - var _$ = global.jQuery; - _$.extend(inputBinding, { - find: function find(scope) { - return _$(scope).find(".crosstalk-input"); - }, - initialize: function initialize(el) { - if (!_$(el).hasClass("crosstalk-input-bound")) { - bindEl(el); - } - }, - getId: function getId(el) { - return el.id; - }, - getValue: function getValue(el) {}, - setValue: function setValue(el, value) {}, - receiveMessage: function receiveMessage(el, data) {}, - subscribe: function subscribe(el, callback) { - _$(el).data("crosstalk-instance").resume(); - }, - unsubscribe: function unsubscribe(el) { - _$(el).data("crosstalk-instance").suspend(); - } - }); - global.Shiny.inputBindings.register(inputBinding, "crosstalk.inputBinding"); -} - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - -},{}],7:[function(require,module,exports){ -(function (global){ -"use strict"; - -var _input = require("./input"); - -var input = _interopRequireWildcard(_input); - -var _filter = require("./filter"); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -var $ = global.jQuery; - -input.register({ - className: "crosstalk-input-checkboxgroup", - - factory: function factory(el, data) { - /* - * map: {"groupA": ["keyA", "keyB", ...], ...} - * group: "ct-groupname" - */ - var ctHandle = new _filter.FilterHandle(data.group); - - var lastKnownKeys = void 0; - var $el = $(el); - $el.on("change", "input[type='checkbox']", function () { - var checked = $el.find("input[type='checkbox']:checked"); - if (checked.length === 0) { - lastKnownKeys = null; - ctHandle.clear(); - } else { - var keys = {}; - checked.each(function () { - data.map[this.value].forEach(function (key) { - keys[key] = true; - }); - }); - var keyArray = Object.keys(keys); - keyArray.sort(); - lastKnownKeys = keyArray; - ctHandle.set(keyArray); - } - }); - - return { - suspend: function suspend() { - ctHandle.clear(); - }, - resume: function resume() { - if (lastKnownKeys) ctHandle.set(lastKnownKeys); - } - }; - } -}); - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - -},{"./filter":2,"./input":6}],8:[function(require,module,exports){ -(function (global){ -"use strict"; - -var _input = require("./input"); - -var input = _interopRequireWildcard(_input); - -var _util = require("./util"); - -var util = _interopRequireWildcard(_util); - -var _filter = require("./filter"); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -var $ = global.jQuery; - -input.register({ - className: "crosstalk-input-select", - - factory: function factory(el, data) { - /* - * items: {value: [...], label: [...]} - * map: {"groupA": ["keyA", "keyB", ...], ...} - * group: "ct-groupname" - */ - - var first = [{ value: "", label: "(All)" }]; - var items = util.dataframeToD3(data.items); - var opts = { - options: first.concat(items), - valueField: "value", - labelField: "label", - searchField: "label" - }; - - var select = $(el).find("select")[0]; - - var selectize = $(select).selectize(opts)[0].selectize; - - var ctHandle = new _filter.FilterHandle(data.group); - - var lastKnownKeys = void 0; - selectize.on("change", function () { - if (selectize.items.length === 0) { - lastKnownKeys = null; - ctHandle.clear(); - } else { - var keys = {}; - selectize.items.forEach(function (group) { - data.map[group].forEach(function (key) { - keys[key] = true; - }); - }); - var keyArray = Object.keys(keys); - keyArray.sort(); - lastKnownKeys = keyArray; - ctHandle.set(keyArray); - } - }); - - return { - suspend: function suspend() { - ctHandle.clear(); - }, - resume: function resume() { - if (lastKnownKeys) ctHandle.set(lastKnownKeys); - } - }; - } -}); - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - -},{"./filter":2,"./input":6,"./util":11}],9:[function(require,module,exports){ -(function (global){ -"use strict"; - -var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); - -var _input = require("./input"); - -var input = _interopRequireWildcard(_input); - -var _filter = require("./filter"); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -var $ = global.jQuery; -var strftime = global.strftime; - -input.register({ - className: "crosstalk-input-slider", - - factory: function factory(el, data) { - /* - * map: {"groupA": ["keyA", "keyB", ...], ...} - * group: "ct-groupname" - */ - var ctHandle = new _filter.FilterHandle(data.group); - - var opts = {}; - var $el = $(el).find("input"); - var dataType = $el.data("data-type"); - var timeFormat = $el.data("time-format"); - var round = $el.data("round"); - var timeFormatter = void 0; - - // Set up formatting functions - if (dataType === "date") { - timeFormatter = strftime.utc(); - opts.prettify = function (num) { - return timeFormatter(timeFormat, new Date(num)); - }; - } else if (dataType === "datetime") { - var timezone = $el.data("timezone"); - if (timezone) timeFormatter = strftime.timezone(timezone);else timeFormatter = strftime; - - opts.prettify = function (num) { - return timeFormatter(timeFormat, new Date(num)); - }; - } else if (dataType === "number") { - if (typeof round !== "undefined") opts.prettify = function (num) { - var factor = Math.pow(10, round); - return Math.round(num * factor) / factor; - }; - } - - $el.ionRangeSlider(opts); - - function getValue() { - var result = $el.data("ionRangeSlider").result; - - // Function for converting numeric value from slider to appropriate type. - var convert = void 0; - var dataType = $el.data("data-type"); - if (dataType === "date") { - convert = function convert(val) { - return formatDateUTC(new Date(+val)); - }; - } else if (dataType === "datetime") { - convert = function convert(val) { - // Convert ms to s - return +val / 1000; - }; - } else { - convert = function convert(val) { - return +val; - }; - } - - if ($el.data("ionRangeSlider").options.type === "double") { - return [convert(result.from), convert(result.to)]; - } else { - return convert(result.from); - } - } - - var lastKnownKeys = null; - - $el.on("change.crosstalkSliderInput", function (event) { - if (!$el.data("updating") && !$el.data("animating")) { - var _getValue = getValue(), - _getValue2 = _slicedToArray(_getValue, 2), - from = _getValue2[0], - to = _getValue2[1]; - - var keys = []; - for (var i = 0; i < data.values.length; i++) { - var val = data.values[i]; - if (val >= from && val <= to) { - keys.push(data.keys[i]); - } - } - keys.sort(); - ctHandle.set(keys); - lastKnownKeys = keys; - } - }); - - // let $el = $(el); - // $el.on("change", "input[type="checkbox"]", function() { - // let checked = $el.find("input[type="checkbox"]:checked"); - // if (checked.length === 0) { - // ctHandle.clear(); - // } else { - // let keys = {}; - // checked.each(function() { - // data.map[this.value].forEach(function(key) { - // keys[key] = true; - // }); - // }); - // let keyArray = Object.keys(keys); - // keyArray.sort(); - // ctHandle.set(keyArray); - // } - // }); - - return { - suspend: function suspend() { - ctHandle.clear(); - }, - resume: function resume() { - if (lastKnownKeys) ctHandle.set(lastKnownKeys); - } - }; - } -}); - -// Convert a number to a string with leading zeros -function padZeros(n, digits) { - var str = n.toString(); - while (str.length < digits) { - str = "0" + str; - }return str; -} - -// Given a Date object, return a string in yyyy-mm-dd format, using the -// UTC date. This may be a day off from the date in the local time zone. -function formatDateUTC(date) { - if (date instanceof Date) { - return date.getUTCFullYear() + "-" + padZeros(date.getUTCMonth() + 1, 2) + "-" + padZeros(date.getUTCDate(), 2); - } else { - return null; - } -} - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - -},{"./filter":2,"./input":6}],10:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.SelectionHandle = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _events = require("./events"); - -var _events2 = _interopRequireDefault(_events); - -var _group = require("./group"); - -var _group2 = _interopRequireDefault(_group); - -var _util = require("./util"); - -var util = _interopRequireWildcard(_util); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -/** - * Use this class to read and write (and listen for changes to) the selection - * for a Crosstalk group. This is intended to be used for linked brushing. - * - * If two (or more) `SelectionHandle` instances in the same webpage share the - * same group name, they will share the same state. Setting the selection using - * one `SelectionHandle` instance will result in the `value` property instantly - * changing across the others, and `"change"` event listeners on all instances - * (including the one that initiated the sending) will fire. - * - * @param {string} [group] - The name of the Crosstalk group, or if none, - * null or undefined (or any other falsy value). This can be changed later - * via the [SelectionHandle#setGroup](#setGroup) method. - * @param {Object} [extraInfo] - An object whose properties will be copied to - * the event object whenever an event is emitted. - */ -var SelectionHandle = exports.SelectionHandle = function () { - function SelectionHandle() { - var group = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; - var extraInfo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; - - _classCallCheck(this, SelectionHandle); - - this._eventRelay = new _events2.default(); - this._emitter = new util.SubscriptionTracker(this._eventRelay); - - // Name of the group we're currently tracking, if any. Can change over time. - this._group = null; - // The Var we're currently tracking, if any. Can change over time. - this._var = null; - // The event handler subscription we currently have on var.on("change"). - this._varOnChangeSub = null; - - this._extraInfo = util.extend({ sender: this }, extraInfo); - - this.setGroup(group); - } - - /** - * Changes the Crosstalk group membership of this SelectionHandle. The group - * being switched away from (if any) will not have its selection value - * modified as a result of calling `setGroup`, even if this handle was the - * most recent handle to set the selection of the group. - * - * The group being switched to (if any) will also not have its selection value - * modified as a result of calling `setGroup`. If you want to set the - * selection value of the new group, call `set` explicitly. - * - * @param {string} group - The name of the Crosstalk group, or null (or - * undefined) to clear the group. - */ - - - _createClass(SelectionHandle, [{ - key: "setGroup", - value: function setGroup(group) { - var _this = this; - - // If group is unchanged, do nothing - if (this._group === group) return; - // Treat null, undefined, and other falsy values the same - if (!this._group && !group) return; - - if (this._var) { - this._var.off("change", this._varOnChangeSub); - this._var = null; - this._varOnChangeSub = null; - } - - this._group = group; - - if (group) { - this._var = (0, _group2.default)(group).var("selection"); - var sub = this._var.on("change", function (e) { - _this._eventRelay.trigger("change", e, _this); - }); - this._varOnChangeSub = sub; - } - } - - /** - * Retrieves the current selection for the group represented by this - * `SelectionHandle`. - * - * - If no selection is active, then this value will be falsy. - * - If a selection is active, but no data points are selected, then this - * value will be an empty array. - * - If a selection is active, and data points are selected, then the keys - * of the selected data points will be present in the array. - */ - - }, { - key: "_mergeExtraInfo", - - - /** - * Combines the given `extraInfo` (if any) with the handle's default - * `_extraInfo` (if any). - * @private - */ - value: function _mergeExtraInfo(extraInfo) { - // Important incidental effect: shallow clone is returned - return util.extend({}, this._extraInfo ? this._extraInfo : null, extraInfo ? extraInfo : null); - } - - /** - * Overwrites the current selection for the group, and raises the `"change"` - * event among all of the group's '`SelectionHandle` instances (including - * this one). - * - * @fires SelectionHandle#change - * @param {string[]} selectedKeys - Falsy, empty array, or array of keys (see - * {@link SelectionHandle#value}). - * @param {Object} [extraInfo] - Extra properties to be included on the event - * object that's passed to listeners (in addition to any options that were - * passed into the `SelectionHandle` constructor). - */ - - }, { - key: "set", - value: function set(selectedKeys, extraInfo) { - if (this._var) this._var.set(selectedKeys, this._mergeExtraInfo(extraInfo)); - } - - /** - * Overwrites the current selection for the group, and raises the `"change"` - * event among all of the group's '`SelectionHandle` instances (including - * this one). - * - * @fires SelectionHandle#change - * @param {Object} [extraInfo] - Extra properties to be included on the event - * object that's passed to listeners (in addition to any that were passed - * into the `SelectionHandle` constructor). - */ - - }, { - key: "clear", - value: function clear(extraInfo) { - if (this._var) this.set(void 0, this._mergeExtraInfo(extraInfo)); - } - - /** - * Subscribes to events on this `SelectionHandle`. - * - * @param {string} eventType - Indicates the type of events to listen to. - * Currently, only `"change"` is supported. - * @param {SelectionHandle~listener} listener - The callback function that - * will be invoked when the event occurs. - * @return {string} - A token to pass to {@link SelectionHandle#off} to cancel - * this subscription. - */ - - }, { - key: "on", - value: function on(eventType, listener) { - return this._emitter.on(eventType, listener); - } - - /** - * Cancels event subscriptions created by {@link SelectionHandle#on}. - * - * @param {string} eventType - The type of event to unsubscribe. - * @param {string|SelectionHandle~listener} listener - Either the callback - * function previously passed into {@link SelectionHandle#on}, or the - * string that was returned from {@link SelectionHandle#on}. - */ - - }, { - key: "off", - value: function off(eventType, listener) { - return this._emitter.off(eventType, listener); - } - - /** - * Shuts down the `SelectionHandle` object. - * - * Removes all event listeners that were added through this handle. - */ - - }, { - key: "close", - value: function close() { - this._emitter.removeAllListeners(); - this.setGroup(null); - } - }, { - key: "value", - get: function get() { - return this._var ? this._var.get() : null; - } - }]); - - return SelectionHandle; -}(); - -/** - * @callback SelectionHandle~listener - * @param {Object} event - An object containing details of the event. For - * `"change"` events, this includes the properties `value` (the new - * value of the selection, or `undefined` if no selection is active), - * `oldValue` (the previous value of the selection), and `sender` (the - * `SelectionHandle` instance that made the change). - */ - -/** - * @event SelectionHandle#change - * @type {object} - * @property {object} value - The new value of the selection, or `undefined` - * if no selection is active. - * @property {object} oldValue - The previous value of the selection. - * @property {SelectionHandle} sender - The `SelectionHandle` instance that - * changed the value. - */ - -},{"./events":1,"./group":4,"./util":11}],11:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -exports.extend = extend; -exports.checkSorted = checkSorted; -exports.diffSortedLists = diffSortedLists; -exports.dataframeToD3 = dataframeToD3; - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function extend(target) { - for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - sources[_key - 1] = arguments[_key]; - } - - for (var i = 0; i < sources.length; i++) { - var src = sources[i]; - if (typeof src === "undefined" || src === null) continue; - - for (var key in src) { - if (src.hasOwnProperty(key)) { - target[key] = src[key]; - } - } - } - return target; -} - -function checkSorted(list) { - for (var i = 1; i < list.length; i++) { - if (list[i] <= list[i - 1]) { - throw new Error("List is not sorted or contains duplicate"); - } - } -} - -function diffSortedLists(a, b) { - var i_a = 0; - var i_b = 0; - - if (!a) a = []; - if (!b) b = []; - - var a_only = []; - var b_only = []; - - checkSorted(a); - checkSorted(b); - - while (i_a < a.length && i_b < b.length) { - if (a[i_a] === b[i_b]) { - i_a++; - i_b++; - } else if (a[i_a] < b[i_b]) { - a_only.push(a[i_a++]); - } else { - b_only.push(b[i_b++]); - } - } - - if (i_a < a.length) a_only = a_only.concat(a.slice(i_a)); - if (i_b < b.length) b_only = b_only.concat(b.slice(i_b)); - return { - removed: a_only, - added: b_only - }; -} - -// Convert from wide: { colA: [1,2,3], colB: [4,5,6], ... } -// to long: [ {colA: 1, colB: 4}, {colA: 2, colB: 5}, ... ] -function dataframeToD3(df) { - var names = []; - var length = void 0; - for (var name in df) { - if (df.hasOwnProperty(name)) names.push(name); - if (_typeof(df[name]) !== "object" || typeof df[name].length === "undefined") { - throw new Error("All fields must be arrays"); - } else if (typeof length !== "undefined" && length !== df[name].length) { - throw new Error("All fields must be arrays of the same length"); - } - length = df[name].length; - } - var results = []; - var item = void 0; - for (var row = 0; row < length; row++) { - item = {}; - for (var col = 0; col < names.length; col++) { - item[names[col]] = df[names[col]][row]; - } - results.push(item); - } - return results; -} - -/** - * Keeps track of all event listener additions/removals and lets all active - * listeners be removed with a single operation. - * - * @private - */ - -var SubscriptionTracker = exports.SubscriptionTracker = function () { - function SubscriptionTracker(emitter) { - _classCallCheck(this, SubscriptionTracker); - - this._emitter = emitter; - this._subs = {}; - } - - _createClass(SubscriptionTracker, [{ - key: "on", - value: function on(eventType, listener) { - var sub = this._emitter.on(eventType, listener); - this._subs[sub] = eventType; - return sub; - } - }, { - key: "off", - value: function off(eventType, listener) { - var sub = this._emitter.off(eventType, listener); - if (sub) { - delete this._subs[sub]; - } - return sub; - } - }, { - key: "removeAllListeners", - value: function removeAllListeners() { - var _this = this; - - var current_subs = this._subs; - this._subs = {}; - Object.keys(current_subs).forEach(function (sub) { - _this._emitter.off(current_subs[sub], sub); - }); - } - }]); - - return SubscriptionTracker; -}(); - -},{}],12:[function(require,module,exports){ -(function (global){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -var _events = require("./events"); - -var _events2 = _interopRequireDefault(_events); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var Var = function () { - function Var(group, name, /*optional*/value) { - _classCallCheck(this, Var); - - this._group = group; - this._name = name; - this._value = value; - this._events = new _events2.default(); - } - - _createClass(Var, [{ - key: "get", - value: function get() { - return this._value; - } - }, { - key: "set", - value: function set(value, /*optional*/event) { - if (this._value === value) { - // Do nothing; the value hasn't changed - return; - } - var oldValue = this._value; - this._value = value; - // Alert JavaScript listeners that the value has changed - var evt = {}; - if (event && (typeof event === "undefined" ? "undefined" : _typeof(event)) === "object") { - for (var k in event) { - if (event.hasOwnProperty(k)) evt[k] = event[k]; - } - } - evt.oldValue = oldValue; - evt.value = value; - this._events.trigger("change", evt, this); - - // TODO: Make this extensible, to let arbitrary back-ends know that - // something has changed - if (global.Shiny && global.Shiny.onInputChange) { - global.Shiny.onInputChange(".clientValue-" + (this._group.name !== null ? this._group.name + "-" : "") + this._name, typeof value === "undefined" ? null : value); - } - } - }, { - key: "on", - value: function on(eventType, listener) { - return this._events.on(eventType, listener); - } - }, { - key: "off", - value: function off(eventType, listener) { - return this._events.off(eventType, listener); - } - }]); - - return Var; -}(); - -exports.default = Var; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - -},{"./events":1}]},{},[5]) -//# sourceMappingURL=crosstalk.js.map diff --git a/docs/articles/xportr_files/crosstalk-1.1.1/js/crosstalk.js.map b/docs/articles/xportr_files/crosstalk-1.1.1/js/crosstalk.js.map deleted file mode 100644 index cff94f08..00000000 --- a/docs/articles/xportr_files/crosstalk-1.1.1/js/crosstalk.js.map +++ /dev/null @@ -1,37 +0,0 @@ -{ - "version": 3, - "sources": [ - "node_modules/browser-pack/_prelude.js", - "javascript/src/events.js", - "javascript/src/filter.js", - "javascript/src/filterset.js", - "javascript/src/group.js", - "javascript/src/index.js", - "javascript/src/input.js", - "javascript/src/input_checkboxgroup.js", - "javascript/src/input_selectize.js", - "javascript/src/input_slider.js", - "javascript/src/selection.js", - "javascript/src/util.js", - "javascript/src/var.js" - ], - "names": [], - "mappings": "AAAA;;;;;;;;;;;ICAqB,M;AACnB,oBAAc;AAAA;;AACZ,SAAK,MAAL,GAAc,EAAd;AACA,SAAK,IAAL,GAAY,CAAZ;AACD;;;;uBAEE,S,EAAW,Q,EAAU;AACtB,UAAI,OAAO,KAAK,MAAL,CAAY,SAAZ,CAAX;AACA,UAAI,CAAC,IAAL,EAAW;AACT,eAAO,KAAK,MAAL,CAAY,SAAZ,IAAyB,EAAhC;AACD;AACD,UAAI,MAAM,QAAS,KAAK,IAAL,EAAnB;AACA,WAAK,GAAL,IAAY,QAAZ;AACA,aAAO,GAAP;AACD;;AAED;;;;wBACI,S,EAAW,Q,EAAU;AACvB,UAAI,OAAO,KAAK,MAAL,CAAY,SAAZ,CAAX;AACA,UAAI,OAAO,QAAP,KAAqB,UAAzB,EAAqC;AACnC,aAAK,IAAI,GAAT,IAAgB,IAAhB,EAAsB;AACpB,cAAI,KAAK,cAAL,CAAoB,GAApB,CAAJ,EAA8B;AAC5B,gBAAI,KAAK,GAAL,MAAc,QAAlB,EAA4B;AAC1B,qBAAO,KAAK,GAAL,CAAP;AACA,qBAAO,GAAP;AACD;AACF;AACF;AACD,eAAO,KAAP;AACD,OAVD,MAUO,IAAI,OAAO,QAAP,KAAqB,QAAzB,EAAmC;AACxC,YAAI,QAAQ,KAAK,QAAL,CAAZ,EAA4B;AAC1B,iBAAO,KAAK,QAAL,CAAP;AACA,iBAAO,QAAP;AACD;AACD,eAAO,KAAP;AACD,OANM,MAMA;AACL,cAAM,IAAI,KAAJ,CAAU,8BAAV,CAAN;AACD;AACF;;;4BAEO,S,EAAW,G,EAAK,O,EAAS;AAC/B,UAAI,OAAO,KAAK,MAAL,CAAY,SAAZ,CAAX;AACA,WAAK,IAAI,GAAT,IAAgB,IAAhB,EAAsB;AACpB,YAAI,KAAK,cAAL,CAAoB,GAApB,CAAJ,EAA8B;AAC5B,eAAK,GAAL,EAAU,IAAV,CAAe,OAAf,EAAwB,GAAxB;AACD;AACF;AACF;;;;;;kBA/CkB,M;;;;;;;;;;;;ACArB;;;;AACA;;;;AACA;;;;AACA;;IAAY,I;;;;;;;;AAEZ,SAAS,YAAT,CAAsB,KAAtB,EAA6B;AAC3B,MAAI,QAAQ,MAAM,GAAN,CAAU,WAAV,CAAZ;AACA,MAAI,SAAS,MAAM,GAAN,EAAb;AACA,MAAI,CAAC,MAAL,EAAa;AACX,aAAS,yBAAT;AACA,UAAM,GAAN,CAAU,MAAV;AACD;AACD,SAAO,MAAP;AACD;;AAED,IAAI,KAAK,CAAT;AACA,SAAS,MAAT,GAAkB;AAChB,SAAO,IAAP;AACD;;AAED;;;;;;;;;;;;;;;;;;;;;;;;;IAwBa,Y,WAAA,Y;AACX,wBAAY,KAAZ,EAAmB,SAAnB,EAA8B;AAAA;;AAC5B,SAAK,WAAL,GAAmB,sBAAnB;AACA,SAAK,QAAL,GAAgB,IAAI,KAAK,mBAAT,CAA6B,KAAK,WAAlC,CAAhB;;AAEA;AACA,SAAK,MAAL,GAAc,IAAd;AACA;AACA,SAAK,UAAL,GAAkB,IAAlB;AACA;AACA,SAAK,UAAL,GAAkB,IAAlB;AACA;AACA,SAAK,eAAL,GAAuB,IAAvB;;AAEA,SAAK,UAAL,GAAkB,KAAK,MAAL,CAAY,EAAE,QAAQ,IAAV,EAAZ,EAA8B,SAA9B,CAAlB;;AAEA,SAAK,GAAL,GAAW,WAAW,QAAtB;;AAEA,SAAK,QAAL,CAAc,KAAd;AACD;;AAED;;;;;;;;;;;;;;6BAUS,K,EAAO;AAAA;;AACd;AACA,UAAI,KAAK,MAAL,KAAgB,KAApB,EACE;AACF;AACA,UAAI,CAAC,KAAK,MAAN,IAAgB,CAAC,KAArB,EACE;;AAEF,UAAI,KAAK,UAAT,EAAqB;AACnB,aAAK,UAAL,CAAgB,GAAhB,CAAoB,QAApB,EAA8B,KAAK,eAAnC;AACA,aAAK,KAAL;AACA,aAAK,eAAL,GAAuB,IAAvB;AACA,aAAK,UAAL,GAAkB,IAAlB;AACA,aAAK,UAAL,GAAkB,IAAlB;AACD;;AAED,WAAK,MAAL,GAAc,KAAd;;AAEA,UAAI,KAAJ,EAAW;AACT,gBAAQ,qBAAI,KAAJ,CAAR;AACA,aAAK,UAAL,GAAkB,aAAa,KAAb,CAAlB;AACA,aAAK,UAAL,GAAkB,qBAAI,KAAJ,EAAW,GAAX,CAAe,QAAf,CAAlB;AACA,YAAI,MAAM,KAAK,UAAL,CAAgB,EAAhB,CAAmB,QAAnB,EAA6B,UAAC,CAAD,EAAO;AAC5C,gBAAK,WAAL,CAAiB,OAAjB,CAAyB,QAAzB,EAAmC,CAAnC;AACD,SAFS,CAAV;AAGA,aAAK,eAAL,GAAuB,GAAvB;AACD;AACF;;AAED;;;;;;;;oCAKgB,S,EAAW;AACzB,aAAO,KAAK,MAAL,CAAY,EAAZ,EACL,KAAK,UAAL,GAAkB,KAAK,UAAvB,GAAoC,IAD/B,EAEL,YAAY,SAAZ,GAAwB,IAFnB,CAAP;AAGD;;AAED;;;;;;;4BAIQ;AACN,WAAK,QAAL,CAAc,kBAAd;AACA,WAAK,KAAL;AACA,WAAK,QAAL,CAAc,IAAd;AACD;;AAED;;;;;;;;;;;;0BASM,S,EAAW;AACf,UAAI,CAAC,KAAK,UAAV,EACE;AACF,WAAK,UAAL,CAAgB,KAAhB,CAAsB,KAAK,GAA3B;AACA,WAAK,SAAL,CAAe,SAAf;AACD;;AAED;;;;;;;;;;;;;;;;;;;;wBAiBI,I,EAAM,S,EAAW;AACnB,UAAI,CAAC,KAAK,UAAV,EACE;AACF,WAAK,UAAL,CAAgB,MAAhB,CAAuB,KAAK,GAA5B,EAAiC,IAAjC;AACA,WAAK,SAAL,CAAe,SAAf;AACD;;AAED;;;;;;;;;;AASA;;;;;;;;;;uBAUG,S,EAAW,Q,EAAU;AACtB,aAAO,KAAK,QAAL,CAAc,EAAd,CAAiB,SAAjB,EAA4B,QAA5B,CAAP;AACD;;AAED;;;;;;;;;;;wBAQI,S,EAAW,Q,EAAU;AACvB,aAAO,KAAK,QAAL,CAAc,GAAd,CAAkB,SAAlB,EAA6B,QAA7B,CAAP;AACD;;;8BAES,S,EAAW;AACnB,UAAI,CAAC,KAAK,UAAV,EACE;AACF,WAAK,UAAL,CAAgB,GAAhB,CAAoB,KAAK,UAAL,CAAgB,KAApC,EAA2C,KAAK,eAAL,CAAqB,SAArB,CAA3C;AACD;;AAED;;;;;;;;;;;wBApCmB;AACjB,aAAO,KAAK,UAAL,GAAkB,KAAK,UAAL,CAAgB,KAAlC,GAA0C,IAAjD;AACD;;;;;;AA6CH;;;;;;;;;;;;;;;;;;;ACzNA;;;;AAEA,SAAS,iBAAT,CAA2B,CAA3B,EAA8B,CAA9B,EAAiC;AAC/B,MAAI,MAAM,CAAV,EAAa;AACX,WAAO,CAAP;AACD,GAFD,MAEO,IAAI,IAAI,CAAR,EAAW;AAChB,WAAO,CAAC,CAAR;AACD,GAFM,MAEA,IAAI,IAAI,CAAR,EAAW;AAChB,WAAO,CAAP;AACD;AACF;;AAED;;;;IAGqB,S;AACnB,uBAAc;AAAA;;AACZ,SAAK,KAAL;AACD;;;;4BAEO;AACN;AACA,WAAK,QAAL,GAAgB,EAAhB;AACA;AACA,WAAK,KAAL,GAAa,EAAb;AACA,WAAK,MAAL,GAAc,IAAd;AACA,WAAK,cAAL,GAAsB,CAAtB;AACD;;;2BAMM,Q,EAAU,I,EAAM;AACrB,UAAI,SAAS,IAAb,EAAmB;AACjB,eAAO,KAAK,KAAL,CAAW,CAAX,CAAP,CADiB,CACK;AACtB,aAAK,IAAL,CAAU,iBAAV;AACD;;AAJoB,6BAME,2BAAgB,KAAK,QAAL,CAAc,QAAd,CAAhB,EAAyC,IAAzC,CANF;AAAA,UAMhB,KANgB,oBAMhB,KANgB;AAAA,UAMT,OANS,oBAMT,OANS;;AAOrB,WAAK,QAAL,CAAc,QAAd,IAA0B,IAA1B;;AAEA,WAAK,IAAI,IAAI,CAAb,EAAgB,IAAI,MAAM,MAA1B,EAAkC,GAAlC,EAAuC;AACrC,aAAK,KAAL,CAAW,MAAM,CAAN,CAAX,IAAuB,CAAC,KAAK,KAAL,CAAW,MAAM,CAAN,CAAX,KAAwB,CAAzB,IAA8B,CAArD;AACD;AACD,WAAK,IAAI,KAAI,CAAb,EAAgB,KAAI,QAAQ,MAA5B,EAAoC,IAApC,EAAyC;AACvC,aAAK,KAAL,CAAW,QAAQ,EAAR,CAAX;AACD;;AAED,WAAK,YAAL,CAAkB,IAAlB;AACD;;AAED;;;;;;;;mCAKmC;AAAA,UAAtB,IAAsB,uEAAf,KAAK,QAAU;;AACjC,UAAI,cAAc,OAAO,IAAP,CAAY,KAAK,QAAjB,EAA2B,MAA7C;AACA,UAAI,gBAAgB,CAApB,EAAuB;AACrB,aAAK,MAAL,GAAc,IAAd;AACD,OAFD,MAEO;AACL,aAAK,MAAL,GAAc,EAAd;AACA,aAAK,IAAI,IAAI,CAAb,EAAgB,IAAI,KAAK,MAAzB,EAAiC,GAAjC,EAAsC;AACpC,cAAI,QAAQ,KAAK,KAAL,CAAW,KAAK,CAAL,CAAX,CAAZ;AACA,cAAI,UAAU,WAAd,EAA2B;AACzB,iBAAK,MAAL,CAAY,IAAZ,CAAiB,KAAK,CAAL,CAAjB;AACD;AACF;AACF;AACF;;;0BAEK,Q,EAAU;AACd,UAAI,OAAO,KAAK,QAAL,CAAc,QAAd,CAAP,KAAoC,WAAxC,EAAqD;AACnD;AACD;;AAED,UAAI,OAAO,KAAK,QAAL,CAAc,QAAd,CAAX;AACA,UAAI,CAAC,IAAL,EAAW;AACT,eAAO,EAAP;AACD;;AAED,WAAK,IAAI,IAAI,CAAb,EAAgB,IAAI,KAAK,MAAzB,EAAiC,GAAjC,EAAsC;AACpC,aAAK,KAAL,CAAW,KAAK,CAAL,CAAX;AACD;AACD,aAAO,KAAK,QAAL,CAAc,QAAd,CAAP;;AAEA,WAAK,YAAL;AACD;;;wBA3DW;AACV,aAAO,KAAK,MAAZ;AACD;;;wBA2Dc;AACb,UAAI,UAAU,OAAO,IAAP,CAAY,KAAK,KAAjB,CAAd;AACA,cAAQ,IAAR,CAAa,iBAAb;AACA,aAAO,OAAP;AACD;;;;;;kBA/EkB,S;;;;;;;;;;;;;;kBCRG,K;;AAPxB;;;;;;;;AAEA;AACA;AACA,OAAO,kBAAP,GAA4B,OAAO,kBAAP,IAA6B,EAAzD;AACA,IAAI,SAAS,OAAO,kBAApB;;AAEe,SAAS,KAAT,CAAe,SAAf,EAA0B;AACvC,MAAI,aAAa,OAAO,SAAP,KAAsB,QAAvC,EAAiD;AAC/C,QAAI,CAAC,OAAO,cAAP,CAAsB,SAAtB,CAAL,EAAuC;AACrC,aAAO,SAAP,IAAoB,IAAI,KAAJ,CAAU,SAAV,CAApB;AACD;AACD,WAAO,OAAO,SAAP,CAAP;AACD,GALD,MAKO,IAAI,QAAO,SAAP,yCAAO,SAAP,OAAsB,QAAtB,IAAkC,UAAU,KAA5C,IAAqD,UAAU,GAAnE,EAAwE;AAC7E;AACA,WAAO,SAAP;AACD,GAHM,MAGA,IAAI,MAAM,OAAN,CAAc,SAAd,KACP,UAAU,MAAV,IAAoB,CADb,IAEP,OAAO,UAAU,CAAV,CAAP,KAAyB,QAFtB,EAEgC;AACrC,WAAO,MAAM,UAAU,CAAV,CAAN,CAAP;AACD,GAJM,MAIA;AACL,UAAM,IAAI,KAAJ,CAAU,4BAAV,CAAN;AACD;AACF;;IAEK,K;AACJ,iBAAY,IAAZ,EAAkB;AAAA;;AAChB,SAAK,IAAL,GAAY,IAAZ;AACA,SAAK,KAAL,GAAa,EAAb;AACD;;;;yBAEG,I,EAAM;AACR,UAAI,CAAC,IAAD,IAAS,OAAO,IAAP,KAAiB,QAA9B,EAAwC;AACtC,cAAM,IAAI,KAAJ,CAAU,kBAAV,CAAN;AACD;;AAED,UAAI,CAAC,KAAK,KAAL,CAAW,cAAX,CAA0B,IAA1B,CAAL,EACE,KAAK,KAAL,CAAW,IAAX,IAAmB,kBAAQ,IAAR,EAAc,IAAd,CAAnB;AACF,aAAO,KAAK,KAAL,CAAW,IAAX,CAAP;AACD;;;wBAEG,I,EAAM;AACR,UAAI,CAAC,IAAD,IAAS,OAAO,IAAP,KAAiB,QAA9B,EAAwC;AACtC,cAAM,IAAI,KAAJ,CAAU,kBAAV,CAAN;AACD;;AAED,aAAO,KAAK,KAAL,CAAW,cAAX,CAA0B,IAA1B,CAAP;AACD;;;;;;;;;;;;;;;;AC/CH;;;;AACA;;AACA;;AACA;;AACA;;AACA;;AACA;;;;AAEA,IAAM,eAAe,qBAAM,SAAN,CAArB;;AAEA,SAAS,IAAT,CAAc,IAAd,EAAoB;AAClB,SAAO,aAAa,GAAb,CAAiB,IAAjB,CAAP;AACD;;AAED,SAAS,GAAT,CAAa,IAAb,EAAmB;AACjB,SAAO,aAAa,GAAb,CAAiB,IAAjB,CAAP;AACD;;AAED,IAAI,OAAO,KAAX,EAAkB;AAChB,SAAO,KAAP,CAAa,uBAAb,CAAqC,qBAArC,EAA4D,UAAS,OAAT,EAAkB;AAC5E,QAAI,OAAO,QAAQ,KAAf,KAA0B,QAA9B,EAAwC;AACtC,2BAAM,QAAQ,KAAd,EAAqB,GAArB,CAAyB,QAAQ,IAAjC,EAAuC,GAAvC,CAA2C,QAAQ,KAAnD;AACD,KAFD,MAEO;AACL,WAAK,QAAQ,IAAb,EAAmB,GAAnB,CAAuB,QAAQ,KAA/B;AACD;AACF,GAND;AAOD;;AAED,IAAM,YAAY;AAChB,wBADgB;AAEhB,OAAK,IAFW;AAGhB,OAAK,GAHW;AAIhB,6CAJgB;AAKhB,oCALgB;AAMhB;AANgB,CAAlB;;AASA;;;kBAGe,S;;AACf,OAAO,SAAP,GAAmB,SAAnB;;;;;;;;;;;QCrCgB,Q,GAAA,Q;QAWA,I,GAAA,I;AAfhB,IAAI,IAAI,OAAO,MAAf;;AAEA,IAAI,WAAW,EAAf;;AAEO,SAAS,QAAT,CAAkB,GAAlB,EAAuB;AAC5B,WAAS,IAAI,SAAb,IAA0B,GAA1B;AACA,MAAI,OAAO,QAAP,IAAmB,OAAO,QAAP,CAAgB,UAAhB,KAA+B,UAAtD,EAAkE;AAChE,MAAE,YAAM;AACN;AACD,KAFD;AAGD,GAJD,MAIO,IAAI,OAAO,QAAX,EAAqB;AAC1B,eAAW,IAAX,EAAiB,GAAjB;AACD;AACF;;AAEM,SAAS,IAAT,GAAgB;AACrB,SAAO,IAAP,CAAY,QAAZ,EAAsB,OAAtB,CAA8B,UAAS,SAAT,EAAoB;AAChD,QAAI,UAAU,SAAS,SAAT,CAAd;AACA,MAAE,MAAM,QAAQ,SAAhB,EAA2B,GAA3B,CAA+B,wBAA/B,EAAyD,IAAzD,CAA8D,UAAS,CAAT,EAAY,EAAZ,EAAgB;AAC5E,mBAAa,OAAb,EAAsB,EAAtB;AACD,KAFD;AAGD,GALD;AAMD;;AAED;AACA,SAAS,OAAT,CAAiB,GAAjB,EAAsB;AACpB,SAAO,IAAI,OAAJ,CAAY,uCAAZ,EAAqD,MAArD,CAAP;AACD;;AAED,SAAS,MAAT,CAAgB,EAAhB,EAAoB;AAClB,MAAI,MAAM,EAAE,EAAF,CAAV;AACA,SAAO,IAAP,CAAY,QAAZ,EAAsB,OAAtB,CAA8B,UAAS,SAAT,EAAoB;AAChD,QAAI,IAAI,QAAJ,CAAa,SAAb,KAA2B,CAAC,IAAI,QAAJ,CAAa,uBAAb,CAAhC,EAAuE;AACrE,UAAI,UAAU,SAAS,SAAT,CAAd;AACA,mBAAa,OAAb,EAAsB,EAAtB;AACD;AACF,GALD;AAMD;;AAED,SAAS,YAAT,CAAsB,OAAtB,EAA+B,EAA/B,EAAmC;AACjC,MAAI,SAAS,EAAE,EAAF,EAAM,IAAN,CAAW,+CAA+C,QAAQ,GAAG,EAAX,CAA/C,GAAgE,IAA3E,CAAb;AACA,MAAI,OAAO,KAAK,KAAL,CAAW,OAAO,CAAP,EAAU,SAArB,CAAX;;AAEA,MAAI,WAAW,QAAQ,OAAR,CAAgB,EAAhB,EAAoB,IAApB,CAAf;AACA,IAAE,EAAF,EAAM,IAAN,CAAW,oBAAX,EAAiC,QAAjC;AACA,IAAE,EAAF,EAAM,QAAN,CAAe,uBAAf;AACD;;AAED,IAAI,OAAO,KAAX,EAAkB;AAChB,MAAI,eAAe,IAAI,OAAO,KAAP,CAAa,YAAjB,EAAnB;AACA,MAAI,KAAI,OAAO,MAAf;AACA,KAAE,MAAF,CAAS,YAAT,EAAuB;AACrB,UAAM,cAAS,KAAT,EAAgB;AACpB,aAAO,GAAE,KAAF,EAAS,IAAT,CAAc,kBAAd,CAAP;AACD,KAHoB;AAIrB,gBAAY,oBAAS,EAAT,EAAa;AACvB,UAAI,CAAC,GAAE,EAAF,EAAM,QAAN,CAAe,uBAAf,CAAL,EAA8C;AAC5C,eAAO,EAAP;AACD;AACF,KARoB;AASrB,WAAO,eAAS,EAAT,EAAa;AAClB,aAAO,GAAG,EAAV;AACD,KAXoB;AAYrB,cAAU,kBAAS,EAAT,EAAa,CAEtB,CAdoB;AAerB,cAAU,kBAAS,EAAT,EAAa,KAAb,EAAoB,CAE7B,CAjBoB;AAkBrB,oBAAgB,wBAAS,EAAT,EAAa,IAAb,EAAmB,CAElC,CApBoB;AAqBrB,eAAW,mBAAS,EAAT,EAAa,QAAb,EAAuB;AAChC,SAAE,EAAF,EAAM,IAAN,CAAW,oBAAX,EAAiC,MAAjC;AACD,KAvBoB;AAwBrB,iBAAa,qBAAS,EAAT,EAAa;AACxB,SAAE,EAAF,EAAM,IAAN,CAAW,oBAAX,EAAiC,OAAjC;AACD;AA1BoB,GAAvB;AA4BA,SAAO,KAAP,CAAa,aAAb,CAA2B,QAA3B,CAAoC,YAApC,EAAkD,wBAAlD;AACD;;;;;;;;AChFD;;IAAY,K;;AACZ;;;;AAEA,IAAI,IAAI,OAAO,MAAf;;AAEA,MAAM,QAAN,CAAe;AACb,aAAW,+BADE;;AAGb,WAAS,iBAAS,EAAT,EAAa,IAAb,EAAmB;AAC1B;;;;AAIA,QAAI,WAAW,yBAAiB,KAAK,KAAtB,CAAf;;AAEA,QAAI,sBAAJ;AACA,QAAI,MAAM,EAAE,EAAF,CAAV;AACA,QAAI,EAAJ,CAAO,QAAP,EAAiB,wBAAjB,EAA2C,YAAW;AACpD,UAAI,UAAU,IAAI,IAAJ,CAAS,gCAAT,CAAd;AACA,UAAI,QAAQ,MAAR,KAAmB,CAAvB,EAA0B;AACxB,wBAAgB,IAAhB;AACA,iBAAS,KAAT;AACD,OAHD,MAGO;AACL,YAAI,OAAO,EAAX;AACA,gBAAQ,IAAR,CAAa,YAAW;AACtB,eAAK,GAAL,CAAS,KAAK,KAAd,EAAqB,OAArB,CAA6B,UAAS,GAAT,EAAc;AACzC,iBAAK,GAAL,IAAY,IAAZ;AACD,WAFD;AAGD,SAJD;AAKA,YAAI,WAAW,OAAO,IAAP,CAAY,IAAZ,CAAf;AACA,iBAAS,IAAT;AACA,wBAAgB,QAAhB;AACA,iBAAS,GAAT,CAAa,QAAb;AACD;AACF,KAjBD;;AAmBA,WAAO;AACL,eAAS,mBAAW;AAClB,iBAAS,KAAT;AACD,OAHI;AAIL,cAAQ,kBAAW;AACjB,YAAI,aAAJ,EACE,SAAS,GAAT,CAAa,aAAb;AACH;AAPI,KAAP;AASD;AAxCY,CAAf;;;;;;;;ACLA;;IAAY,K;;AACZ;;IAAY,I;;AACZ;;;;AAEA,IAAI,IAAI,OAAO,MAAf;;AAEA,MAAM,QAAN,CAAe;AACb,aAAW,wBADE;;AAGb,WAAS,iBAAS,EAAT,EAAa,IAAb,EAAmB;AAC1B;;;;;;AAMA,QAAI,QAAQ,CAAC,EAAC,OAAO,EAAR,EAAY,OAAO,OAAnB,EAAD,CAAZ;AACA,QAAI,QAAQ,KAAK,aAAL,CAAmB,KAAK,KAAxB,CAAZ;AACA,QAAI,OAAO;AACT,eAAS,MAAM,MAAN,CAAa,KAAb,CADA;AAET,kBAAY,OAFH;AAGT,kBAAY,OAHH;AAIT,mBAAa;AAJJ,KAAX;;AAOA,QAAI,SAAS,EAAE,EAAF,EAAM,IAAN,CAAW,QAAX,EAAqB,CAArB,CAAb;;AAEA,QAAI,YAAY,EAAE,MAAF,EAAU,SAAV,CAAoB,IAApB,EAA0B,CAA1B,EAA6B,SAA7C;;AAEA,QAAI,WAAW,yBAAiB,KAAK,KAAtB,CAAf;;AAEA,QAAI,sBAAJ;AACA,cAAU,EAAV,CAAa,QAAb,EAAuB,YAAW;AAChC,UAAI,UAAU,KAAV,CAAgB,MAAhB,KAA2B,CAA/B,EAAkC;AAChC,wBAAgB,IAAhB;AACA,iBAAS,KAAT;AACD,OAHD,MAGO;AACL,YAAI,OAAO,EAAX;AACA,kBAAU,KAAV,CAAgB,OAAhB,CAAwB,UAAS,KAAT,EAAgB;AACtC,eAAK,GAAL,CAAS,KAAT,EAAgB,OAAhB,CAAwB,UAAS,GAAT,EAAc;AACpC,iBAAK,GAAL,IAAY,IAAZ;AACD,WAFD;AAGD,SAJD;AAKA,YAAI,WAAW,OAAO,IAAP,CAAY,IAAZ,CAAf;AACA,iBAAS,IAAT;AACA,wBAAgB,QAAhB;AACA,iBAAS,GAAT,CAAa,QAAb;AACD;AACF,KAhBD;;AAkBA,WAAO;AACL,eAAS,mBAAW;AAClB,iBAAS,KAAT;AACD,OAHI;AAIL,cAAQ,kBAAW;AACjB,YAAI,aAAJ,EACE,SAAS,GAAT,CAAa,aAAb;AACH;AAPI,KAAP;AASD;AArDY,CAAf;;;;;;;;;;ACNA;;IAAY,K;;AACZ;;;;AAEA,IAAI,IAAI,OAAO,MAAf;AACA,IAAI,WAAW,OAAO,QAAtB;;AAEA,MAAM,QAAN,CAAe;AACb,aAAW,wBADE;;AAGb,WAAS,iBAAS,EAAT,EAAa,IAAb,EAAmB;AAC1B;;;;AAIA,QAAI,WAAW,yBAAiB,KAAK,KAAtB,CAAf;;AAEA,QAAI,OAAO,EAAX;AACA,QAAI,MAAM,EAAE,EAAF,EAAM,IAAN,CAAW,OAAX,CAAV;AACA,QAAI,WAAW,IAAI,IAAJ,CAAS,WAAT,CAAf;AACA,QAAI,aAAa,IAAI,IAAJ,CAAS,aAAT,CAAjB;AACA,QAAI,QAAQ,IAAI,IAAJ,CAAS,OAAT,CAAZ;AACA,QAAI,sBAAJ;;AAEA;AACA,QAAI,aAAa,MAAjB,EAAyB;AACvB,sBAAgB,SAAS,GAAT,EAAhB;AACA,WAAK,QAAL,GAAgB,UAAS,GAAT,EAAc;AAC5B,eAAO,cAAc,UAAd,EAA0B,IAAI,IAAJ,CAAS,GAAT,CAA1B,CAAP;AACD,OAFD;AAID,KAND,MAMO,IAAI,aAAa,UAAjB,EAA6B;AAClC,UAAI,WAAW,IAAI,IAAJ,CAAS,UAAT,CAAf;AACA,UAAI,QAAJ,EACE,gBAAgB,SAAS,QAAT,CAAkB,QAAlB,CAAhB,CADF,KAGE,gBAAgB,QAAhB;;AAEF,WAAK,QAAL,GAAgB,UAAS,GAAT,EAAc;AAC5B,eAAO,cAAc,UAAd,EAA0B,IAAI,IAAJ,CAAS,GAAT,CAA1B,CAAP;AACD,OAFD;AAGD,KAVM,MAUA,IAAI,aAAa,QAAjB,EAA2B;AAChC,UAAI,OAAO,KAAP,KAAiB,WAArB,EACE,KAAK,QAAL,GAAgB,UAAS,GAAT,EAAc;AAC5B,YAAI,SAAS,KAAK,GAAL,CAAS,EAAT,EAAa,KAAb,CAAb;AACA,eAAO,KAAK,KAAL,CAAW,MAAM,MAAjB,IAA2B,MAAlC;AACD,OAHD;AAIH;;AAED,QAAI,cAAJ,CAAmB,IAAnB;;AAEA,aAAS,QAAT,GAAoB;AAClB,UAAI,SAAS,IAAI,IAAJ,CAAS,gBAAT,EAA2B,MAAxC;;AAEA;AACA,UAAI,gBAAJ;AACA,UAAI,WAAW,IAAI,IAAJ,CAAS,WAAT,CAAf;AACA,UAAI,aAAa,MAAjB,EAAyB;AACvB,kBAAU,iBAAS,GAAT,EAAc;AACtB,iBAAO,cAAc,IAAI,IAAJ,CAAS,CAAC,GAAV,CAAd,CAAP;AACD,SAFD;AAGD,OAJD,MAIO,IAAI,aAAa,UAAjB,EAA6B;AAClC,kBAAU,iBAAS,GAAT,EAAc;AACtB;AACA,iBAAO,CAAC,GAAD,GAAO,IAAd;AACD,SAHD;AAID,OALM,MAKA;AACL,kBAAU,iBAAS,GAAT,EAAc;AAAE,iBAAO,CAAC,GAAR;AAAc,SAAxC;AACD;;AAED,UAAI,IAAI,IAAJ,CAAS,gBAAT,EAA2B,OAA3B,CAAmC,IAAnC,KAA4C,QAAhD,EAA0D;AACxD,eAAO,CAAC,QAAQ,OAAO,IAAf,CAAD,EAAuB,QAAQ,OAAO,EAAf,CAAvB,CAAP;AACD,OAFD,MAEO;AACL,eAAO,QAAQ,OAAO,IAAf,CAAP;AACD;AACF;;AAED,QAAI,gBAAgB,IAApB;;AAEA,QAAI,EAAJ,CAAO,6BAAP,EAAsC,UAAS,KAAT,EAAgB;AACpD,UAAI,CAAC,IAAI,IAAJ,CAAS,UAAT,CAAD,IAAyB,CAAC,IAAI,IAAJ,CAAS,WAAT,CAA9B,EAAqD;AAAA,wBAClC,UADkC;AAAA;AAAA,YAC9C,IAD8C;AAAA,YACxC,EADwC;;AAEnD,YAAI,OAAO,EAAX;AACA,aAAK,IAAI,IAAI,CAAb,EAAgB,IAAI,KAAK,MAAL,CAAY,MAAhC,EAAwC,GAAxC,EAA6C;AAC3C,cAAI,MAAM,KAAK,MAAL,CAAY,CAAZ,CAAV;AACA,cAAI,OAAO,IAAP,IAAe,OAAO,EAA1B,EAA8B;AAC5B,iBAAK,IAAL,CAAU,KAAK,IAAL,CAAU,CAAV,CAAV;AACD;AACF;AACD,aAAK,IAAL;AACA,iBAAS,GAAT,CAAa,IAAb;AACA,wBAAgB,IAAhB;AACD;AACF,KAdD;;AAiBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,WAAO;AACL,eAAS,mBAAW;AAClB,iBAAS,KAAT;AACD,OAHI;AAIL,cAAQ,kBAAW;AACjB,YAAI,aAAJ,EACE,SAAS,GAAT,CAAa,aAAb;AACH;AAPI,KAAP;AASD;AApHY,CAAf;;AAwHA;AACA,SAAS,QAAT,CAAkB,CAAlB,EAAqB,MAArB,EAA6B;AAC3B,MAAI,MAAM,EAAE,QAAF,EAAV;AACA,SAAO,IAAI,MAAJ,GAAa,MAApB;AACE,UAAM,MAAM,GAAZ;AADF,GAEA,OAAO,GAAP;AACD;;AAED;AACA;AACA,SAAS,aAAT,CAAuB,IAAvB,EAA6B;AAC3B,MAAI,gBAAgB,IAApB,EAA0B;AACxB,WAAO,KAAK,cAAL,KAAwB,GAAxB,GACA,SAAS,KAAK,WAAL,KAAmB,CAA5B,EAA+B,CAA/B,CADA,GACoC,GADpC,GAEA,SAAS,KAAK,UAAL,EAAT,EAA4B,CAA5B,CAFP;AAID,GALD,MAKO;AACL,WAAO,IAAP;AACD;AACF;;;;;;;;;;;;;;ACjJD;;;;AACA;;;;AACA;;IAAY,I;;;;;;;;AAEZ;;;;;;;;;;;;;;;;IAgBa,e,WAAA,e;AAEX,6BAA4C;AAAA,QAAhC,KAAgC,uEAAxB,IAAwB;AAAA,QAAlB,SAAkB,uEAAN,IAAM;;AAAA;;AAC1C,SAAK,WAAL,GAAmB,sBAAnB;AACA,SAAK,QAAL,GAAgB,IAAI,KAAK,mBAAT,CAA6B,KAAK,WAAlC,CAAhB;;AAEA;AACA,SAAK,MAAL,GAAc,IAAd;AACA;AACA,SAAK,IAAL,GAAY,IAAZ;AACA;AACA,SAAK,eAAL,GAAuB,IAAvB;;AAEA,SAAK,UAAL,GAAkB,KAAK,MAAL,CAAY,EAAE,QAAQ,IAAV,EAAZ,EAA8B,SAA9B,CAAlB;;AAEA,SAAK,QAAL,CAAc,KAAd;AACD;;AAED;;;;;;;;;;;;;;;;;6BAaS,K,EAAO;AAAA;;AACd;AACA,UAAI,KAAK,MAAL,KAAgB,KAApB,EACE;AACF;AACA,UAAI,CAAC,KAAK,MAAN,IAAgB,CAAC,KAArB,EACE;;AAEF,UAAI,KAAK,IAAT,EAAe;AACb,aAAK,IAAL,CAAU,GAAV,CAAc,QAAd,EAAwB,KAAK,eAA7B;AACA,aAAK,IAAL,GAAY,IAAZ;AACA,aAAK,eAAL,GAAuB,IAAvB;AACD;;AAED,WAAK,MAAL,GAAc,KAAd;;AAEA,UAAI,KAAJ,EAAW;AACT,aAAK,IAAL,GAAY,qBAAI,KAAJ,EAAW,GAAX,CAAe,WAAf,CAAZ;AACA,YAAI,MAAM,KAAK,IAAL,CAAU,EAAV,CAAa,QAAb,EAAuB,UAAC,CAAD,EAAO;AACtC,gBAAK,WAAL,CAAiB,OAAjB,CAAyB,QAAzB,EAAmC,CAAnC;AACD,SAFS,CAAV;AAGA,aAAK,eAAL,GAAuB,GAAvB;AACD;AACF;;AAED;;;;;;;;;;;;;;;AAcA;;;;;oCAKgB,S,EAAW;AACzB;AACA,aAAO,KAAK,MAAL,CAAY,EAAZ,EACL,KAAK,UAAL,GAAkB,KAAK,UAAvB,GAAoC,IAD/B,EAEL,YAAY,SAAZ,GAAwB,IAFnB,CAAP;AAGD;;AAED;;;;;;;;;;;;;;;wBAYI,Y,EAAc,S,EAAW;AAC3B,UAAI,KAAK,IAAT,EACE,KAAK,IAAL,CAAU,GAAV,CAAc,YAAd,EAA4B,KAAK,eAAL,CAAqB,SAArB,CAA5B;AACH;;AAED;;;;;;;;;;;;;0BAUM,S,EAAW;AACf,UAAI,KAAK,IAAT,EACE,KAAK,GAAL,CAAS,KAAK,CAAd,EAAiB,KAAK,eAAL,CAAqB,SAArB,CAAjB;AACH;;AAED;;;;;;;;;;;;;uBAUG,S,EAAW,Q,EAAU;AACtB,aAAO,KAAK,QAAL,CAAc,EAAd,CAAiB,SAAjB,EAA4B,QAA5B,CAAP;AACD;;AAED;;;;;;;;;;;wBAQI,S,EAAW,Q,EAAU;AACvB,aAAO,KAAK,QAAL,CAAc,GAAd,CAAkB,SAAlB,EAA6B,QAA7B,CAAP;AACD;;AAED;;;;;;;;4BAKQ;AACN,WAAK,QAAL,CAAc,kBAAd;AACA,WAAK,QAAL,CAAc,IAAd;AACD;;;wBAlFW;AACV,aAAO,KAAK,IAAL,GAAY,KAAK,IAAL,CAAU,GAAV,EAAZ,GAA8B,IAArC;AACD;;;;;;AAmFH;;;;;;;;;AASA;;;;;;;;;;;;;;;;;;;;;QCpLgB,M,GAAA,M;QAeA,W,GAAA,W;QAQA,e,GAAA,e;QAoCA,a,GAAA,a;;;;AA3DT,SAAS,MAAT,CAAgB,MAAhB,EAAoC;AAAA,oCAAT,OAAS;AAAT,WAAS;AAAA;;AACzC,OAAK,IAAI,IAAI,CAAb,EAAgB,IAAI,QAAQ,MAA5B,EAAoC,GAApC,EAAyC;AACvC,QAAI,MAAM,QAAQ,CAAR,CAAV;AACA,QAAI,OAAO,GAAP,KAAgB,WAAhB,IAA+B,QAAQ,IAA3C,EACE;;AAEF,SAAK,IAAI,GAAT,IAAgB,GAAhB,EAAqB;AACnB,UAAI,IAAI,cAAJ,CAAmB,GAAnB,CAAJ,EAA6B;AAC3B,eAAO,GAAP,IAAc,IAAI,GAAJ,CAAd;AACD;AACF;AACF;AACD,SAAO,MAAP;AACD;;AAEM,SAAS,WAAT,CAAqB,IAArB,EAA2B;AAChC,OAAK,IAAI,IAAI,CAAb,EAAgB,IAAI,KAAK,MAAzB,EAAiC,GAAjC,EAAsC;AACpC,QAAI,KAAK,CAAL,KAAW,KAAK,IAAE,CAAP,CAAf,EAA0B;AACxB,YAAM,IAAI,KAAJ,CAAU,0CAAV,CAAN;AACD;AACF;AACF;;AAEM,SAAS,eAAT,CAAyB,CAAzB,EAA4B,CAA5B,EAA+B;AACpC,MAAI,MAAM,CAAV;AACA,MAAI,MAAM,CAAV;;AAEA,MAAI,CAAC,CAAL,EAAQ,IAAI,EAAJ;AACR,MAAI,CAAC,CAAL,EAAQ,IAAI,EAAJ;;AAER,MAAI,SAAS,EAAb;AACA,MAAI,SAAS,EAAb;;AAEA,cAAY,CAAZ;AACA,cAAY,CAAZ;;AAEA,SAAO,MAAM,EAAE,MAAR,IAAkB,MAAM,EAAE,MAAjC,EAAyC;AACvC,QAAI,EAAE,GAAF,MAAW,EAAE,GAAF,CAAf,EAAuB;AACrB;AACA;AACD,KAHD,MAGO,IAAI,EAAE,GAAF,IAAS,EAAE,GAAF,CAAb,EAAqB;AAC1B,aAAO,IAAP,CAAY,EAAE,KAAF,CAAZ;AACD,KAFM,MAEA;AACL,aAAO,IAAP,CAAY,EAAE,KAAF,CAAZ;AACD;AACF;;AAED,MAAI,MAAM,EAAE,MAAZ,EACE,SAAS,OAAO,MAAP,CAAc,EAAE,KAAF,CAAQ,GAAR,CAAd,CAAT;AACF,MAAI,MAAM,EAAE,MAAZ,EACE,SAAS,OAAO,MAAP,CAAc,EAAE,KAAF,CAAQ,GAAR,CAAd,CAAT;AACF,SAAO;AACL,aAAS,MADJ;AAEL,WAAO;AAFF,GAAP;AAID;;AAED;AACA;AACO,SAAS,aAAT,CAAuB,EAAvB,EAA2B;AAChC,MAAI,QAAQ,EAAZ;AACA,MAAI,eAAJ;AACA,OAAK,IAAI,IAAT,IAAiB,EAAjB,EAAqB;AACnB,QAAI,GAAG,cAAH,CAAkB,IAAlB,CAAJ,EACE,MAAM,IAAN,CAAW,IAAX;AACF,QAAI,QAAO,GAAG,IAAH,CAAP,MAAqB,QAArB,IAAiC,OAAO,GAAG,IAAH,EAAS,MAAhB,KAA4B,WAAjE,EAA8E;AAC5E,YAAM,IAAI,KAAJ,CAAU,2BAAV,CAAN;AACD,KAFD,MAEO,IAAI,OAAO,MAAP,KAAmB,WAAnB,IAAkC,WAAW,GAAG,IAAH,EAAS,MAA1D,EAAkE;AACvE,YAAM,IAAI,KAAJ,CAAU,8CAAV,CAAN;AACD;AACD,aAAS,GAAG,IAAH,EAAS,MAAlB;AACD;AACD,MAAI,UAAU,EAAd;AACA,MAAI,aAAJ;AACA,OAAK,IAAI,MAAM,CAAf,EAAkB,MAAM,MAAxB,EAAgC,KAAhC,EAAuC;AACrC,WAAO,EAAP;AACA,SAAK,IAAI,MAAM,CAAf,EAAkB,MAAM,MAAM,MAA9B,EAAsC,KAAtC,EAA6C;AAC3C,WAAK,MAAM,GAAN,CAAL,IAAmB,GAAG,MAAM,GAAN,CAAH,EAAe,GAAf,CAAnB;AACD;AACD,YAAQ,IAAR,CAAa,IAAb;AACD;AACD,SAAO,OAAP;AACD;;AAED;;;;;;;IAMa,mB,WAAA,mB;AACX,+BAAY,OAAZ,EAAqB;AAAA;;AACnB,SAAK,QAAL,GAAgB,OAAhB;AACA,SAAK,KAAL,GAAa,EAAb;AACD;;;;uBAEE,S,EAAW,Q,EAAU;AACtB,UAAI,MAAM,KAAK,QAAL,CAAc,EAAd,CAAiB,SAAjB,EAA4B,QAA5B,CAAV;AACA,WAAK,KAAL,CAAW,GAAX,IAAkB,SAAlB;AACA,aAAO,GAAP;AACD;;;wBAEG,S,EAAW,Q,EAAU;AACvB,UAAI,MAAM,KAAK,QAAL,CAAc,GAAd,CAAkB,SAAlB,EAA6B,QAA7B,CAAV;AACA,UAAI,GAAJ,EAAS;AACP,eAAO,KAAK,KAAL,CAAW,GAAX,CAAP;AACD;AACD,aAAO,GAAP;AACD;;;yCAEoB;AAAA;;AACnB,UAAI,eAAe,KAAK,KAAxB;AACA,WAAK,KAAL,GAAa,EAAb;AACA,aAAO,IAAP,CAAY,YAAZ,EAA0B,OAA1B,CAAkC,UAAC,GAAD,EAAS;AACzC,cAAK,QAAL,CAAc,GAAd,CAAkB,aAAa,GAAb,CAAlB,EAAqC,GAArC;AACD,OAFD;AAGD;;;;;;;;;;;;;;;;;;ACpHH;;;;;;;;IAEqB,G;AACnB,eAAY,KAAZ,EAAmB,IAAnB,EAAyB,YAAa,KAAtC,EAA6C;AAAA;;AAC3C,SAAK,MAAL,GAAc,KAAd;AACA,SAAK,KAAL,GAAa,IAAb;AACA,SAAK,MAAL,GAAc,KAAd;AACA,SAAK,OAAL,GAAe,sBAAf;AACD;;;;0BAEK;AACJ,aAAO,KAAK,MAAZ;AACD;;;wBAEG,K,EAAO,YAAa,K,EAAO;AAC7B,UAAI,KAAK,MAAL,KAAgB,KAApB,EAA2B;AACzB;AACA;AACD;AACD,UAAI,WAAW,KAAK,MAApB;AACA,WAAK,MAAL,GAAc,KAAd;AACA;AACA,UAAI,MAAM,EAAV;AACA,UAAI,SAAS,QAAO,KAAP,yCAAO,KAAP,OAAkB,QAA/B,EAAyC;AACvC,aAAK,IAAI,CAAT,IAAc,KAAd,EAAqB;AACnB,cAAI,MAAM,cAAN,CAAqB,CAArB,CAAJ,EACE,IAAI,CAAJ,IAAS,MAAM,CAAN,CAAT;AACH;AACF;AACD,UAAI,QAAJ,GAAe,QAAf;AACA,UAAI,KAAJ,GAAY,KAAZ;AACA,WAAK,OAAL,CAAa,OAAb,CAAqB,QAArB,EAA+B,GAA/B,EAAoC,IAApC;;AAEA;AACA;AACA,UAAI,OAAO,KAAP,IAAgB,OAAO,KAAP,CAAa,aAAjC,EAAgD;AAC9C,eAAO,KAAP,CAAa,aAAb,CACE,mBACG,KAAK,MAAL,CAAY,IAAZ,KAAqB,IAArB,GAA4B,KAAK,MAAL,CAAY,IAAZ,GAAmB,GAA/C,GAAqD,EADxD,IAEE,KAAK,KAHT,EAIE,OAAO,KAAP,KAAkB,WAAlB,GAAgC,IAAhC,GAAuC,KAJzC;AAMD;AACF;;;uBAEE,S,EAAW,Q,EAAU;AACtB,aAAO,KAAK,OAAL,CAAa,EAAb,CAAgB,SAAhB,EAA2B,QAA3B,CAAP;AACD;;;wBAEG,S,EAAW,Q,EAAU;AACvB,aAAO,KAAK,OAAL,CAAa,GAAb,CAAiB,SAAjB,EAA4B,QAA5B,CAAP;AACD;;;;;;kBAjDkB,G", - "file": "generated.js", - "sourceRoot": "", - "sourcesContent": [ - "(function(){function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o {\n this._eventRelay.trigger(\"change\", e, this);\n });\n this._varOnChangeSub = sub;\n }\n }\n\n /**\n * Combine the given `extraInfo` (if any) with the handle's default\n * `_extraInfo` (if any).\n * @private\n */\n _mergeExtraInfo(extraInfo) {\n return util.extend({},\n this._extraInfo ? this._extraInfo : null,\n extraInfo ? extraInfo : null);\n }\n\n /**\n * Close the handle. This clears this handle's contribution to the filter set,\n * and unsubscribes all event listeners.\n */\n close() {\n this._emitter.removeAllListeners();\n this.clear();\n this.setGroup(null);\n }\n\n /**\n * Clear this handle's contribution to the filter set.\n *\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any options that were\n * passed into the `FilterHandle` constructor).\n * \n * @fires FilterHandle#change\n */\n clear(extraInfo) {\n if (!this._filterSet)\n return;\n this._filterSet.clear(this._id);\n this._onChange(extraInfo);\n }\n\n /**\n * Set this handle's contribution to the filter set. This array should consist\n * of the keys of the rows that _should_ be displayed; any keys that are not\n * present in the array will be considered _filtered out_. Note that multiple\n * `FilterHandle` instances in the group may each contribute an array of keys,\n * and only those keys that appear in _all_ of the arrays make it through the\n * filter.\n *\n * @param {string[]} keys - Empty array, or array of keys. To clear the\n * filter, don't pass an empty array; instead, use the\n * {@link FilterHandle#clear} method.\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any options that were\n * passed into the `FilterHandle` constructor).\n * \n * @fires FilterHandle#change\n */\n set(keys, extraInfo) {\n if (!this._filterSet)\n return;\n this._filterSet.update(this._id, keys);\n this._onChange(extraInfo);\n }\n\n /**\n * @return {string[]|null} - Either: 1) an array of keys that made it through\n * all of the `FilterHandle` instances, or, 2) `null`, which means no filter\n * is being applied (all data should be displayed).\n */\n get filteredKeys() {\n return this._filterSet ? this._filterSet.value : null;\n }\n\n /**\n * Subscribe to events on this `FilterHandle`.\n *\n * @param {string} eventType - Indicates the type of events to listen to.\n * Currently, only `\"change\"` is supported.\n * @param {FilterHandle~listener} listener - The callback function that\n * will be invoked when the event occurs.\n * @return {string} - A token to pass to {@link FilterHandle#off} to cancel\n * this subscription.\n */\n on(eventType, listener) {\n return this._emitter.on(eventType, listener);\n }\n\n /**\n * Cancel event subscriptions created by {@link FilterHandle#on}.\n *\n * @param {string} eventType - The type of event to unsubscribe.\n * @param {string|FilterHandle~listener} listener - Either the callback\n * function previously passed into {@link FilterHandle#on}, or the\n * string that was returned from {@link FilterHandle#on}.\n */\n off(eventType, listener) {\n return this._emitter.off(eventType, listener);\n }\n\n _onChange(extraInfo) {\n if (!this._filterSet)\n return;\n this._filterVar.set(this._filterSet.value, this._mergeExtraInfo(extraInfo));\n }\n\n /**\n * @callback FilterHandle~listener\n * @param {Object} event - An object containing details of the event. For\n * `\"change\"` events, this includes the properties `value` (the new\n * value of the filter set, or `null` if no filter set is active),\n * `oldValue` (the previous value of the filter set), and `sender` (the\n * `FilterHandle` instance that made the change).\n */\n\n}\n\n/**\n * @event FilterHandle#change\n * @type {object}\n * @property {object} value - The new value of the filter set, or `null`\n * if no filter set is active.\n * @property {object} oldValue - The previous value of the filter set.\n * @property {FilterHandle} sender - The `FilterHandle` instance that\n * changed the value.\n */\n", - "import { diffSortedLists } from \"./util\";\n\nfunction naturalComparator(a, b) {\n if (a === b) {\n return 0;\n } else if (a < b) {\n return -1;\n } else if (a > b) {\n return 1;\n }\n}\n\n/**\n * @private\n */\nexport default class FilterSet {\n constructor() {\n this.reset();\n }\n\n reset() {\n // Key: handle ID, Value: array of selected keys, or null\n this._handles = {};\n // Key: key string, Value: count of handles that include it\n this._keys = {};\n this._value = null;\n this._activeHandles = 0;\n }\n\n get value() {\n return this._value;\n }\n\n update(handleId, keys) {\n if (keys !== null) {\n keys = keys.slice(0); // clone before sorting\n keys.sort(naturalComparator);\n }\n\n let {added, removed} = diffSortedLists(this._handles[handleId], keys);\n this._handles[handleId] = keys;\n\n for (let i = 0; i < added.length; i++) {\n this._keys[added[i]] = (this._keys[added[i]] || 0) + 1;\n }\n for (let i = 0; i < removed.length; i++) {\n this._keys[removed[i]]--;\n }\n\n this._updateValue(keys);\n }\n\n /**\n * @param {string[]} keys Sorted array of strings that indicate\n * a superset of possible keys.\n * @private\n */\n _updateValue(keys = this._allKeys) {\n let handleCount = Object.keys(this._handles).length;\n if (handleCount === 0) {\n this._value = null;\n } else {\n this._value = [];\n for (let i = 0; i < keys.length; i++) {\n let count = this._keys[keys[i]];\n if (count === handleCount) {\n this._value.push(keys[i]);\n }\n }\n }\n }\n\n clear(handleId) {\n if (typeof(this._handles[handleId]) === \"undefined\") {\n return;\n }\n\n let keys = this._handles[handleId];\n if (!keys) {\n keys = [];\n }\n\n for (let i = 0; i < keys.length; i++) {\n this._keys[keys[i]]--;\n }\n delete this._handles[handleId];\n\n this._updateValue();\n }\n\n get _allKeys() {\n let allKeys = Object.keys(this._keys);\n allKeys.sort(naturalComparator);\n return allKeys;\n }\n}\n", - "import Var from \"./var\";\n\n// Use a global so that multiple copies of crosstalk.js can be loaded and still\n// have groups behave as singletons across all copies.\nglobal.__crosstalk_groups = global.__crosstalk_groups || {};\nlet groups = global.__crosstalk_groups;\n\nexport default function group(groupName) {\n if (groupName && typeof(groupName) === \"string\") {\n if (!groups.hasOwnProperty(groupName)) {\n groups[groupName] = new Group(groupName);\n }\n return groups[groupName];\n } else if (typeof(groupName) === \"object\" && groupName._vars && groupName.var) {\n // Appears to already be a group object\n return groupName;\n } else if (Array.isArray(groupName) &&\n groupName.length == 1 &&\n typeof(groupName[0]) === \"string\") {\n return group(groupName[0]);\n } else {\n throw new Error(\"Invalid groupName argument\");\n }\n}\n\nclass Group {\n constructor(name) {\n this.name = name;\n this._vars = {};\n }\n\n var(name) {\n if (!name || typeof(name) !== \"string\") {\n throw new Error(\"Invalid var name\");\n }\n\n if (!this._vars.hasOwnProperty(name))\n this._vars[name] = new Var(this, name);\n return this._vars[name];\n }\n\n has(name) {\n if (!name || typeof(name) !== \"string\") {\n throw new Error(\"Invalid var name\");\n }\n\n return this._vars.hasOwnProperty(name);\n }\n}\n", - "import group from \"./group\";\nimport { SelectionHandle } from \"./selection\";\nimport { FilterHandle } from \"./filter\";\nimport { bind } from \"./input\";\nimport \"./input_selectize\";\nimport \"./input_checkboxgroup\";\nimport \"./input_slider\";\n\nconst defaultGroup = group(\"default\");\n\nfunction var_(name) {\n return defaultGroup.var(name);\n}\n\nfunction has(name) {\n return defaultGroup.has(name);\n}\n\nif (global.Shiny) {\n global.Shiny.addCustomMessageHandler(\"update-client-value\", function(message) {\n if (typeof(message.group) === \"string\") {\n group(message.group).var(message.name).set(message.value);\n } else {\n var_(message.name).set(message.value);\n }\n });\n}\n\nconst crosstalk = {\n group: group,\n var: var_,\n has: has,\n SelectionHandle: SelectionHandle,\n FilterHandle: FilterHandle,\n bind: bind\n};\n\n/**\n * @namespace crosstalk\n */\nexport default crosstalk;\nglobal.crosstalk = crosstalk;\n", - "let $ = global.jQuery;\n\nlet bindings = {};\n\nexport function register(reg) {\n bindings[reg.className] = reg;\n if (global.document && global.document.readyState !== \"complete\") {\n $(() => {\n bind();\n });\n } else if (global.document) {\n setTimeout(bind, 100);\n }\n}\n\nexport function bind() {\n Object.keys(bindings).forEach(function(className) {\n let binding = bindings[className];\n $(\".\" + binding.className).not(\".crosstalk-input-bound\").each(function(i, el) {\n bindInstance(binding, el);\n });\n });\n}\n\n// Escape jQuery identifier\nfunction $escape(val) {\n return val.replace(/([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^`{|}~])/g, \"\\\\$1\");\n}\n\nfunction bindEl(el) {\n let $el = $(el);\n Object.keys(bindings).forEach(function(className) {\n if ($el.hasClass(className) && !$el.hasClass(\"crosstalk-input-bound\")) {\n let binding = bindings[className];\n bindInstance(binding, el);\n }\n });\n}\n\nfunction bindInstance(binding, el) {\n let jsonEl = $(el).find(\"script[type='application/json'][data-for='\" + $escape(el.id) + \"']\");\n let data = JSON.parse(jsonEl[0].innerText);\n\n let instance = binding.factory(el, data);\n $(el).data(\"crosstalk-instance\", instance);\n $(el).addClass(\"crosstalk-input-bound\");\n}\n\nif (global.Shiny) {\n let inputBinding = new global.Shiny.InputBinding();\n let $ = global.jQuery;\n $.extend(inputBinding, {\n find: function(scope) {\n return $(scope).find(\".crosstalk-input\");\n },\n initialize: function(el) {\n if (!$(el).hasClass(\"crosstalk-input-bound\")) {\n bindEl(el);\n }\n },\n getId: function(el) {\n return el.id;\n },\n getValue: function(el) {\n\n },\n setValue: function(el, value) {\n\n },\n receiveMessage: function(el, data) {\n\n },\n subscribe: function(el, callback) {\n $(el).data(\"crosstalk-instance\").resume();\n },\n unsubscribe: function(el) {\n $(el).data(\"crosstalk-instance\").suspend();\n }\n });\n global.Shiny.inputBindings.register(inputBinding, \"crosstalk.inputBinding\");\n}\n", - "import * as input from \"./input\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\n\ninput.register({\n className: \"crosstalk-input-checkboxgroup\",\n\n factory: function(el, data) {\n /*\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n let ctHandle = new FilterHandle(data.group);\n\n let lastKnownKeys;\n let $el = $(el);\n $el.on(\"change\", \"input[type='checkbox']\", function() {\n let checked = $el.find(\"input[type='checkbox']:checked\");\n if (checked.length === 0) {\n lastKnownKeys = null;\n ctHandle.clear();\n } else {\n let keys = {};\n checked.each(function() {\n data.map[this.value].forEach(function(key) {\n keys[key] = true;\n });\n });\n let keyArray = Object.keys(keys);\n keyArray.sort();\n lastKnownKeys = keyArray;\n ctHandle.set(keyArray);\n }\n });\n\n return {\n suspend: function() {\n ctHandle.clear();\n },\n resume: function() {\n if (lastKnownKeys)\n ctHandle.set(lastKnownKeys);\n }\n };\n }\n});\n", - "import * as input from \"./input\";\nimport * as util from \"./util\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\n\ninput.register({\n className: \"crosstalk-input-select\",\n\n factory: function(el, data) {\n /*\n * items: {value: [...], label: [...]}\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n\n let first = [{value: \"\", label: \"(All)\"}];\n let items = util.dataframeToD3(data.items);\n let opts = {\n options: first.concat(items),\n valueField: \"value\",\n labelField: \"label\",\n searchField: \"label\"\n };\n\n let select = $(el).find(\"select\")[0];\n\n let selectize = $(select).selectize(opts)[0].selectize;\n\n let ctHandle = new FilterHandle(data.group);\n\n let lastKnownKeys;\n selectize.on(\"change\", function() {\n if (selectize.items.length === 0) {\n lastKnownKeys = null;\n ctHandle.clear();\n } else {\n let keys = {};\n selectize.items.forEach(function(group) {\n data.map[group].forEach(function(key) {\n keys[key] = true;\n });\n });\n let keyArray = Object.keys(keys);\n keyArray.sort();\n lastKnownKeys = keyArray;\n ctHandle.set(keyArray);\n }\n });\n\n return {\n suspend: function() {\n ctHandle.clear();\n },\n resume: function() {\n if (lastKnownKeys)\n ctHandle.set(lastKnownKeys);\n }\n };\n }\n});\n", - "import * as input from \"./input\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\nlet strftime = global.strftime;\n\ninput.register({\n className: \"crosstalk-input-slider\",\n\n factory: function(el, data) {\n /*\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n let ctHandle = new FilterHandle(data.group);\n\n let opts = {};\n let $el = $(el).find(\"input\");\n let dataType = $el.data(\"data-type\");\n let timeFormat = $el.data(\"time-format\");\n let round = $el.data(\"round\");\n let timeFormatter;\n\n // Set up formatting functions\n if (dataType === \"date\") {\n timeFormatter = strftime.utc();\n opts.prettify = function(num) {\n return timeFormatter(timeFormat, new Date(num));\n };\n\n } else if (dataType === \"datetime\") {\n let timezone = $el.data(\"timezone\");\n if (timezone)\n timeFormatter = strftime.timezone(timezone);\n else\n timeFormatter = strftime;\n\n opts.prettify = function(num) {\n return timeFormatter(timeFormat, new Date(num));\n };\n } else if (dataType === \"number\") {\n if (typeof round !== \"undefined\")\n opts.prettify = function(num) {\n let factor = Math.pow(10, round);\n return Math.round(num * factor) / factor;\n };\n }\n\n $el.ionRangeSlider(opts);\n\n function getValue() {\n let result = $el.data(\"ionRangeSlider\").result;\n\n // Function for converting numeric value from slider to appropriate type.\n let convert;\n let dataType = $el.data(\"data-type\");\n if (dataType === \"date\") {\n convert = function(val) {\n return formatDateUTC(new Date(+val));\n };\n } else if (dataType === \"datetime\") {\n convert = function(val) {\n // Convert ms to s\n return +val / 1000;\n };\n } else {\n convert = function(val) { return +val; };\n }\n\n if ($el.data(\"ionRangeSlider\").options.type === \"double\") {\n return [convert(result.from), convert(result.to)];\n } else {\n return convert(result.from);\n }\n }\n\n let lastKnownKeys = null;\n\n $el.on(\"change.crosstalkSliderInput\", function(event) {\n if (!$el.data(\"updating\") && !$el.data(\"animating\")) {\n let [from, to] = getValue();\n let keys = [];\n for (let i = 0; i < data.values.length; i++) {\n let val = data.values[i];\n if (val >= from && val <= to) {\n keys.push(data.keys[i]);\n }\n }\n keys.sort();\n ctHandle.set(keys);\n lastKnownKeys = keys;\n }\n });\n\n\n // let $el = $(el);\n // $el.on(\"change\", \"input[type=\"checkbox\"]\", function() {\n // let checked = $el.find(\"input[type=\"checkbox\"]:checked\");\n // if (checked.length === 0) {\n // ctHandle.clear();\n // } else {\n // let keys = {};\n // checked.each(function() {\n // data.map[this.value].forEach(function(key) {\n // keys[key] = true;\n // });\n // });\n // let keyArray = Object.keys(keys);\n // keyArray.sort();\n // ctHandle.set(keyArray);\n // }\n // });\n\n return {\n suspend: function() {\n ctHandle.clear();\n },\n resume: function() {\n if (lastKnownKeys)\n ctHandle.set(lastKnownKeys);\n }\n };\n }\n});\n\n\n// Convert a number to a string with leading zeros\nfunction padZeros(n, digits) {\n let str = n.toString();\n while (str.length < digits)\n str = \"0\" + str;\n return str;\n}\n\n// Given a Date object, return a string in yyyy-mm-dd format, using the\n// UTC date. This may be a day off from the date in the local time zone.\nfunction formatDateUTC(date) {\n if (date instanceof Date) {\n return date.getUTCFullYear() + \"-\" +\n padZeros(date.getUTCMonth()+1, 2) + \"-\" +\n padZeros(date.getUTCDate(), 2);\n\n } else {\n return null;\n }\n}\n", - "import Events from \"./events\";\nimport grp from \"./group\";\nimport * as util from \"./util\";\n\n/**\n * Use this class to read and write (and listen for changes to) the selection\n * for a Crosstalk group. This is intended to be used for linked brushing.\n *\n * If two (or more) `SelectionHandle` instances in the same webpage share the\n * same group name, they will share the same state. Setting the selection using\n * one `SelectionHandle` instance will result in the `value` property instantly\n * changing across the others, and `\"change\"` event listeners on all instances\n * (including the one that initiated the sending) will fire.\n *\n * @param {string} [group] - The name of the Crosstalk group, or if none,\n * null or undefined (or any other falsy value). This can be changed later\n * via the [SelectionHandle#setGroup](#setGroup) method.\n * @param {Object} [extraInfo] - An object whose properties will be copied to\n * the event object whenever an event is emitted.\n */\nexport class SelectionHandle {\n\n constructor(group = null, extraInfo = null) {\n this._eventRelay = new Events();\n this._emitter = new util.SubscriptionTracker(this._eventRelay);\n\n // Name of the group we're currently tracking, if any. Can change over time.\n this._group = null;\n // The Var we're currently tracking, if any. Can change over time.\n this._var = null;\n // The event handler subscription we currently have on var.on(\"change\").\n this._varOnChangeSub = null;\n\n this._extraInfo = util.extend({ sender: this }, extraInfo);\n\n this.setGroup(group);\n }\n\n /**\n * Changes the Crosstalk group membership of this SelectionHandle. The group\n * being switched away from (if any) will not have its selection value\n * modified as a result of calling `setGroup`, even if this handle was the\n * most recent handle to set the selection of the group.\n *\n * The group being switched to (if any) will also not have its selection value\n * modified as a result of calling `setGroup`. If you want to set the\n * selection value of the new group, call `set` explicitly.\n *\n * @param {string} group - The name of the Crosstalk group, or null (or\n * undefined) to clear the group.\n */\n setGroup(group) {\n // If group is unchanged, do nothing\n if (this._group === group)\n return;\n // Treat null, undefined, and other falsy values the same\n if (!this._group && !group)\n return;\n\n if (this._var) {\n this._var.off(\"change\", this._varOnChangeSub);\n this._var = null;\n this._varOnChangeSub = null;\n }\n\n this._group = group;\n\n if (group) {\n this._var = grp(group).var(\"selection\");\n let sub = this._var.on(\"change\", (e) => {\n this._eventRelay.trigger(\"change\", e, this);\n });\n this._varOnChangeSub = sub;\n }\n }\n\n /**\n * Retrieves the current selection for the group represented by this\n * `SelectionHandle`.\n *\n * - If no selection is active, then this value will be falsy.\n * - If a selection is active, but no data points are selected, then this\n * value will be an empty array.\n * - If a selection is active, and data points are selected, then the keys\n * of the selected data points will be present in the array.\n */\n get value() {\n return this._var ? this._var.get() : null;\n }\n\n /**\n * Combines the given `extraInfo` (if any) with the handle's default\n * `_extraInfo` (if any).\n * @private\n */\n _mergeExtraInfo(extraInfo) {\n // Important incidental effect: shallow clone is returned\n return util.extend({},\n this._extraInfo ? this._extraInfo : null,\n extraInfo ? extraInfo : null);\n }\n\n /**\n * Overwrites the current selection for the group, and raises the `\"change\"`\n * event among all of the group's '`SelectionHandle` instances (including\n * this one).\n *\n * @fires SelectionHandle#change\n * @param {string[]} selectedKeys - Falsy, empty array, or array of keys (see\n * {@link SelectionHandle#value}).\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any options that were\n * passed into the `SelectionHandle` constructor).\n */\n set(selectedKeys, extraInfo) {\n if (this._var)\n this._var.set(selectedKeys, this._mergeExtraInfo(extraInfo));\n }\n\n /**\n * Overwrites the current selection for the group, and raises the `\"change\"`\n * event among all of the group's '`SelectionHandle` instances (including\n * this one).\n *\n * @fires SelectionHandle#change\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any that were passed\n * into the `SelectionHandle` constructor).\n */\n clear(extraInfo) {\n if (this._var)\n this.set(void 0, this._mergeExtraInfo(extraInfo));\n }\n\n /**\n * Subscribes to events on this `SelectionHandle`.\n *\n * @param {string} eventType - Indicates the type of events to listen to.\n * Currently, only `\"change\"` is supported.\n * @param {SelectionHandle~listener} listener - The callback function that\n * will be invoked when the event occurs.\n * @return {string} - A token to pass to {@link SelectionHandle#off} to cancel\n * this subscription.\n */\n on(eventType, listener) {\n return this._emitter.on(eventType, listener);\n }\n\n /**\n * Cancels event subscriptions created by {@link SelectionHandle#on}.\n *\n * @param {string} eventType - The type of event to unsubscribe.\n * @param {string|SelectionHandle~listener} listener - Either the callback\n * function previously passed into {@link SelectionHandle#on}, or the\n * string that was returned from {@link SelectionHandle#on}.\n */\n off(eventType, listener) {\n return this._emitter.off(eventType, listener);\n }\n\n /**\n * Shuts down the `SelectionHandle` object.\n *\n * Removes all event listeners that were added through this handle.\n */\n close() {\n this._emitter.removeAllListeners();\n this.setGroup(null);\n }\n}\n\n/**\n * @callback SelectionHandle~listener\n * @param {Object} event - An object containing details of the event. For\n * `\"change\"` events, this includes the properties `value` (the new\n * value of the selection, or `undefined` if no selection is active),\n * `oldValue` (the previous value of the selection), and `sender` (the\n * `SelectionHandle` instance that made the change).\n */\n\n/**\n * @event SelectionHandle#change\n * @type {object}\n * @property {object} value - The new value of the selection, or `undefined`\n * if no selection is active.\n * @property {object} oldValue - The previous value of the selection.\n * @property {SelectionHandle} sender - The `SelectionHandle` instance that\n * changed the value.\n */\n", - "export function extend(target, ...sources) {\n for (let i = 0; i < sources.length; i++) {\n let src = sources[i];\n if (typeof(src) === \"undefined\" || src === null)\n continue;\n\n for (let key in src) {\n if (src.hasOwnProperty(key)) {\n target[key] = src[key];\n }\n }\n }\n return target;\n}\n\nexport function checkSorted(list) {\n for (let i = 1; i < list.length; i++) {\n if (list[i] <= list[i-1]) {\n throw new Error(\"List is not sorted or contains duplicate\");\n }\n }\n}\n\nexport function diffSortedLists(a, b) {\n let i_a = 0;\n let i_b = 0;\n\n if (!a) a = [];\n if (!b) b = [];\n\n let a_only = [];\n let b_only = [];\n\n checkSorted(a);\n checkSorted(b);\n\n while (i_a < a.length && i_b < b.length) {\n if (a[i_a] === b[i_b]) {\n i_a++;\n i_b++;\n } else if (a[i_a] < b[i_b]) {\n a_only.push(a[i_a++]);\n } else {\n b_only.push(b[i_b++]);\n }\n }\n\n if (i_a < a.length)\n a_only = a_only.concat(a.slice(i_a));\n if (i_b < b.length)\n b_only = b_only.concat(b.slice(i_b));\n return {\n removed: a_only,\n added: b_only\n };\n}\n\n// Convert from wide: { colA: [1,2,3], colB: [4,5,6], ... }\n// to long: [ {colA: 1, colB: 4}, {colA: 2, colB: 5}, ... ]\nexport function dataframeToD3(df) {\n let names = [];\n let length;\n for (let name in df) {\n if (df.hasOwnProperty(name))\n names.push(name);\n if (typeof(df[name]) !== \"object\" || typeof(df[name].length) === \"undefined\") {\n throw new Error(\"All fields must be arrays\");\n } else if (typeof(length) !== \"undefined\" && length !== df[name].length) {\n throw new Error(\"All fields must be arrays of the same length\");\n }\n length = df[name].length;\n }\n let results = [];\n let item;\n for (let row = 0; row < length; row++) {\n item = {};\n for (let col = 0; col < names.length; col++) {\n item[names[col]] = df[names[col]][row];\n }\n results.push(item);\n }\n return results;\n}\n\n/**\n * Keeps track of all event listener additions/removals and lets all active\n * listeners be removed with a single operation.\n *\n * @private\n */\nexport class SubscriptionTracker {\n constructor(emitter) {\n this._emitter = emitter;\n this._subs = {};\n }\n\n on(eventType, listener) {\n let sub = this._emitter.on(eventType, listener);\n this._subs[sub] = eventType;\n return sub;\n }\n\n off(eventType, listener) {\n let sub = this._emitter.off(eventType, listener);\n if (sub) {\n delete this._subs[sub];\n }\n return sub;\n }\n\n removeAllListeners() {\n let current_subs = this._subs;\n this._subs = {};\n Object.keys(current_subs).forEach((sub) => {\n this._emitter.off(current_subs[sub], sub);\n });\n }\n}\n", - "import Events from \"./events\";\n\nexport default class Var {\n constructor(group, name, /*optional*/ value) {\n this._group = group;\n this._name = name;\n this._value = value;\n this._events = new Events();\n }\n\n get() {\n return this._value;\n }\n\n set(value, /*optional*/ event) {\n if (this._value === value) {\n // Do nothing; the value hasn't changed\n return;\n }\n let oldValue = this._value;\n this._value = value;\n // Alert JavaScript listeners that the value has changed\n let evt = {};\n if (event && typeof(event) === \"object\") {\n for (let k in event) {\n if (event.hasOwnProperty(k))\n evt[k] = event[k];\n }\n }\n evt.oldValue = oldValue;\n evt.value = value;\n this._events.trigger(\"change\", evt, this);\n\n // TODO: Make this extensible, to let arbitrary back-ends know that\n // something has changed\n if (global.Shiny && global.Shiny.onInputChange) {\n global.Shiny.onInputChange(\n \".clientValue-\" +\n (this._group.name !== null ? this._group.name + \"-\" : \"\") +\n this._name,\n typeof(value) === \"undefined\" ? null : value\n );\n }\n }\n\n on(eventType, listener) {\n return this._events.on(eventType, listener);\n }\n\n off(eventType, listener) {\n return this._events.off(eventType, listener);\n }\n}\n" - ] -} \ No newline at end of file diff --git a/docs/articles/xportr_files/crosstalk-1.1.1/js/crosstalk.min.js b/docs/articles/xportr_files/crosstalk-1.1.1/js/crosstalk.min.js deleted file mode 100644 index b7ec0ac9..00000000 --- a/docs/articles/xportr_files/crosstalk-1.1.1/js/crosstalk.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!function o(u,a,l){function s(n,e){if(!a[n]){if(!u[n]){var t="function"==typeof require&&require;if(!e&&t)return t(n,!0);if(f)return f(n,!0);var r=new Error("Cannot find module '"+n+"'");throw r.code="MODULE_NOT_FOUND",r}var i=a[n]={exports:{}};u[n][0].call(i.exports,function(e){var t=u[n][1][e];return s(t||e)},i,i.exports,o,u,a,l)}return a[n].exports}for(var f="function"==typeof require&&require,e=0;e?@[\\\]^`{|}~])/g,"\\$1")+"']"),r=JSON.parse(n[0].innerText),i=e.factory(t,r);o(t).data("crosstalk-instance",i),o(t).addClass("crosstalk-input-bound")}if(t.Shiny){var e=new t.Shiny.InputBinding,u=t.jQuery;u.extend(e,{find:function(e){return u(e).find(".crosstalk-input")},initialize:function(e){var t,n;u(e).hasClass("crosstalk-input-bound")||(n=o(t=e),Object.keys(r).forEach(function(e){n.hasClass(e)&&!n.hasClass("crosstalk-input-bound")&&i(r[e],t)}))},getId:function(e){return e.id},getValue:function(e){},setValue:function(e,t){},receiveMessage:function(e,t){},subscribe:function(e,t){u(e).data("crosstalk-instance").resume()},unsubscribe:function(e){u(e).data("crosstalk-instance").suspend()}}),t.Shiny.inputBindings.register(e,"crosstalk.inputBinding")}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],7:[function(r,e,t){(function(e){"use strict";var t=function(e){{if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}}(r("./input")),n=r("./filter");var a=e.jQuery;t.register({className:"crosstalk-input-checkboxgroup",factory:function(e,r){var i=new n.FilterHandle(r.group),o=void 0,u=a(e);return u.on("change","input[type='checkbox']",function(){var e=u.find("input[type='checkbox']:checked");if(0===e.length)o=null,i.clear();else{var t={};e.each(function(){r.map[this.value].forEach(function(e){t[e]=!0})});var n=Object.keys(t);n.sort(),o=n,i.set(n)}}),{suspend:function(){i.clear()},resume:function(){o&&i.set(o)}}}})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./filter":2,"./input":6}],8:[function(r,e,t){(function(e){"use strict";var t=n(r("./input")),l=n(r("./util")),s=r("./filter");function n(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}var f=e.jQuery;t.register({className:"crosstalk-input-select",factory:function(e,n){var t=l.dataframeToD3(n.items),r={options:[{value:"",label:"(All)"}].concat(t),valueField:"value",labelField:"label",searchField:"label"},i=f(e).find("select")[0],o=f(i).selectize(r)[0].selectize,u=new s.FilterHandle(n.group),a=void 0;return o.on("change",function(){if(0===o.items.length)a=null,u.clear();else{var t={};o.items.forEach(function(e){n.map[e].forEach(function(e){t[e]=!0})});var e=Object.keys(t);e.sort(),a=e,u.set(e)}}),{suspend:function(){u.clear()},resume:function(){a&&u.set(a)}}}})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./filter":2,"./input":6,"./util":11}],9:[function(n,e,t){(function(e){"use strict";var d=function(e,t){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return function(e,t){var n=[],r=!0,i=!1,o=void 0;try{for(var u,a=e[Symbol.iterator]();!(r=(u=a.next()).done)&&(n.push(u.value),!t||n.length!==t);r=!0);}catch(e){i=!0,o=e}finally{try{!r&&a.return&&a.return()}finally{if(i)throw o}}return n}(e,t);throw new TypeError("Invalid attempt to destructure non-iterable instance")},t=function(e){{if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}}(n("./input")),a=n("./filter");var v=e.jQuery,p=e.strftime;function y(e,t){for(var n=e.toString();n.length {\n this._eventRelay.trigger(\"change\", e, this);\n });\n this._varOnChangeSub = sub;\n }\n }\n\n /**\n * Combine the given `extraInfo` (if any) with the handle's default\n * `_extraInfo` (if any).\n * @private\n */\n _mergeExtraInfo(extraInfo) {\n return util.extend({},\n this._extraInfo ? this._extraInfo : null,\n extraInfo ? extraInfo : null);\n }\n\n /**\n * Close the handle. This clears this handle's contribution to the filter set,\n * and unsubscribes all event listeners.\n */\n close() {\n this._emitter.removeAllListeners();\n this.clear();\n this.setGroup(null);\n }\n\n /**\n * Clear this handle's contribution to the filter set.\n *\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any options that were\n * passed into the `FilterHandle` constructor).\n * \n * @fires FilterHandle#change\n */\n clear(extraInfo) {\n if (!this._filterSet)\n return;\n this._filterSet.clear(this._id);\n this._onChange(extraInfo);\n }\n\n /**\n * Set this handle's contribution to the filter set. This array should consist\n * of the keys of the rows that _should_ be displayed; any keys that are not\n * present in the array will be considered _filtered out_. Note that multiple\n * `FilterHandle` instances in the group may each contribute an array of keys,\n * and only those keys that appear in _all_ of the arrays make it through the\n * filter.\n *\n * @param {string[]} keys - Empty array, or array of keys. To clear the\n * filter, don't pass an empty array; instead, use the\n * {@link FilterHandle#clear} method.\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any options that were\n * passed into the `FilterHandle` constructor).\n * \n * @fires FilterHandle#change\n */\n set(keys, extraInfo) {\n if (!this._filterSet)\n return;\n this._filterSet.update(this._id, keys);\n this._onChange(extraInfo);\n }\n\n /**\n * @return {string[]|null} - Either: 1) an array of keys that made it through\n * all of the `FilterHandle` instances, or, 2) `null`, which means no filter\n * is being applied (all data should be displayed).\n */\n get filteredKeys() {\n return this._filterSet ? this._filterSet.value : null;\n }\n\n /**\n * Subscribe to events on this `FilterHandle`.\n *\n * @param {string} eventType - Indicates the type of events to listen to.\n * Currently, only `\"change\"` is supported.\n * @param {FilterHandle~listener} listener - The callback function that\n * will be invoked when the event occurs.\n * @return {string} - A token to pass to {@link FilterHandle#off} to cancel\n * this subscription.\n */\n on(eventType, listener) {\n return this._emitter.on(eventType, listener);\n }\n\n /**\n * Cancel event subscriptions created by {@link FilterHandle#on}.\n *\n * @param {string} eventType - The type of event to unsubscribe.\n * @param {string|FilterHandle~listener} listener - Either the callback\n * function previously passed into {@link FilterHandle#on}, or the\n * string that was returned from {@link FilterHandle#on}.\n */\n off(eventType, listener) {\n return this._emitter.off(eventType, listener);\n }\n\n _onChange(extraInfo) {\n if (!this._filterSet)\n return;\n this._filterVar.set(this._filterSet.value, this._mergeExtraInfo(extraInfo));\n }\n\n /**\n * @callback FilterHandle~listener\n * @param {Object} event - An object containing details of the event. For\n * `\"change\"` events, this includes the properties `value` (the new\n * value of the filter set, or `null` if no filter set is active),\n * `oldValue` (the previous value of the filter set), and `sender` (the\n * `FilterHandle` instance that made the change).\n */\n\n}\n\n/**\n * @event FilterHandle#change\n * @type {object}\n * @property {object} value - The new value of the filter set, or `null`\n * if no filter set is active.\n * @property {object} oldValue - The previous value of the filter set.\n * @property {FilterHandle} sender - The `FilterHandle` instance that\n * changed the value.\n */\n","import { diffSortedLists } from \"./util\";\n\nfunction naturalComparator(a, b) {\n if (a === b) {\n return 0;\n } else if (a < b) {\n return -1;\n } else if (a > b) {\n return 1;\n }\n}\n\n/**\n * @private\n */\nexport default class FilterSet {\n constructor() {\n this.reset();\n }\n\n reset() {\n // Key: handle ID, Value: array of selected keys, or null\n this._handles = {};\n // Key: key string, Value: count of handles that include it\n this._keys = {};\n this._value = null;\n this._activeHandles = 0;\n }\n\n get value() {\n return this._value;\n }\n\n update(handleId, keys) {\n if (keys !== null) {\n keys = keys.slice(0); // clone before sorting\n keys.sort(naturalComparator);\n }\n\n let {added, removed} = diffSortedLists(this._handles[handleId], keys);\n this._handles[handleId] = keys;\n\n for (let i = 0; i < added.length; i++) {\n this._keys[added[i]] = (this._keys[added[i]] || 0) + 1;\n }\n for (let i = 0; i < removed.length; i++) {\n this._keys[removed[i]]--;\n }\n\n this._updateValue(keys);\n }\n\n /**\n * @param {string[]} keys Sorted array of strings that indicate\n * a superset of possible keys.\n * @private\n */\n _updateValue(keys = this._allKeys) {\n let handleCount = Object.keys(this._handles).length;\n if (handleCount === 0) {\n this._value = null;\n } else {\n this._value = [];\n for (let i = 0; i < keys.length; i++) {\n let count = this._keys[keys[i]];\n if (count === handleCount) {\n this._value.push(keys[i]);\n }\n }\n }\n }\n\n clear(handleId) {\n if (typeof(this._handles[handleId]) === \"undefined\") {\n return;\n }\n\n let keys = this._handles[handleId];\n if (!keys) {\n keys = [];\n }\n\n for (let i = 0; i < keys.length; i++) {\n this._keys[keys[i]]--;\n }\n delete this._handles[handleId];\n\n this._updateValue();\n }\n\n get _allKeys() {\n let allKeys = Object.keys(this._keys);\n allKeys.sort(naturalComparator);\n return allKeys;\n }\n}\n","import Var from \"./var\";\n\n// Use a global so that multiple copies of crosstalk.js can be loaded and still\n// have groups behave as singletons across all copies.\nglobal.__crosstalk_groups = global.__crosstalk_groups || {};\nlet groups = global.__crosstalk_groups;\n\nexport default function group(groupName) {\n if (groupName && typeof(groupName) === \"string\") {\n if (!groups.hasOwnProperty(groupName)) {\n groups[groupName] = new Group(groupName);\n }\n return groups[groupName];\n } else if (typeof(groupName) === \"object\" && groupName._vars && groupName.var) {\n // Appears to already be a group object\n return groupName;\n } else if (Array.isArray(groupName) &&\n groupName.length == 1 &&\n typeof(groupName[0]) === \"string\") {\n return group(groupName[0]);\n } else {\n throw new Error(\"Invalid groupName argument\");\n }\n}\n\nclass Group {\n constructor(name) {\n this.name = name;\n this._vars = {};\n }\n\n var(name) {\n if (!name || typeof(name) !== \"string\") {\n throw new Error(\"Invalid var name\");\n }\n\n if (!this._vars.hasOwnProperty(name))\n this._vars[name] = new Var(this, name);\n return this._vars[name];\n }\n\n has(name) {\n if (!name || typeof(name) !== \"string\") {\n throw new Error(\"Invalid var name\");\n }\n\n return this._vars.hasOwnProperty(name);\n }\n}\n","import group from \"./group\";\nimport { SelectionHandle } from \"./selection\";\nimport { FilterHandle } from \"./filter\";\nimport { bind } from \"./input\";\nimport \"./input_selectize\";\nimport \"./input_checkboxgroup\";\nimport \"./input_slider\";\n\nconst defaultGroup = group(\"default\");\n\nfunction var_(name) {\n return defaultGroup.var(name);\n}\n\nfunction has(name) {\n return defaultGroup.has(name);\n}\n\nif (global.Shiny) {\n global.Shiny.addCustomMessageHandler(\"update-client-value\", function(message) {\n if (typeof(message.group) === \"string\") {\n group(message.group).var(message.name).set(message.value);\n } else {\n var_(message.name).set(message.value);\n }\n });\n}\n\nconst crosstalk = {\n group: group,\n var: var_,\n has: has,\n SelectionHandle: SelectionHandle,\n FilterHandle: FilterHandle,\n bind: bind\n};\n\n/**\n * @namespace crosstalk\n */\nexport default crosstalk;\nglobal.crosstalk = crosstalk;\n","let $ = global.jQuery;\n\nlet bindings = {};\n\nexport function register(reg) {\n bindings[reg.className] = reg;\n if (global.document && global.document.readyState !== \"complete\") {\n $(() => {\n bind();\n });\n } else if (global.document) {\n setTimeout(bind, 100);\n }\n}\n\nexport function bind() {\n Object.keys(bindings).forEach(function(className) {\n let binding = bindings[className];\n $(\".\" + binding.className).not(\".crosstalk-input-bound\").each(function(i, el) {\n bindInstance(binding, el);\n });\n });\n}\n\n// Escape jQuery identifier\nfunction $escape(val) {\n return val.replace(/([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^`{|}~])/g, \"\\\\$1\");\n}\n\nfunction bindEl(el) {\n let $el = $(el);\n Object.keys(bindings).forEach(function(className) {\n if ($el.hasClass(className) && !$el.hasClass(\"crosstalk-input-bound\")) {\n let binding = bindings[className];\n bindInstance(binding, el);\n }\n });\n}\n\nfunction bindInstance(binding, el) {\n let jsonEl = $(el).find(\"script[type='application/json'][data-for='\" + $escape(el.id) + \"']\");\n let data = JSON.parse(jsonEl[0].innerText);\n\n let instance = binding.factory(el, data);\n $(el).data(\"crosstalk-instance\", instance);\n $(el).addClass(\"crosstalk-input-bound\");\n}\n\nif (global.Shiny) {\n let inputBinding = new global.Shiny.InputBinding();\n let $ = global.jQuery;\n $.extend(inputBinding, {\n find: function(scope) {\n return $(scope).find(\".crosstalk-input\");\n },\n initialize: function(el) {\n if (!$(el).hasClass(\"crosstalk-input-bound\")) {\n bindEl(el);\n }\n },\n getId: function(el) {\n return el.id;\n },\n getValue: function(el) {\n\n },\n setValue: function(el, value) {\n\n },\n receiveMessage: function(el, data) {\n\n },\n subscribe: function(el, callback) {\n $(el).data(\"crosstalk-instance\").resume();\n },\n unsubscribe: function(el) {\n $(el).data(\"crosstalk-instance\").suspend();\n }\n });\n global.Shiny.inputBindings.register(inputBinding, \"crosstalk.inputBinding\");\n}\n","import * as input from \"./input\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\n\ninput.register({\n className: \"crosstalk-input-checkboxgroup\",\n\n factory: function(el, data) {\n /*\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n let ctHandle = new FilterHandle(data.group);\n\n let lastKnownKeys;\n let $el = $(el);\n $el.on(\"change\", \"input[type='checkbox']\", function() {\n let checked = $el.find(\"input[type='checkbox']:checked\");\n if (checked.length === 0) {\n lastKnownKeys = null;\n ctHandle.clear();\n } else {\n let keys = {};\n checked.each(function() {\n data.map[this.value].forEach(function(key) {\n keys[key] = true;\n });\n });\n let keyArray = Object.keys(keys);\n keyArray.sort();\n lastKnownKeys = keyArray;\n ctHandle.set(keyArray);\n }\n });\n\n return {\n suspend: function() {\n ctHandle.clear();\n },\n resume: function() {\n if (lastKnownKeys)\n ctHandle.set(lastKnownKeys);\n }\n };\n }\n});\n","import * as input from \"./input\";\nimport * as util from \"./util\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\n\ninput.register({\n className: \"crosstalk-input-select\",\n\n factory: function(el, data) {\n /*\n * items: {value: [...], label: [...]}\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n\n let first = [{value: \"\", label: \"(All)\"}];\n let items = util.dataframeToD3(data.items);\n let opts = {\n options: first.concat(items),\n valueField: \"value\",\n labelField: \"label\",\n searchField: \"label\"\n };\n\n let select = $(el).find(\"select\")[0];\n\n let selectize = $(select).selectize(opts)[0].selectize;\n\n let ctHandle = new FilterHandle(data.group);\n\n let lastKnownKeys;\n selectize.on(\"change\", function() {\n if (selectize.items.length === 0) {\n lastKnownKeys = null;\n ctHandle.clear();\n } else {\n let keys = {};\n selectize.items.forEach(function(group) {\n data.map[group].forEach(function(key) {\n keys[key] = true;\n });\n });\n let keyArray = Object.keys(keys);\n keyArray.sort();\n lastKnownKeys = keyArray;\n ctHandle.set(keyArray);\n }\n });\n\n return {\n suspend: function() {\n ctHandle.clear();\n },\n resume: function() {\n if (lastKnownKeys)\n ctHandle.set(lastKnownKeys);\n }\n };\n }\n});\n","import * as input from \"./input\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\nlet strftime = global.strftime;\n\ninput.register({\n className: \"crosstalk-input-slider\",\n\n factory: function(el, data) {\n /*\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n let ctHandle = new FilterHandle(data.group);\n\n let opts = {};\n let $el = $(el).find(\"input\");\n let dataType = $el.data(\"data-type\");\n let timeFormat = $el.data(\"time-format\");\n let round = $el.data(\"round\");\n let timeFormatter;\n\n // Set up formatting functions\n if (dataType === \"date\") {\n timeFormatter = strftime.utc();\n opts.prettify = function(num) {\n return timeFormatter(timeFormat, new Date(num));\n };\n\n } else if (dataType === \"datetime\") {\n let timezone = $el.data(\"timezone\");\n if (timezone)\n timeFormatter = strftime.timezone(timezone);\n else\n timeFormatter = strftime;\n\n opts.prettify = function(num) {\n return timeFormatter(timeFormat, new Date(num));\n };\n } else if (dataType === \"number\") {\n if (typeof round !== \"undefined\")\n opts.prettify = function(num) {\n let factor = Math.pow(10, round);\n return Math.round(num * factor) / factor;\n };\n }\n\n $el.ionRangeSlider(opts);\n\n function getValue() {\n let result = $el.data(\"ionRangeSlider\").result;\n\n // Function for converting numeric value from slider to appropriate type.\n let convert;\n let dataType = $el.data(\"data-type\");\n if (dataType === \"date\") {\n convert = function(val) {\n return formatDateUTC(new Date(+val));\n };\n } else if (dataType === \"datetime\") {\n convert = function(val) {\n // Convert ms to s\n return +val / 1000;\n };\n } else {\n convert = function(val) { return +val; };\n }\n\n if ($el.data(\"ionRangeSlider\").options.type === \"double\") {\n return [convert(result.from), convert(result.to)];\n } else {\n return convert(result.from);\n }\n }\n\n let lastKnownKeys = null;\n\n $el.on(\"change.crosstalkSliderInput\", function(event) {\n if (!$el.data(\"updating\") && !$el.data(\"animating\")) {\n let [from, to] = getValue();\n let keys = [];\n for (let i = 0; i < data.values.length; i++) {\n let val = data.values[i];\n if (val >= from && val <= to) {\n keys.push(data.keys[i]);\n }\n }\n keys.sort();\n ctHandle.set(keys);\n lastKnownKeys = keys;\n }\n });\n\n\n // let $el = $(el);\n // $el.on(\"change\", \"input[type=\"checkbox\"]\", function() {\n // let checked = $el.find(\"input[type=\"checkbox\"]:checked\");\n // if (checked.length === 0) {\n // ctHandle.clear();\n // } else {\n // let keys = {};\n // checked.each(function() {\n // data.map[this.value].forEach(function(key) {\n // keys[key] = true;\n // });\n // });\n // let keyArray = Object.keys(keys);\n // keyArray.sort();\n // ctHandle.set(keyArray);\n // }\n // });\n\n return {\n suspend: function() {\n ctHandle.clear();\n },\n resume: function() {\n if (lastKnownKeys)\n ctHandle.set(lastKnownKeys);\n }\n };\n }\n});\n\n\n// Convert a number to a string with leading zeros\nfunction padZeros(n, digits) {\n let str = n.toString();\n while (str.length < digits)\n str = \"0\" + str;\n return str;\n}\n\n// Given a Date object, return a string in yyyy-mm-dd format, using the\n// UTC date. This may be a day off from the date in the local time zone.\nfunction formatDateUTC(date) {\n if (date instanceof Date) {\n return date.getUTCFullYear() + \"-\" +\n padZeros(date.getUTCMonth()+1, 2) + \"-\" +\n padZeros(date.getUTCDate(), 2);\n\n } else {\n return null;\n }\n}\n","import Events from \"./events\";\nimport grp from \"./group\";\nimport * as util from \"./util\";\n\n/**\n * Use this class to read and write (and listen for changes to) the selection\n * for a Crosstalk group. This is intended to be used for linked brushing.\n *\n * If two (or more) `SelectionHandle` instances in the same webpage share the\n * same group name, they will share the same state. Setting the selection using\n * one `SelectionHandle` instance will result in the `value` property instantly\n * changing across the others, and `\"change\"` event listeners on all instances\n * (including the one that initiated the sending) will fire.\n *\n * @param {string} [group] - The name of the Crosstalk group, or if none,\n * null or undefined (or any other falsy value). This can be changed later\n * via the [SelectionHandle#setGroup](#setGroup) method.\n * @param {Object} [extraInfo] - An object whose properties will be copied to\n * the event object whenever an event is emitted.\n */\nexport class SelectionHandle {\n\n constructor(group = null, extraInfo = null) {\n this._eventRelay = new Events();\n this._emitter = new util.SubscriptionTracker(this._eventRelay);\n\n // Name of the group we're currently tracking, if any. Can change over time.\n this._group = null;\n // The Var we're currently tracking, if any. Can change over time.\n this._var = null;\n // The event handler subscription we currently have on var.on(\"change\").\n this._varOnChangeSub = null;\n\n this._extraInfo = util.extend({ sender: this }, extraInfo);\n\n this.setGroup(group);\n }\n\n /**\n * Changes the Crosstalk group membership of this SelectionHandle. The group\n * being switched away from (if any) will not have its selection value\n * modified as a result of calling `setGroup`, even if this handle was the\n * most recent handle to set the selection of the group.\n *\n * The group being switched to (if any) will also not have its selection value\n * modified as a result of calling `setGroup`. If you want to set the\n * selection value of the new group, call `set` explicitly.\n *\n * @param {string} group - The name of the Crosstalk group, or null (or\n * undefined) to clear the group.\n */\n setGroup(group) {\n // If group is unchanged, do nothing\n if (this._group === group)\n return;\n // Treat null, undefined, and other falsy values the same\n if (!this._group && !group)\n return;\n\n if (this._var) {\n this._var.off(\"change\", this._varOnChangeSub);\n this._var = null;\n this._varOnChangeSub = null;\n }\n\n this._group = group;\n\n if (group) {\n this._var = grp(group).var(\"selection\");\n let sub = this._var.on(\"change\", (e) => {\n this._eventRelay.trigger(\"change\", e, this);\n });\n this._varOnChangeSub = sub;\n }\n }\n\n /**\n * Retrieves the current selection for the group represented by this\n * `SelectionHandle`.\n *\n * - If no selection is active, then this value will be falsy.\n * - If a selection is active, but no data points are selected, then this\n * value will be an empty array.\n * - If a selection is active, and data points are selected, then the keys\n * of the selected data points will be present in the array.\n */\n get value() {\n return this._var ? this._var.get() : null;\n }\n\n /**\n * Combines the given `extraInfo` (if any) with the handle's default\n * `_extraInfo` (if any).\n * @private\n */\n _mergeExtraInfo(extraInfo) {\n // Important incidental effect: shallow clone is returned\n return util.extend({},\n this._extraInfo ? this._extraInfo : null,\n extraInfo ? extraInfo : null);\n }\n\n /**\n * Overwrites the current selection for the group, and raises the `\"change\"`\n * event among all of the group's '`SelectionHandle` instances (including\n * this one).\n *\n * @fires SelectionHandle#change\n * @param {string[]} selectedKeys - Falsy, empty array, or array of keys (see\n * {@link SelectionHandle#value}).\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any options that were\n * passed into the `SelectionHandle` constructor).\n */\n set(selectedKeys, extraInfo) {\n if (this._var)\n this._var.set(selectedKeys, this._mergeExtraInfo(extraInfo));\n }\n\n /**\n * Overwrites the current selection for the group, and raises the `\"change\"`\n * event among all of the group's '`SelectionHandle` instances (including\n * this one).\n *\n * @fires SelectionHandle#change\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any that were passed\n * into the `SelectionHandle` constructor).\n */\n clear(extraInfo) {\n if (this._var)\n this.set(void 0, this._mergeExtraInfo(extraInfo));\n }\n\n /**\n * Subscribes to events on this `SelectionHandle`.\n *\n * @param {string} eventType - Indicates the type of events to listen to.\n * Currently, only `\"change\"` is supported.\n * @param {SelectionHandle~listener} listener - The callback function that\n * will be invoked when the event occurs.\n * @return {string} - A token to pass to {@link SelectionHandle#off} to cancel\n * this subscription.\n */\n on(eventType, listener) {\n return this._emitter.on(eventType, listener);\n }\n\n /**\n * Cancels event subscriptions created by {@link SelectionHandle#on}.\n *\n * @param {string} eventType - The type of event to unsubscribe.\n * @param {string|SelectionHandle~listener} listener - Either the callback\n * function previously passed into {@link SelectionHandle#on}, or the\n * string that was returned from {@link SelectionHandle#on}.\n */\n off(eventType, listener) {\n return this._emitter.off(eventType, listener);\n }\n\n /**\n * Shuts down the `SelectionHandle` object.\n *\n * Removes all event listeners that were added through this handle.\n */\n close() {\n this._emitter.removeAllListeners();\n this.setGroup(null);\n }\n}\n\n/**\n * @callback SelectionHandle~listener\n * @param {Object} event - An object containing details of the event. For\n * `\"change\"` events, this includes the properties `value` (the new\n * value of the selection, or `undefined` if no selection is active),\n * `oldValue` (the previous value of the selection), and `sender` (the\n * `SelectionHandle` instance that made the change).\n */\n\n/**\n * @event SelectionHandle#change\n * @type {object}\n * @property {object} value - The new value of the selection, or `undefined`\n * if no selection is active.\n * @property {object} oldValue - The previous value of the selection.\n * @property {SelectionHandle} sender - The `SelectionHandle` instance that\n * changed the value.\n */\n","export function extend(target, ...sources) {\n for (let i = 0; i < sources.length; i++) {\n let src = sources[i];\n if (typeof(src) === \"undefined\" || src === null)\n continue;\n\n for (let key in src) {\n if (src.hasOwnProperty(key)) {\n target[key] = src[key];\n }\n }\n }\n return target;\n}\n\nexport function checkSorted(list) {\n for (let i = 1; i < list.length; i++) {\n if (list[i] <= list[i-1]) {\n throw new Error(\"List is not sorted or contains duplicate\");\n }\n }\n}\n\nexport function diffSortedLists(a, b) {\n let i_a = 0;\n let i_b = 0;\n\n if (!a) a = [];\n if (!b) b = [];\n\n let a_only = [];\n let b_only = [];\n\n checkSorted(a);\n checkSorted(b);\n\n while (i_a < a.length && i_b < b.length) {\n if (a[i_a] === b[i_b]) {\n i_a++;\n i_b++;\n } else if (a[i_a] < b[i_b]) {\n a_only.push(a[i_a++]);\n } else {\n b_only.push(b[i_b++]);\n }\n }\n\n if (i_a < a.length)\n a_only = a_only.concat(a.slice(i_a));\n if (i_b < b.length)\n b_only = b_only.concat(b.slice(i_b));\n return {\n removed: a_only,\n added: b_only\n };\n}\n\n// Convert from wide: { colA: [1,2,3], colB: [4,5,6], ... }\n// to long: [ {colA: 1, colB: 4}, {colA: 2, colB: 5}, ... ]\nexport function dataframeToD3(df) {\n let names = [];\n let length;\n for (let name in df) {\n if (df.hasOwnProperty(name))\n names.push(name);\n if (typeof(df[name]) !== \"object\" || typeof(df[name].length) === \"undefined\") {\n throw new Error(\"All fields must be arrays\");\n } else if (typeof(length) !== \"undefined\" && length !== df[name].length) {\n throw new Error(\"All fields must be arrays of the same length\");\n }\n length = df[name].length;\n }\n let results = [];\n let item;\n for (let row = 0; row < length; row++) {\n item = {};\n for (let col = 0; col < names.length; col++) {\n item[names[col]] = df[names[col]][row];\n }\n results.push(item);\n }\n return results;\n}\n\n/**\n * Keeps track of all event listener additions/removals and lets all active\n * listeners be removed with a single operation.\n *\n * @private\n */\nexport class SubscriptionTracker {\n constructor(emitter) {\n this._emitter = emitter;\n this._subs = {};\n }\n\n on(eventType, listener) {\n let sub = this._emitter.on(eventType, listener);\n this._subs[sub] = eventType;\n return sub;\n }\n\n off(eventType, listener) {\n let sub = this._emitter.off(eventType, listener);\n if (sub) {\n delete this._subs[sub];\n }\n return sub;\n }\n\n removeAllListeners() {\n let current_subs = this._subs;\n this._subs = {};\n Object.keys(current_subs).forEach((sub) => {\n this._emitter.off(current_subs[sub], sub);\n });\n }\n}\n","import Events from \"./events\";\n\nexport default class Var {\n constructor(group, name, /*optional*/ value) {\n this._group = group;\n this._name = name;\n this._value = value;\n this._events = new Events();\n }\n\n get() {\n return this._value;\n }\n\n set(value, /*optional*/ event) {\n if (this._value === value) {\n // Do nothing; the value hasn't changed\n return;\n }\n let oldValue = this._value;\n this._value = value;\n // Alert JavaScript listeners that the value has changed\n let evt = {};\n if (event && typeof(event) === \"object\") {\n for (let k in event) {\n if (event.hasOwnProperty(k))\n evt[k] = event[k];\n }\n }\n evt.oldValue = oldValue;\n evt.value = value;\n this._events.trigger(\"change\", evt, this);\n\n // TODO: Make this extensible, to let arbitrary back-ends know that\n // something has changed\n if (global.Shiny && global.Shiny.onInputChange) {\n global.Shiny.onInputChange(\n \".clientValue-\" +\n (this._group.name !== null ? this._group.name + \"-\" : \"\") +\n this._name,\n typeof(value) === \"undefined\" ? null : value\n );\n }\n }\n\n on(eventType, listener) {\n return this._events.on(eventType, listener);\n }\n\n off(eventType, listener) {\n return this._events.off(eventType, listener);\n }\n}\n"]} \ No newline at end of file diff --git a/docs/articles/xportr_files/datatables-binding-0.17/datatables.js b/docs/articles/xportr_files/datatables-binding-0.17/datatables.js deleted file mode 100644 index 47909da2..00000000 --- a/docs/articles/xportr_files/datatables-binding-0.17/datatables.js +++ /dev/null @@ -1,1422 +0,0 @@ -(function() { - -// some helper functions: using a global object DTWidget so that it can be used -// in JS() code, e.g. datatable(options = list(foo = JS('code'))); unlike R's -// dynamic scoping, when 'code' is eval()'ed, JavaScript does not know objects -// from the "parent frame", e.g. JS('DTWidget') will not work unless it was made -// a global object -var DTWidget = {}; - -// 123456666.7890 -> 123,456,666.7890 -var markInterval = function(d, digits, interval, mark, decMark, precision) { - x = precision ? d.toPrecision(digits) : d.toFixed(digits); - if (!/^-?[\d.]+$/.test(x)) return x; - var xv = x.split('.'); - if (xv.length > 2) return x; // should have at most one decimal point - xv[0] = xv[0].replace(new RegExp('\\B(?=(\\d{' + interval + '})+(?!\\d))', 'g'), mark); - return xv.join(decMark); -}; - -DTWidget.formatCurrency = function(data, currency, digits, interval, mark, decMark, before) { - var d = parseFloat(data); - if (isNaN(d)) return ''; - var res = markInterval(d, digits, interval, mark, decMark); - res = before ? (/^-/.test(res) ? '-' + currency + res.replace(/^-/, '') : currency + res) : - res + currency; - return res; -}; - -DTWidget.formatString = function(data, prefix, suffix) { - var d = data; - if (d === null) return ''; - return prefix + d + suffix; -}; - -DTWidget.formatPercentage = function(data, digits, interval, mark, decMark) { - var d = parseFloat(data); - if (isNaN(d)) return ''; - return markInterval(d * 100, digits, interval, mark, decMark) + '%'; -}; - -DTWidget.formatRound = function(data, digits, interval, mark, decMark) { - var d = parseFloat(data); - if (isNaN(d)) return ''; - return markInterval(d, digits, interval, mark, decMark); -}; - -DTWidget.formatSignif = function(data, digits, interval, mark, decMark) { - var d = parseFloat(data); - if (isNaN(d)) return ''; - return markInterval(d, digits, interval, mark, decMark, true); -}; - -DTWidget.formatDate = function(data, method, params) { - var d = data; - if (d === null) return ''; - // (new Date('2015-10-28')).toDateString() may return 2015-10-27 because the - // actual time created could be like 'Tue Oct 27 2015 19:00:00 GMT-0500 (CDT)', - // i.e. the date-only string is treated as UTC time instead of local time - if ((method === 'toDateString' || method === 'toLocaleDateString') && /^\d{4,}\D\d{2}\D\d{2}$/.test(d)) { - d = d.split(/\D/); - d = new Date(d[0], d[1] - 1, d[2]); - } else { - d = new Date(d); - } - return d[method].apply(d, params); -}; - -window.DTWidget = DTWidget; - -var transposeArray2D = function(a) { - return a.length === 0 ? a : HTMLWidgets.transposeArray2D(a); -}; - -var crosstalkPluginsInstalled = false; - -function maybeInstallCrosstalkPlugins() { - if (crosstalkPluginsInstalled) - return; - crosstalkPluginsInstalled = true; - - $.fn.dataTable.ext.afnFiltering.push( - function(oSettings, aData, iDataIndex) { - var ctfilter = oSettings.nTable.ctfilter; - if (ctfilter && !ctfilter[iDataIndex]) - return false; - - var ctselect = oSettings.nTable.ctselect; - if (ctselect && !ctselect[iDataIndex]) - return false; - - return true; - } - ); -} - -HTMLWidgets.widget({ - name: "datatables", - type: "output", - renderOnNullValue: true, - initialize: function(el, width, height) { - $(el).html(' '); - return { - data: null, - ctfilterHandle: new crosstalk.FilterHandle(), - ctfilterSubscription: null, - ctselectHandle: new crosstalk.SelectionHandle(), - ctselectSubscription: null - }; - }, - renderValue: function(el, data, instance) { - if (el.offsetWidth === 0 || el.offsetHeight === 0) { - instance.data = data; - return; - } - instance.data = null; - var $el = $(el); - $el.empty(); - - if (data === null) { - $el.append(' '); - // clear previous Shiny inputs (if any) - for (var i in instance.clearInputs) instance.clearInputs[i](); - instance.clearInputs = {}; - return; - } - - var crosstalkOptions = data.crosstalkOptions; - if (!crosstalkOptions) crosstalkOptions = { - 'key': null, 'group': null - }; - if (crosstalkOptions.group) { - maybeInstallCrosstalkPlugins(); - instance.ctfilterHandle.setGroup(crosstalkOptions.group); - instance.ctselectHandle.setGroup(crosstalkOptions.group); - } - - // If we are in a flexdashboard scroll layout then we: - // (a) Always want to use pagination (otherwise we'll have - // a "double scroll bar" effect on the phone); and - // (b) Never want to fill the container (we want the pagination - // level to determine the size of the container) - if (window.FlexDashboard && !window.FlexDashboard.isFillPage()) { - data.options.paging = true; - data.fillContainer = false; - } - - // if we are in the viewer then we always want to fillContainer and - // and autoHideNavigation (unless the user has explicitly set these) - if (window.HTMLWidgets.viewerMode) { - if (!data.hasOwnProperty("fillContainer")) - data.fillContainer = true; - if (!data.hasOwnProperty("autoHideNavigation")) - data.autoHideNavigation = true; - } - - // propagate fillContainer to instance (so we have it in resize) - instance.fillContainer = data.fillContainer; - - var cells = data.data; - - if (cells instanceof Array) cells = transposeArray2D(cells); - - $el.append(data.container); - var $table = $el.find('table'); - if (data.class) $table.addClass(data.class); - if (data.caption) $table.prepend(data.caption); - - if (!data.selection) data.selection = { - mode: 'none', selected: null, target: 'row', selectable: null - }; - if (HTMLWidgets.shinyMode && data.selection.mode !== 'none' && - data.selection.target === 'row+column') { - if ($table.children('tfoot').length === 0) { - $table.append($('')); - $table.find('thead tr').clone().appendTo($table.find('tfoot')); - } - } - - // column filters - var filterRow; - switch (data.filter) { - case 'top': - $table.children('thead').append(data.filterHTML); - filterRow = $table.find('thead tr:last td'); - break; - case 'bottom': - if ($table.children('tfoot').length === 0) { - $table.append($('')); - } - $table.children('tfoot').prepend(data.filterHTML); - filterRow = $table.find('tfoot tr:first td'); - break; - } - - var options = { searchDelay: 1000 }; - if (cells !== null) $.extend(options, { - data: cells - }); - - // options for fillContainer - var bootstrapActive = typeof($.fn.popover) != 'undefined'; - if (instance.fillContainer) { - - // force scrollX/scrollY and turn off autoWidth - options.scrollX = true; - options.scrollY = "100px"; // can be any value, we'll adjust below - - // if we aren't paginating then move around the info/filter controls - // to save space at the bottom and rephrase the info callback - if (data.options.paging === false) { - - // we know how to do this cleanly for bootstrap, not so much - // for other themes/layouts - if (bootstrapActive) { - options.dom = "<'row'<'col-sm-4'i><'col-sm-8'f>>" + - "<'row'<'col-sm-12'tr>>"; - } - - options.fnInfoCallback = function(oSettings, iStart, iEnd, - iMax, iTotal, sPre) { - return Number(iTotal).toLocaleString() + " records"; - }; - } - } - - // auto hide navigation if requested - // Note, this only works on client-side processing mode as on server-side, - // cells (data.data) is null; In addition, we require the pageLength option - // being provided explicitly to enable this. Despite we may be able to deduce - // the default value of pageLength, it may complicate things so we'd rather - // put this responsiblity to users and warn them on the R side. - if (data.autoHideNavigation === true && data.options.paging !== false) { - // strip all nav if length >= cells - if ((cells instanceof Array) && data.options.pageLength >= cells.length) - options.dom = bootstrapActive ? "<'row'<'col-sm-12'tr>>" : "t"; - // alternatively lean things out for flexdashboard mobile portrait - else if (bootstrapActive && window.FlexDashboard && window.FlexDashboard.isMobilePhone()) - options.dom = "<'row'<'col-sm-12'f>>" + - "<'row'<'col-sm-12'tr>>" + - "<'row'<'col-sm-12'p>>"; - } - - $.extend(true, options, data.options || {}); - - var searchCols = options.searchCols; - if (searchCols) { - searchCols = searchCols.map(function(x) { - return x === null ? '' : x.search; - }); - // FIXME: this means I don't respect the escapeRegex setting - delete options.searchCols; - } - - // server-side processing? - var server = options.serverSide === true; - - // use the dataSrc function to pre-process JSON data returned from R - var DT_rows_all = [], DT_rows_current = []; - if (server && HTMLWidgets.shinyMode && typeof options.ajax === 'object' && - /^session\/[\da-z]+\/dataobj/.test(options.ajax.url) && !options.ajax.dataSrc) { - options.ajax.dataSrc = function(json) { - DT_rows_all = $.makeArray(json.DT_rows_all); - DT_rows_current = $.makeArray(json.DT_rows_current); - var data = json.data; - if (!colReorderEnabled()) return data; - var table = $table.DataTable(), order = table.colReorder.order(), flag = true, i, j, row; - for (i = 0; i < order.length; ++i) if (order[i] !== i) flag = false; - if (flag) return data; - for (i = 0; i < data.length; ++i) { - row = data[i].slice(); - for (j = 0; j < order.length; ++j) data[i][j] = row[order[j]]; - } - return data; - }; - } - - var thiz = this; - if (instance.fillContainer) $table.on('init.dt', function(e) { - thiz.fillAvailableHeight(el, $(el).innerHeight()); - }); - // If the page contains serveral datatables and one of which enables colReorder, - // the table.colReorder.order() function will exist but throws error when called. - // So it seems like the only way to know if colReorder is enabled or not is to - // check the options. - var colReorderEnabled = function() { return "colReorder" in options; }; - var table = $table.DataTable(options); - $el.data('datatable', table); - - // Unregister previous Crosstalk event subscriptions, if they exist - if (instance.ctfilterSubscription) { - instance.ctfilterHandle.off("change", instance.ctfilterSubscription); - instance.ctfilterSubscription = null; - } - if (instance.ctselectSubscription) { - instance.ctselectHandle.off("change", instance.ctselectSubscription); - instance.ctselectSubscription = null; - } - - if (!crosstalkOptions.group) { - $table[0].ctfilter = null; - $table[0].ctselect = null; - } else { - var key = crosstalkOptions.key; - function keysToMatches(keys) { - if (!keys) { - return null; - } else { - var selectedKeys = {}; - for (var i = 0; i < keys.length; i++) { - selectedKeys[keys[i]] = true; - } - var matches = {}; - for (var j = 0; j < key.length; j++) { - if (selectedKeys[key[j]]) - matches[j] = true; - } - return matches; - } - } - - function applyCrosstalkFilter(e) { - $table[0].ctfilter = keysToMatches(e.value); - table.draw(); - } - instance.ctfilterSubscription = instance.ctfilterHandle.on("change", applyCrosstalkFilter); - applyCrosstalkFilter({value: instance.ctfilterHandle.filteredKeys}); - - function applyCrosstalkSelection(e) { - if (e.sender !== instance.ctselectHandle) { - table - .rows('.' + selClass, {search: 'applied'}) - .nodes() - .to$() - .removeClass(selClass); - if (selectedRows) - changeInput('rows_selected', selectedRows(), void 0, true); - } - - if (e.sender !== instance.ctselectHandle && e.value && e.value.length) { - var matches = keysToMatches(e.value); - - // persistent selection with plotly (& leaflet) - var ctOpts = crosstalk.var("plotlyCrosstalkOpts").get() || {}; - if (ctOpts.persistent === true) { - var matches = $.extend(matches, $table[0].ctselect); - } - - $table[0].ctselect = matches; - table.draw(); - } else { - if ($table[0].ctselect) { - $table[0].ctselect = null; - table.draw(); - } - } - } - instance.ctselectSubscription = instance.ctselectHandle.on("change", applyCrosstalkSelection); - // TODO: This next line doesn't seem to work when renderDataTable is used - applyCrosstalkSelection({value: instance.ctselectHandle.value}); - } - - var inArray = function(val, array) { - return $.inArray(val, $.makeArray(array)) > -1; - }; - - // encode + to %2B when searching in the table on server side, because - // shiny::parseQueryString() treats + as spaces, and DataTables does not - // encode + to %2B (or % to %25) when sending the request - var encode_plus = function(x) { - return server ? x.replace(/%/g, '%25').replace(/\+/g, '%2B') : x; - }; - - // search the i-th column - var searchColumn = function(i, value) { - var regex = false, ci = true; - if (options.search) { - regex = options.search.regex, - ci = options.search.caseInsensitive !== false; - } - return table.column(i).search(encode_plus(value), regex, !regex, ci); - }; - - if (data.filter !== 'none') { - - filterRow.each(function(i, td) { - - var $td = $(td), type = $td.data('type'), filter; - var $input = $td.children('div').first().children('input'); - $input.prop('disabled', !table.settings()[0].aoColumns[i].bSearchable || type === 'disabled'); - $input.on('input blur', function() { - $input.next('span').toggle(Boolean($input.val())); - }); - // Bootstrap sets pointer-events to none and we won't be able to click - // the clear button - $input.next('span').css('pointer-events', 'auto').hide().click(function() { - $(this).hide().prev('input').val('').trigger('input').focus(); - }); - var searchCol; // search string for this column - if (searchCols && searchCols[i]) { - searchCol = searchCols[i]; - $input.val(searchCol).trigger('input'); - } - var $x = $td.children('div').last(); - - // remove the overflow: hidden attribute of the scrollHead - // (otherwise the scrolling table body obscures the filters) - // The workaround and the discussion from - // https://github.com/rstudio/DT/issues/554#issuecomment-518007347 - // Otherwise the filter selection will not be anchored to the values - // when the columns number is many and scrollX is enabled. - var scrollHead = $(el).find('.dataTables_scrollHead,.dataTables_scrollFoot'); - var cssOverflowHead = scrollHead.css('overflow'); - var scrollBody = $(el).find('.dataTables_scrollBody'); - var cssOverflowBody = scrollBody.css('overflow'); - var scrollTable = $(el).find('.dataTables_scroll'); - var cssOverflowTable = scrollTable.css('overflow'); - if (cssOverflowHead === 'hidden') { - $x.on('show hide', function(e) { - if (e.type === 'show') { - scrollHead.css('overflow', 'visible'); - scrollBody.css('overflow', 'visible'); - scrollTable.css('overflow-x', 'scroll'); - } else { - scrollHead.css('overflow', cssOverflowHead); - scrollBody.css('overflow', cssOverflowBody); - scrollTable.css('overflow-x', cssOverflowTable); - } - }); - $x.css('z-index', 25); - } - - if (inArray(type, ['factor', 'logical'])) { - $input.on({ - click: function() { - $input.parent().hide(); $x.show().trigger('show'); filter[0].selectize.focus(); - }, - input: function() { - if ($input.val() === '') filter[0].selectize.setValue([]); - } - }); - var $input2 = $x.children('select'); - filter = $input2.selectize({ - options: $input2.data('options').map(function(v, i) { - return ({text: v, value: v}); - }), - plugins: ['remove_button'], - hideSelected: true, - onChange: function(value) { - if (value === null) value = []; // compatibility with jQuery 3.0 - $input.val(value.length ? JSON.stringify(value) : ''); - if (value.length) $input.trigger('input'); - $input.attr('title', $input.val()); - if (server) { - table.column(i).search(value.length ? encode_plus(JSON.stringify(value)) : '').draw(); - return; - } - // turn off filter if nothing selected - $td.data('filter', value.length > 0); - table.draw(); // redraw table, and filters will be applied - } - }); - if (searchCol) filter[0].selectize.setValue(JSON.parse(searchCol)); - filter[0].selectize.on('blur', function() { - $x.hide().trigger('hide'); $input.parent().show(); $input.trigger('blur'); - }); - filter.next('div').css('margin-bottom', 'auto'); - } else if (type === 'character') { - var fun = function() { - searchColumn(i, $input.val()).draw(); - }; - if (server) { - fun = $.fn.dataTable.util.throttle(fun, options.searchDelay); - } - $input.on('input', fun); - } else if (inArray(type, ['number', 'integer', 'date', 'time'])) { - var $x0 = $x; - $x = $x0.children('div').first(); - $x0.css({ - 'background-color': '#fff', - 'border': '1px #ddd solid', - 'border-radius': '4px', - 'padding': '20px 20px 10px 20px' - }); - var $spans = $x0.children('span').css({ - 'margin-top': '10px', - 'white-space': 'nowrap' - }); - var $span1 = $spans.first(), $span2 = $spans.last(); - var r1 = +$x.data('min'), r2 = +$x.data('max'); - // when the numbers are too small or have many decimal places, the - // slider may have numeric precision problems (#150) - var scale = Math.pow(10, Math.max(0, +$x.data('scale') || 0)); - r1 = Math.round(r1 * scale); r2 = Math.round(r2 * scale); - var scaleBack = function(x, scale) { - if (scale === 1) return x; - var d = Math.round(Math.log(scale) / Math.log(10)); - // to avoid problems like 3.423/100 -> 0.034230000000000003 - return (x / scale).toFixed(d); - }; - var slider_min = function() { - return filter.noUiSlider('options').range.min; - }; - var slider_max = function() { - return filter.noUiSlider('options').range.max; - }; - $input.on({ - focus: function() { - $x0.show().trigger('show'); - // first, make sure the slider div leaves at least 20px between - // the two (slider value) span's - $x0.width(Math.max(160, $span1.outerWidth() + $span2.outerWidth() + 20)); - // then, if the input is really wide, make the slider the same - // width as the input - if ($x0.outerWidth() < $input.outerWidth()) { - $x0.outerWidth($input.outerWidth()); - } - // make sure the slider div does not reach beyond the right margin - if ($(window).width() < $x0.offset().left + $x0.width()) { - $x0.offset({ - 'left': $input.offset().left + $input.outerWidth() - $x0.outerWidth() - }); - } - }, - blur: function() { - $x0.hide().trigger('hide'); - }, - input: function() { - if ($input.val() === '') filter.val([slider_min(), slider_max()]); - }, - change: function() { - var v = $input.val().replace(/\s/g, ''); - if (v === '') return; - v = v.split('...'); - if (v.length !== 2) { - $input.parent().addClass('has-error'); - return; - } - if (v[0] === '') v[0] = slider_min(); - if (v[1] === '') v[1] = slider_max(); - $input.parent().removeClass('has-error'); - // treat date as UTC time at midnight - var strTime = function(x) { - var s = type === 'date' ? 'T00:00:00Z' : ''; - var t = new Date(x + s).getTime(); - // add 10 minutes to date since it does not hurt the date, and - // it helps avoid the tricky floating point arithmetic problems, - // e.g. sometimes the date may be a few milliseconds earlier - // than the midnight due to precision problems in noUiSlider - return type === 'date' ? t + 3600000 : t; - }; - if (inArray(type, ['date', 'time'])) { - v[0] = strTime(v[0]); - v[1] = strTime(v[1]); - } - if (v[0] != slider_min()) v[0] *= scale; - if (v[1] != slider_max()) v[1] *= scale; - filter.val(v); - } - }); - var formatDate = function(d, isoFmt) { - d = scaleBack(d, scale); - if (type === 'number') return d; - if (type === 'integer') return parseInt(d); - var x = new Date(+d); - var fmt = ('filterDateFmt' in data) ? data.filterDateFmt[i] : undefined; - if (fmt !== undefined && isoFmt === false) return x[fmt.method].apply(x, fmt.params); - if (type === 'date') { - var pad0 = function(x) { - return ('0' + x).substr(-2, 2); - }; - return x.getUTCFullYear() + '-' + pad0(1 + x.getUTCMonth()) - + '-' + pad0(x.getUTCDate()); - } else { - return x.toISOString(); - } - }; - var opts = type === 'date' ? { step: 60 * 60 * 1000 } : - type === 'integer' ? { step: 1 } : {}; - filter = $x.noUiSlider($.extend({ - start: [r1, r2], - range: {min: r1, max: r2}, - connect: true - }, opts)); - if (scale > 1) (function() { - var t1 = r1, t2 = r2; - var val = filter.val(); - while (val[0] > r1 || val[1] < r2) { - if (val[0] > r1) { - t1 -= val[0] - r1; - } - if (val[1] < r2) { - t2 += r2 - val[1]; - } - filter = $x.noUiSlider($.extend({ - start: [t1, t2], - range: {min: t1, max: t2}, - connect: true - }, opts), true); - val = filter.val(); - } - r1 = t1; r2 = t2; - })(); - var updateSliderText = function(v1, v2) { - $span1.text(formatDate(v1, false)); $span2.text(formatDate(v2, false)); - }; - updateSliderText(r1, r2); - var updateSlider = function(e) { - var val = filter.val(); - // turn off filter if in full range - $td.data('filter', val[0] > slider_min() || val[1] < slider_max()); - var v1 = formatDate(val[0]), v2 = formatDate(val[1]), ival; - if ($td.data('filter')) { - ival = v1 + ' ... ' + v2; - $input.attr('title', ival).val(ival).trigger('input'); - } else { - $input.attr('title', '').val(''); - } - updateSliderText(val[0], val[1]); - if (e.type === 'slide') return; // no searching when sliding only - if (server) { - table.column(i).search($td.data('filter') ? ival : '').draw(); - return; - } - table.draw(); - }; - filter.on({ - set: updateSlider, - slide: updateSlider - }); - } - - // server-side processing will be handled by R (or whatever server - // language you use); the following code is only needed for client-side - // processing - if (server) { - // if a search string has been pre-set, search now - if (searchCol) searchColumn(i, searchCol).draw(); - return; - } - - var customFilter = function(settings, data, dataIndex) { - // there is no way to attach a search function to a specific table, - // and we need to make sure a global search function is not applied to - // all tables (i.e. a range filter in a previous table should not be - // applied to the current table); we use the settings object to - // determine if we want to perform searching on the current table, - // since settings.sTableId will be different to different tables - if (table.settings()[0] !== settings) return true; - // no filter on this column or no need to filter this column - if (typeof filter === 'undefined' || !$td.data('filter')) return true; - - var r = filter.val(), v, r0, r1; - var i_data = function(i) { - if (!colReorderEnabled()) return i; - var order = table.colReorder.order(), k; - for (k = 0; k < order.length; ++k) if (order[k] === i) return k; - return i; // in theory it will never be here... - } - v = data[i_data(i)]; - if (type === 'number' || type === 'integer') { - v = parseFloat(v); - // how to handle NaN? currently exclude these rows - if (isNaN(v)) return(false); - r0 = parseFloat(scaleBack(r[0], scale)) - r1 = parseFloat(scaleBack(r[1], scale)); - if (v >= r0 && v <= r1) return true; - } else if (type === 'date' || type === 'time') { - v = new Date(v); - r0 = new Date(r[0] / scale); r1 = new Date(r[1] / scale); - if (v >= r0 && v <= r1) return true; - } else if (type === 'factor') { - if (r.length === 0 || inArray(v, r)) return true; - } else if (type === 'logical') { - if (r.length === 0) return true; - if (inArray(v === '' ? 'na' : v, r)) return true; - } - return false; - }; - - $.fn.dataTable.ext.search.push(customFilter); - - // search for the preset search strings if it is non-empty - if (searchCol) { - if (inArray(type, ['factor', 'logical'])) { - filter[0].selectize.setValue(JSON.parse(searchCol)); - } else if (type === 'character') { - $input.trigger('input'); - } else if (inArray(type, ['number', 'integer', 'date', 'time'])) { - $input.trigger('change'); - } - } - - }); - - } - - // highlight search keywords - var highlight = function() { - var body = $(table.table().body()); - // removing the old highlighting first - body.unhighlight(); - - // don't highlight the "not found" row, so we get the rows using the api - if (table.rows({ filter: 'applied' }).data().length === 0) return; - // highlight global search keywords - body.highlight($.trim(table.search()).split(/\s+/)); - // then highlight keywords from individual column filters - if (filterRow) filterRow.each(function(i, td) { - var $td = $(td), type = $td.data('type'); - if (type !== 'character') return; - var $input = $td.children('div').first().children('input'); - var column = table.column(i).nodes().to$(), - val = $.trim($input.val()); - if (type !== 'character' || val === '') return; - column.highlight(val.split(/\s+/)); - }); - }; - - if (options.searchHighlight) { - table - .on('draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth', highlight) - .on('destroy', function() { - // remove event handler - table.off('draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth'); - }); - - // Set the option for escaping regex characters in our search string. This will be used - // for all future matching. - jQuery.fn.highlight.options.escapeRegex = (!options.search || !options.search.regex); - - // initial highlight for state saved conditions and initial states - highlight(); - } - - // run the callback function on the table instance - if (typeof data.callback === 'function') data.callback(table); - - // double click to edit the cell, row, column, or all cells - if (data.editable) table.on('dblclick.dt', 'tbody td', function(e) { - // only bring up the editor when the cell itself is dbclicked, and ignore - // other dbclick events bubbled up (e.g. from the ) - if (e.target !== this) return; - var target = [], immediate = false; - switch (data.editable.target) { - case 'cell': - target = [this]; - immediate = true; // edit will take effect immediately - break; - case 'row': - target = table.cells(table.cell(this).index().row, '*').nodes(); - break; - case 'column': - target = table.cells('*', table.cell(this).index().column).nodes(); - break; - case 'all': - target = table.cells().nodes(); - break; - default: - throw 'The editable parameter must be "cell", "row", "column", or "all"'; - } - var disableCols = data.editable.disable ? data.editable.disable.columns : null; - for (var i = 0; i < target.length; i++) { - (function(cell, current) { - var $cell = $(cell), html = $cell.html(); - var _cell = table.cell(cell), value = _cell.data(); - var $input = $(''), changed = false; - if (!immediate) { - $cell.data('input', $input).data('html', html); - $input.attr('title', 'Hit Ctrl+Enter to finish editing, or Esc to cancel'); - } - $input.val(value); - if (inArray(_cell.index().column, disableCols)) { - $input.attr('readonly', '').css('filter', 'invert(25%)'); - } - $cell.empty().append($input); - if (cell === current) $input.focus(); - $input.css('width', '100%'); - - if (immediate) $input.on('change', function() { - changed = true; - var valueNew = $input.val(); - if (valueNew != value) { - _cell.data(valueNew); - if (HTMLWidgets.shinyMode) { - changeInput('cell_edit', [cellInfo(cell)], 'DT.cellInfo', null, {priority: "event"}); - } - // for server-side processing, users have to call replaceData() to update the table - if (!server) table.draw(false); - } else { - $cell.html(html); - } - $input.remove(); - }).on('blur', function() { - if (!changed) $input.trigger('change'); - }).on('keyup', function(e) { - // hit Escape to cancel editing - if (e.keyCode === 27) $input.trigger('blur'); - }); - - // bulk edit (row, column, or all) - if (!immediate) $input.on('keyup', function(e) { - var removeInput = function($cell, restore) { - $cell.data('input').remove(); - if (restore) $cell.html($cell.data('html')); - } - if (e.keyCode === 27) { - for (var i = 0; i < target.length; i++) { - removeInput($(target[i]), true); - } - } else if (e.keyCode === 13 && e.ctrlKey) { - // Ctrl + Enter - var cell, $cell, _cell, cellData = []; - for (var i = 0; i < target.length; i++) { - cell = target[i]; $cell = $(cell); _cell = table.cell(cell); - _cell.data($cell.data('input').val()); - HTMLWidgets.shinyMode && cellData.push(cellInfo(cell)); - removeInput($cell, false); - } - if (HTMLWidgets.shinyMode) { - changeInput('cell_edit', cellData, 'DT.cellInfo', null, {priority: "event"}); - } - if (!server) table.draw(false); - } - }); - })(target[i], this); - } - }); - - // interaction with shiny - if (!HTMLWidgets.shinyMode && !crosstalkOptions.group) return; - - var methods = {}; - var shinyData = {}; - - methods.updateCaption = function(caption) { - if (!caption) return; - $table.children('caption').replaceWith(caption); - } - - // register clear functions to remove input values when the table is removed - instance.clearInputs = {}; - - var changeInput = function(id, value, type, noCrosstalk, opts) { - var event = id; - id = el.id + '_' + id; - if (type) id = id + ':' + type; - // do not update if the new value is the same as old value - if (event !== 'cell_edit' && !/_clicked$/.test(event) && shinyData.hasOwnProperty(id) && shinyData[id] === JSON.stringify(value)) - return; - shinyData[id] = JSON.stringify(value); - if (HTMLWidgets.shinyMode && Shiny.setInputValue) { - Shiny.setInputValue(id, value, opts); - if (!instance.clearInputs[id]) instance.clearInputs[id] = function() { - Shiny.setInputValue(id, null); - } - } - - // HACK - if (event === "rows_selected" && !noCrosstalk) { - if (crosstalkOptions.group) { - var keys = crosstalkOptions.key; - var selectedKeys = null; - if (value) { - selectedKeys = []; - for (var i = 0; i < value.length; i++) { - // The value array's contents use 1-based row numbers, so we must - // convert to 0-based before indexing into the keys array. - selectedKeys.push(keys[value[i] - 1]); - } - } - instance.ctselectHandle.set(selectedKeys); - } - } - }; - - var addOne = function(x) { - return x.map(function(i) { return 1 + i; }); - }; - - var unique = function(x) { - var ux = []; - $.each(x, function(i, el){ - if ($.inArray(el, ux) === -1) ux.push(el); - }); - return ux; - } - - // change the row index of a cell - var tweakCellIndex = function(cell) { - var info = cell.index(); - // some cell may not be valid. e.g, #759 - // when using the RowGroup extension, datatables will - // generate the row label and the cells are not part of - // the data thus contain no row/col info - if (info === undefined) - return {row: null, col: null}; - if (server) { - info.row = DT_rows_current[info.row]; - } else { - info.row += 1; - } - return {row: info.row, col: info.column}; - } - - var cleanSelectedValues = function() { - changeInput('rows_selected', []); - changeInput('columns_selected', []); - changeInput('cells_selected', transposeArray2D([]), 'shiny.matrix'); - } - // #828 we should clean the selection on the server-side when the table reloads - cleanSelectedValues(); - - // a flag to indicates if select extension is initialized or not - var flagSelectExt = table.settings()[0]._select !== undefined; - // the Select extension should only be used in the client mode and - // when the selection.mode is set to none - if (data.selection.mode === 'none' && !server && flagSelectExt) { - var updateRowsSelected = function() { - var rows = table.rows({selected: true}); - var selected = []; - $.each(rows.indexes().toArray(), function(i, v) { - selected.push(v + 1); - }); - changeInput('rows_selected', selected); - } - var updateColsSelected = function() { - var columns = table.columns({selected: true}); - changeInput('columns_selected', columns.indexes().toArray()); - } - var updateCellsSelected = function() { - var cells = table.cells({selected: true}); - var selected = []; - cells.every(function() { - var row = this.index().row; - var col = this.index().column; - selected = selected.concat([[row + 1, col]]); - }); - changeInput('cells_selected', transposeArray2D(selected), 'shiny.matrix'); - } - table.on('select deselect', function(e, dt, type, indexes) { - updateRowsSelected(); - updateColsSelected(); - updateCellsSelected(); - }) - } - - var selMode = data.selection.mode, selTarget = data.selection.target; - var selDisable = data.selection.selectable === false; - if (inArray(selMode, ['single', 'multiple'])) { - var selClass = inArray(data.style, ['bootstrap', 'bootstrap4']) ? 'active' : 'selected'; - // selected1: row indices; selected2: column indices - var initSel = function(x) { - if (x === null || typeof x === 'boolean' || selTarget === 'cell') { - return {rows: [], cols: []}; - } else if (selTarget === 'row') { - return {rows: $.makeArray(x), cols: []}; - } else if (selTarget === 'column') { - return {rows: [], cols: $.makeArray(x)}; - } else if (selTarget === 'row+column') { - return {rows: $.makeArray(x.rows), cols: $.makeArray(x.cols)}; - } - } - var selected = data.selection.selected; - var selected1 = initSel(selected).rows, selected2 = initSel(selected).cols; - // selectable should contain either all positive or all non-positive values, not both - // positive values indicate "selectable" while non-positive values means "nonselectable" - // the assertion is performed on R side. (only column indicides could be zero which indicates - // the row name) - var selectable = data.selection.selectable; - var selectable1 = initSel(selectable).rows, selectable2 = initSel(selectable).cols; - - // After users reorder the rows or filter the table, we cannot use the table index - // directly. Instead, we need this function to find out the rows between the two clicks. - // If user filter the table again between the start click and the end click, the behavior - // would be undefined, but it should not be a problem. - var shiftSelRowsIndex = function(start, end) { - var indexes = server ? DT_rows_all : table.rows({ search: 'applied' }).indexes().toArray(); - start = indexes.indexOf(start); end = indexes.indexOf(end); - // if start is larger than end, we need to swap - if (start > end) { - var tmp = end; end = start; start = tmp; - } - return indexes.slice(start, end + 1); - } - - var serverRowIndex = function(clientRowIndex) { - return server ? DT_rows_current[clientRowIndex] : clientRowIndex + 1; - } - - // row, column, or cell selection - var lastClickedRow; - if (inArray(selTarget, ['row', 'row+column'])) { - // Get the current selected rows. It will also - // update the selected1's value based on the current row selection state - // Note we can't put this function inside selectRows() directly, - // the reason is method.selectRows() will override selected1's value but this - // function will add rows to selected1 (keep the existing selection), which is - // inconsistent with column and cell selection. - var selectedRows = function() { - var rows = table.rows('.' + selClass); - var idx = rows.indexes().toArray(); - if (!server) { - selected1 = addOne(idx); - return selected1; - } - idx = idx.map(function(i) { - return DT_rows_current[i]; - }); - selected1 = selMode === 'multiple' ? unique(selected1.concat(idx)) : idx; - return selected1; - } - // Change selected1's value based on selectable1, then refresh the row state - var onlyKeepSelectableRows = function() { - if (selDisable) { // users can't select; useful when only want backend select - selected1 = []; - return; - } - if (selectable1.length === 0) return; - var nonselectable = selectable1[0] <= 0; - if (nonselectable) { - // should make selectable1 positive - selected1 = $(selected1).not(selectable1.map(function(i) { return -i; })).get(); - } else { - selected1 = $(selected1).filter(selectable1).get(); - } - } - // Change selected1's value based on selectable1, then - // refresh the row selection state according to values in selected1 - var selectRows = function(ignoreSelectable) { - if (!ignoreSelectable) onlyKeepSelectableRows(); - table.$('tr.' + selClass).removeClass(selClass); - if (selected1.length === 0) return; - if (server) { - table.rows({page: 'current'}).every(function() { - if (inArray(DT_rows_current[this.index()], selected1)) { - $(this.node()).addClass(selClass); - } - }); - } else { - var selected0 = selected1.map(function(i) { return i - 1; }); - $(table.rows(selected0).nodes()).addClass(selClass); - } - } - table.on('mousedown.dt', 'tbody tr', function(e) { - var $this = $(this), thisRow = table.row(this); - if (selMode === 'multiple') { - if (e.shiftKey && lastClickedRow !== undefined) { - // select or de-select depends on the last clicked row's status - var flagSel = !$this.hasClass(selClass); - var crtClickedRow = serverRowIndex(thisRow.index()); - if (server) { - var rowsIndex = shiftSelRowsIndex(lastClickedRow, crtClickedRow); - // update current page's selClass - rowsIndex.map(function(i) { - var rowIndex = DT_rows_current.indexOf(i); - if (rowIndex >= 0) { - var row = table.row(rowIndex).nodes().to$(); - var flagRowSel = !row.hasClass(selClass); - if (flagSel === flagRowSel) row.toggleClass(selClass); - } - }); - // update selected1 - if (flagSel) { - selected1 = unique(selected1.concat(rowsIndex)); - } else { - selected1 = selected1.filter(function(index) { - return !inArray(index, rowsIndex); - }); - } - } else { - // js starts from 0 - shiftSelRowsIndex(lastClickedRow - 1, crtClickedRow - 1).map(function(value) { - var row = table.row(value).nodes().to$(); - var flagRowSel = !row.hasClass(selClass); - if (flagSel === flagRowSel) row.toggleClass(selClass); - }); - } - e.preventDefault(); - } else { - $this.toggleClass(selClass); - } - } else { - if ($this.hasClass(selClass)) { - $this.removeClass(selClass); - } else { - table.$('tr.' + selClass).removeClass(selClass); - $this.addClass(selClass); - } - } - if (server && !$this.hasClass(selClass)) { - var id = DT_rows_current[thisRow.index()]; - // remove id from selected1 since its class .selected has been removed - if (inArray(id, selected1)) selected1.splice($.inArray(id, selected1), 1); - } - selectedRows(); // update selected1's value based on selClass - selectRows(false); // only keep the selectable rows - changeInput('rows_selected', selected1); - changeInput('row_last_clicked', serverRowIndex(thisRow.index()), null, null, {priority: 'event'}); - lastClickedRow = serverRowIndex(thisRow.index()); - }); - selectRows(false); // in case users have specified pre-selected rows - // restore selected rows after the table is redrawn (e.g. sort/search/page); - // client-side tables will preserve the selections automatically; for - // server-side tables, we have to *real* row indices are in `selected1` - changeInput('rows_selected', selected1); - if (server) table.on('draw.dt', function(e) { selectRows(false); }); - methods.selectRows = function(selected, ignoreSelectable) { - selected1 = $.makeArray(selected); - selectRows(ignoreSelectable); - changeInput('rows_selected', selected1); - } - } - - if (inArray(selTarget, ['column', 'row+column'])) { - if (selTarget === 'row+column') { - $(table.columns().footer()).css('cursor', 'pointer'); - } - // update selected2's value based on selectable2 - var onlyKeepSelectableCols = function() { - if (selDisable) { // users can't select; useful when only want backend select - selected2 = []; - return; - } - if (selectable2.length === 0) return; - var nonselectable = selectable2[0] <= 0; - if (nonselectable) { - // need to make selectable2 positive - selected2 = $(selected2).not(selectable2.map(function(i) { return -i; })).get(); - } else { - selected2 = $(selected2).filter(selectable2).get(); - } - } - // update selected2 and then - // refresh the col selection state according to values in selected2 - var selectCols = function(ignoreSelectable) { - if (!ignoreSelectable) onlyKeepSelectableCols(); - // if selected2 is not a valide index (e.g., larger than the column number) - // table.columns(selected2) will fail and result in a blank table - // this is different from the table.rows(), where the out-of-range indexes - // doesn't affect at all - selected2 = $(selected2).filter(table.columns().indexes()).get(); - table.columns().nodes().flatten().to$().removeClass(selClass); - if (selected2.length > 0) - table.columns(selected2).nodes().flatten().to$().addClass(selClass); - } - var callback = function() { - var colIdx = selTarget === 'column' ? table.cell(this).index().column : - $.inArray(this, table.columns().footer()), - thisCol = $(table.column(colIdx).nodes()); - if (colIdx === -1) return; - if (thisCol.hasClass(selClass)) { - thisCol.removeClass(selClass); - selected2.splice($.inArray(colIdx, selected2), 1); - } else { - if (selMode === 'single') $(table.cells().nodes()).removeClass(selClass); - thisCol.addClass(selClass); - selected2 = selMode === 'single' ? [colIdx] : unique(selected2.concat([colIdx])); - } - selectCols(false); // update selected2 based on selectable - changeInput('columns_selected', selected2); - } - if (selTarget === 'column') { - $(table.table().body()).on('click.dt', 'td', callback); - } else { - $(table.table().footer()).on('click.dt', 'tr th', callback); - } - selectCols(false); // in case users have specified pre-selected columns - changeInput('columns_selected', selected2); - if (server) table.on('draw.dt', function(e) { selectCols(false); }); - methods.selectColumns = function(selected, ignoreSelectable) { - selected2 = $.makeArray(selected); - selectCols(ignoreSelectable); - changeInput('columns_selected', selected2); - } - } - - if (selTarget === 'cell') { - var selected3 = [], selectable3 = []; - if (selected !== null) selected3 = selected; - if (selectable !== null && typeof selectable !== 'boolean') selectable3 = selectable; - var findIndex = function(ij, sel) { - for (var i = 0; i < sel.length; i++) { - if (ij[0] === sel[i][0] && ij[1] === sel[i][1]) return i; - } - return -1; - } - // Change selected3's value based on selectable3, then refresh the cell state - var onlyKeepSelectableCells = function() { - if (selDisable) { // users can't select; useful when only want backend select - selected3 = []; - return; - } - if (selectable3.length === 0) return; - var nonselectable = selectable3[0][0] <= 0; - var out = []; - if (nonselectable) { - selected3.map(function(ij) { - // should make selectable3 positive - if (findIndex([-ij[0], -ij[1]], selectable3) === -1) { out.push(ij); } - }); - } else { - selected3.map(function(ij) { - if (findIndex(ij, selectable3) > -1) { out.push(ij); } - }); - } - selected3 = out; - } - // Change selected3's value based on selectable3, then - // refresh the cell selection state according to values in selected3 - var selectCells = function(ignoreSelectable) { - if (!ignoreSelectable) onlyKeepSelectableCells(); - table.$('td.' + selClass).removeClass(selClass); - if (selected3.length === 0) return; - if (server) { - table.cells({page: 'current'}).every(function() { - var info = tweakCellIndex(this); - if (findIndex([info.row, info.col], selected3) > -1) - $(this.node()).addClass(selClass); - }); - } else { - selected3.map(function(ij) { - $(table.cell(ij[0] - 1, ij[1]).node()).addClass(selClass); - }); - } - }; - table.on('click.dt', 'tbody td', function() { - var $this = $(this), info = tweakCellIndex(table.cell(this)); - if ($this.hasClass(selClass)) { - $this.removeClass(selClass); - selected3.splice(findIndex([info.row, info.col], selected3), 1); - } else { - if (selMode === 'single') $(table.cells().nodes()).removeClass(selClass); - $this.addClass(selClass); - selected3 = selMode === 'single' ? [[info.row, info.col]] : - unique(selected3.concat([[info.row, info.col]])); - } - selectCells(false); // must call this to update selected3 based on selectable3 - changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); - }); - selectCells(false); // in case users have specified pre-selected columns - changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); - - if (server) table.on('draw.dt', function(e) { selectCells(false); }); - methods.selectCells = function(selected, ignoreSelectable) { - selected3 = selected ? selected : []; - selectCells(ignoreSelectable); - changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); - } - } - } - - // expose some table info to Shiny - var updateTableInfo = function(e, settings) { - // TODO: is anyone interested in the page info? - // changeInput('page_info', table.page.info()); - var updateRowInfo = function(id, modifier) { - var idx; - if (server) { - idx = modifier.page === 'current' ? DT_rows_current : DT_rows_all; - } else { - var rows = table.rows($.extend({ - search: 'applied', - page: 'all' - }, modifier)); - idx = addOne(rows.indexes().toArray()); - } - changeInput('rows' + '_' + id, idx); - }; - updateRowInfo('current', {page: 'current'}); - updateRowInfo('all', {}); - } - table.on('draw.dt', updateTableInfo); - updateTableInfo(); - - // state info - table.on('draw.dt column-visibility.dt', function() { - changeInput('state', table.state()); - }); - changeInput('state', table.state()); - - // search info - var updateSearchInfo = function() { - changeInput('search', table.search()); - if (filterRow) changeInput('search_columns', filterRow.toArray().map(function(td) { - return $(td).find('input').first().val(); - })); - } - table.on('draw.dt', updateSearchInfo); - updateSearchInfo(); - - var cellInfo = function(thiz) { - var info = tweakCellIndex(table.cell(thiz)); - info.value = table.cell(thiz).data(); - return info; - } - // the current cell clicked on - table.on('click.dt', 'tbody td', function() { - changeInput('cell_clicked', cellInfo(this), null, null, {priority: 'event'}); - }) - changeInput('cell_clicked', {}); - - // do not trigger table selection when clicking on links unless they have classes - table.on('click.dt', 'tbody td a', function(e) { - if (this.className === '') e.stopPropagation(); - }); - - methods.addRow = function(data, rowname, resetPaging) { - var n = table.columns().indexes().length, d = n - data.length; - if (d === 1) { - data = rowname.concat(data) - } else if (d !== 0) { - console.log(data); - console.log(table.columns().indexes()); - throw 'New data must be of the same length as current data (' + n + ')'; - }; - table.row.add(data).draw(resetPaging); - } - - methods.updateSearch = function(keywords) { - if (keywords.global !== null) - $(table.table().container()).find('input[type=search]').first() - .val(keywords.global).trigger('input'); - var columns = keywords.columns; - if (!filterRow || columns === null) return; - filterRow.toArray().map(function(td, i) { - var v = typeof columns === 'string' ? columns : columns[i]; - if (typeof v === 'undefined') { - console.log('The search keyword for column ' + i + ' is undefined') - return; - } - $(td).find('input').first().val(v); - searchColumn(i, v); - }); - table.draw(); - } - - methods.hideCols = function(hide, reset) { - if (reset) table.columns().visible(true, false); - table.columns(hide).visible(false); - } - - methods.showCols = function(show, reset) { - if (reset) table.columns().visible(false, false); - table.columns(show).visible(true); - } - - methods.colReorder = function(order, origOrder) { - table.colReorder.order(order, origOrder); - } - - methods.selectPage = function(page) { - if (table.page.info().pages < page || page < 1) { - throw 'Selected page is out of range'; - }; - table.page(page - 1).draw(false); - } - - methods.reloadData = function(resetPaging, clearSelection) { - // empty selections first if necessary - if (methods.selectRows && inArray('row', clearSelection)) methods.selectRows([]); - if (methods.selectColumns && inArray('column', clearSelection)) methods.selectColumns([]); - if (methods.selectCells && inArray('cell', clearSelection)) methods.selectCells([]); - table.ajax.reload(null, resetPaging); - } - - table.shinyMethods = methods; - }, - resize: function(el, width, height, instance) { - if (instance.data) this.renderValue(el, instance.data, instance); - - // dynamically adjust height if fillContainer = TRUE - if (instance.fillContainer) - this.fillAvailableHeight(el, height); - - this.adjustWidth(el); - }, - - // dynamically set the scroll body to fill available height - // (used with fillContainer = TRUE) - fillAvailableHeight: function(el, availableHeight) { - - // see how much of the table is occupied by header/footer elements - // and use that to compute a target scroll body height - var dtWrapper = $(el).find('div.dataTables_wrapper'); - var dtScrollBody = $(el).find($('div.dataTables_scrollBody')); - var framingHeight = dtWrapper.innerHeight() - dtScrollBody.innerHeight(); - var scrollBodyHeight = availableHeight - framingHeight; - - // set the height - dtScrollBody.height(scrollBodyHeight + 'px'); - }, - - // adjust the width of columns; remove the hard-coded widths on table and the - // scroll header when scrollX/Y are enabled - adjustWidth: function(el) { - var $el = $(el), table = $el.data('datatable'); - if (table) table.columns.adjust(); - $el.find('.dataTables_scrollHeadInner').css('width', '') - .children('table').css('margin-left', ''); - } -}); - - if (!HTMLWidgets.shinyMode) return; - - Shiny.addCustomMessageHandler('datatable-calls', function(data) { - var id = data.id; - var el = document.getElementById(id); - var table = el ? $(el).data('datatable') : null; - if (!table) { - console.log("Couldn't find table with id " + id); - return; - } - - var methods = table.shinyMethods, call = data.call; - if (methods[call.method]) { - methods[call.method].apply(table, call.args); - } else { - console.log("Unknown method " + call.method); - } - }); - -})(); diff --git a/docs/articles/xportr_files/datatables-binding-0.18/datatables.js b/docs/articles/xportr_files/datatables-binding-0.18/datatables.js deleted file mode 100644 index e4148252..00000000 --- a/docs/articles/xportr_files/datatables-binding-0.18/datatables.js +++ /dev/null @@ -1,1412 +0,0 @@ -(function() { - -// some helper functions: using a global object DTWidget so that it can be used -// in JS() code, e.g. datatable(options = list(foo = JS('code'))); unlike R's -// dynamic scoping, when 'code' is eval()'ed, JavaScript does not know objects -// from the "parent frame", e.g. JS('DTWidget') will not work unless it was made -// a global object -var DTWidget = {}; - -// 123456666.7890 -> 123,456,666.7890 -var markInterval = function(d, digits, interval, mark, decMark, precision) { - x = precision ? d.toPrecision(digits) : d.toFixed(digits); - if (!/^-?[\d.]+$/.test(x)) return x; - var xv = x.split('.'); - if (xv.length > 2) return x; // should have at most one decimal point - xv[0] = xv[0].replace(new RegExp('\\B(?=(\\d{' + interval + '})+(?!\\d))', 'g'), mark); - return xv.join(decMark); -}; - -DTWidget.formatCurrency = function(data, currency, digits, interval, mark, decMark, before) { - var d = parseFloat(data); - if (isNaN(d)) return ''; - var res = markInterval(d, digits, interval, mark, decMark); - res = before ? (/^-/.test(res) ? '-' + currency + res.replace(/^-/, '') : currency + res) : - res + currency; - return res; -}; - -DTWidget.formatString = function(data, prefix, suffix) { - var d = data; - if (d === null) return ''; - return prefix + d + suffix; -}; - -DTWidget.formatPercentage = function(data, digits, interval, mark, decMark) { - var d = parseFloat(data); - if (isNaN(d)) return ''; - return markInterval(d * 100, digits, interval, mark, decMark) + '%'; -}; - -DTWidget.formatRound = function(data, digits, interval, mark, decMark) { - var d = parseFloat(data); - if (isNaN(d)) return ''; - return markInterval(d, digits, interval, mark, decMark); -}; - -DTWidget.formatSignif = function(data, digits, interval, mark, decMark) { - var d = parseFloat(data); - if (isNaN(d)) return ''; - return markInterval(d, digits, interval, mark, decMark, true); -}; - -DTWidget.formatDate = function(data, method, params) { - var d = data; - if (d === null) return ''; - // (new Date('2015-10-28')).toDateString() may return 2015-10-27 because the - // actual time created could be like 'Tue Oct 27 2015 19:00:00 GMT-0500 (CDT)', - // i.e. the date-only string is treated as UTC time instead of local time - if ((method === 'toDateString' || method === 'toLocaleDateString') && /^\d{4,}\D\d{2}\D\d{2}$/.test(d)) { - d = d.split(/\D/); - d = new Date(d[0], d[1] - 1, d[2]); - } else { - d = new Date(d); - } - return d[method].apply(d, params); -}; - -window.DTWidget = DTWidget; - -var transposeArray2D = function(a) { - return a.length === 0 ? a : HTMLWidgets.transposeArray2D(a); -}; - -var crosstalkPluginsInstalled = false; - -function maybeInstallCrosstalkPlugins() { - if (crosstalkPluginsInstalled) - return; - crosstalkPluginsInstalled = true; - - $.fn.dataTable.ext.afnFiltering.push( - function(oSettings, aData, iDataIndex) { - var ctfilter = oSettings.nTable.ctfilter; - if (ctfilter && !ctfilter[iDataIndex]) - return false; - - var ctselect = oSettings.nTable.ctselect; - if (ctselect && !ctselect[iDataIndex]) - return false; - - return true; - } - ); -} - -HTMLWidgets.widget({ - name: "datatables", - type: "output", - renderOnNullValue: true, - initialize: function(el, width, height) { - $(el).html(' '); - return { - data: null, - ctfilterHandle: new crosstalk.FilterHandle(), - ctfilterSubscription: null, - ctselectHandle: new crosstalk.SelectionHandle(), - ctselectSubscription: null - }; - }, - renderValue: function(el, data, instance) { - if (el.offsetWidth === 0 || el.offsetHeight === 0) { - instance.data = data; - return; - } - instance.data = null; - var $el = $(el); - $el.empty(); - - if (data === null) { - $el.append(' '); - // clear previous Shiny inputs (if any) - for (var i in instance.clearInputs) instance.clearInputs[i](); - instance.clearInputs = {}; - return; - } - - var crosstalkOptions = data.crosstalkOptions; - if (!crosstalkOptions) crosstalkOptions = { - 'key': null, 'group': null - }; - if (crosstalkOptions.group) { - maybeInstallCrosstalkPlugins(); - instance.ctfilterHandle.setGroup(crosstalkOptions.group); - instance.ctselectHandle.setGroup(crosstalkOptions.group); - } - - // if we are in the viewer then we always want to fillContainer and - // and autoHideNavigation (unless the user has explicitly set these) - if (window.HTMLWidgets.viewerMode) { - if (!data.hasOwnProperty("fillContainer")) - data.fillContainer = true; - if (!data.hasOwnProperty("autoHideNavigation")) - data.autoHideNavigation = true; - } - - // propagate fillContainer to instance (so we have it in resize) - instance.fillContainer = data.fillContainer; - - var cells = data.data; - - if (cells instanceof Array) cells = transposeArray2D(cells); - - $el.append(data.container); - var $table = $el.find('table'); - if (data.class) $table.addClass(data.class); - if (data.caption) $table.prepend(data.caption); - - if (!data.selection) data.selection = { - mode: 'none', selected: null, target: 'row', selectable: null - }; - if (HTMLWidgets.shinyMode && data.selection.mode !== 'none' && - data.selection.target === 'row+column') { - if ($table.children('tfoot').length === 0) { - $table.append($('')); - $table.find('thead tr').clone().appendTo($table.find('tfoot')); - } - } - - // column filters - var filterRow; - switch (data.filter) { - case 'top': - $table.children('thead').append(data.filterHTML); - filterRow = $table.find('thead tr:last td'); - break; - case 'bottom': - if ($table.children('tfoot').length === 0) { - $table.append($('')); - } - $table.children('tfoot').prepend(data.filterHTML); - filterRow = $table.find('tfoot tr:first td'); - break; - } - - var options = { searchDelay: 1000 }; - if (cells !== null) $.extend(options, { - data: cells - }); - - // options for fillContainer - var bootstrapActive = typeof($.fn.popover) != 'undefined'; - if (instance.fillContainer) { - - // force scrollX/scrollY and turn off autoWidth - options.scrollX = true; - options.scrollY = "100px"; // can be any value, we'll adjust below - - // if we aren't paginating then move around the info/filter controls - // to save space at the bottom and rephrase the info callback - if (data.options.paging === false) { - - // we know how to do this cleanly for bootstrap, not so much - // for other themes/layouts - if (bootstrapActive) { - options.dom = "<'row'<'col-sm-4'i><'col-sm-8'f>>" + - "<'row'<'col-sm-12'tr>>"; - } - - options.fnInfoCallback = function(oSettings, iStart, iEnd, - iMax, iTotal, sPre) { - return Number(iTotal).toLocaleString() + " records"; - }; - } - } - - // auto hide navigation if requested - // Note, this only works on client-side processing mode as on server-side, - // cells (data.data) is null; In addition, we require the pageLength option - // being provided explicitly to enable this. Despite we may be able to deduce - // the default value of pageLength, it may complicate things so we'd rather - // put this responsiblity to users and warn them on the R side. - if (data.autoHideNavigation === true && data.options.paging !== false) { - // strip all nav if length >= cells - if ((cells instanceof Array) && data.options.pageLength >= cells.length) - options.dom = bootstrapActive ? "<'row'<'col-sm-12'tr>>" : "t"; - // alternatively lean things out for flexdashboard mobile portrait - else if (bootstrapActive && window.FlexDashboard && window.FlexDashboard.isMobilePhone()) - options.dom = "<'row'<'col-sm-12'f>>" + - "<'row'<'col-sm-12'tr>>" + - "<'row'<'col-sm-12'p>>"; - } - - $.extend(true, options, data.options || {}); - - var searchCols = options.searchCols; - if (searchCols) { - searchCols = searchCols.map(function(x) { - return x === null ? '' : x.search; - }); - // FIXME: this means I don't respect the escapeRegex setting - delete options.searchCols; - } - - // server-side processing? - var server = options.serverSide === true; - - // use the dataSrc function to pre-process JSON data returned from R - var DT_rows_all = [], DT_rows_current = []; - if (server && HTMLWidgets.shinyMode && typeof options.ajax === 'object' && - /^session\/[\da-z]+\/dataobj/.test(options.ajax.url) && !options.ajax.dataSrc) { - options.ajax.dataSrc = function(json) { - DT_rows_all = $.makeArray(json.DT_rows_all); - DT_rows_current = $.makeArray(json.DT_rows_current); - var data = json.data; - if (!colReorderEnabled()) return data; - var table = $table.DataTable(), order = table.colReorder.order(), flag = true, i, j, row; - for (i = 0; i < order.length; ++i) if (order[i] !== i) flag = false; - if (flag) return data; - for (i = 0; i < data.length; ++i) { - row = data[i].slice(); - for (j = 0; j < order.length; ++j) data[i][j] = row[order[j]]; - } - return data; - }; - } - - var thiz = this; - if (instance.fillContainer) $table.on('init.dt', function(e) { - thiz.fillAvailableHeight(el, $(el).innerHeight()); - }); - // If the page contains serveral datatables and one of which enables colReorder, - // the table.colReorder.order() function will exist but throws error when called. - // So it seems like the only way to know if colReorder is enabled or not is to - // check the options. - var colReorderEnabled = function() { return "colReorder" in options; }; - var table = $table.DataTable(options); - $el.data('datatable', table); - - // Unregister previous Crosstalk event subscriptions, if they exist - if (instance.ctfilterSubscription) { - instance.ctfilterHandle.off("change", instance.ctfilterSubscription); - instance.ctfilterSubscription = null; - } - if (instance.ctselectSubscription) { - instance.ctselectHandle.off("change", instance.ctselectSubscription); - instance.ctselectSubscription = null; - } - - if (!crosstalkOptions.group) { - $table[0].ctfilter = null; - $table[0].ctselect = null; - } else { - var key = crosstalkOptions.key; - function keysToMatches(keys) { - if (!keys) { - return null; - } else { - var selectedKeys = {}; - for (var i = 0; i < keys.length; i++) { - selectedKeys[keys[i]] = true; - } - var matches = {}; - for (var j = 0; j < key.length; j++) { - if (selectedKeys[key[j]]) - matches[j] = true; - } - return matches; - } - } - - function applyCrosstalkFilter(e) { - $table[0].ctfilter = keysToMatches(e.value); - table.draw(); - } - instance.ctfilterSubscription = instance.ctfilterHandle.on("change", applyCrosstalkFilter); - applyCrosstalkFilter({value: instance.ctfilterHandle.filteredKeys}); - - function applyCrosstalkSelection(e) { - if (e.sender !== instance.ctselectHandle) { - table - .rows('.' + selClass, {search: 'applied'}) - .nodes() - .to$() - .removeClass(selClass); - if (selectedRows) - changeInput('rows_selected', selectedRows(), void 0, true); - } - - if (e.sender !== instance.ctselectHandle && e.value && e.value.length) { - var matches = keysToMatches(e.value); - - // persistent selection with plotly (& leaflet) - var ctOpts = crosstalk.var("plotlyCrosstalkOpts").get() || {}; - if (ctOpts.persistent === true) { - var matches = $.extend(matches, $table[0].ctselect); - } - - $table[0].ctselect = matches; - table.draw(); - } else { - if ($table[0].ctselect) { - $table[0].ctselect = null; - table.draw(); - } - } - } - instance.ctselectSubscription = instance.ctselectHandle.on("change", applyCrosstalkSelection); - // TODO: This next line doesn't seem to work when renderDataTable is used - applyCrosstalkSelection({value: instance.ctselectHandle.value}); - } - - var inArray = function(val, array) { - return $.inArray(val, $.makeArray(array)) > -1; - }; - - // encode + to %2B when searching in the table on server side, because - // shiny::parseQueryString() treats + as spaces, and DataTables does not - // encode + to %2B (or % to %25) when sending the request - var encode_plus = function(x) { - return server ? x.replace(/%/g, '%25').replace(/\+/g, '%2B') : x; - }; - - // search the i-th column - var searchColumn = function(i, value) { - var regex = false, ci = true; - if (options.search) { - regex = options.search.regex, - ci = options.search.caseInsensitive !== false; - } - return table.column(i).search(encode_plus(value), regex, !regex, ci); - }; - - if (data.filter !== 'none') { - - filterRow.each(function(i, td) { - - var $td = $(td), type = $td.data('type'), filter; - var $input = $td.children('div').first().children('input'); - $input.prop('disabled', !table.settings()[0].aoColumns[i].bSearchable || type === 'disabled'); - $input.on('input blur', function() { - $input.next('span').toggle(Boolean($input.val())); - }); - // Bootstrap sets pointer-events to none and we won't be able to click - // the clear button - $input.next('span').css('pointer-events', 'auto').hide().click(function() { - $(this).hide().prev('input').val('').trigger('input').focus(); - }); - var searchCol; // search string for this column - if (searchCols && searchCols[i]) { - searchCol = searchCols[i]; - $input.val(searchCol).trigger('input'); - } - var $x = $td.children('div').last(); - - // remove the overflow: hidden attribute of the scrollHead - // (otherwise the scrolling table body obscures the filters) - // The workaround and the discussion from - // https://github.com/rstudio/DT/issues/554#issuecomment-518007347 - // Otherwise the filter selection will not be anchored to the values - // when the columns number is many and scrollX is enabled. - var scrollHead = $(el).find('.dataTables_scrollHead,.dataTables_scrollFoot'); - var cssOverflowHead = scrollHead.css('overflow'); - var scrollBody = $(el).find('.dataTables_scrollBody'); - var cssOverflowBody = scrollBody.css('overflow'); - var scrollTable = $(el).find('.dataTables_scroll'); - var cssOverflowTable = scrollTable.css('overflow'); - if (cssOverflowHead === 'hidden') { - $x.on('show hide', function(e) { - if (e.type === 'show') { - scrollHead.css('overflow', 'visible'); - scrollBody.css('overflow', 'visible'); - scrollTable.css('overflow-x', 'scroll'); - } else { - scrollHead.css('overflow', cssOverflowHead); - scrollBody.css('overflow', cssOverflowBody); - scrollTable.css('overflow-x', cssOverflowTable); - } - }); - $x.css('z-index', 25); - } - - if (inArray(type, ['factor', 'logical'])) { - $input.on({ - click: function() { - $input.parent().hide(); $x.show().trigger('show'); filter[0].selectize.focus(); - }, - input: function() { - if ($input.val() === '') filter[0].selectize.setValue([]); - } - }); - var $input2 = $x.children('select'); - filter = $input2.selectize({ - options: $input2.data('options').map(function(v, i) { - return ({text: v, value: v}); - }), - plugins: ['remove_button'], - hideSelected: true, - onChange: function(value) { - if (value === null) value = []; // compatibility with jQuery 3.0 - $input.val(value.length ? JSON.stringify(value) : ''); - if (value.length) $input.trigger('input'); - $input.attr('title', $input.val()); - if (server) { - table.column(i).search(value.length ? encode_plus(JSON.stringify(value)) : '').draw(); - return; - } - // turn off filter if nothing selected - $td.data('filter', value.length > 0); - table.draw(); // redraw table, and filters will be applied - } - }); - if (searchCol) filter[0].selectize.setValue(JSON.parse(searchCol)); - filter[0].selectize.on('blur', function() { - $x.hide().trigger('hide'); $input.parent().show(); $input.trigger('blur'); - }); - filter.next('div').css('margin-bottom', 'auto'); - } else if (type === 'character') { - var fun = function() { - searchColumn(i, $input.val()).draw(); - }; - if (server) { - fun = $.fn.dataTable.util.throttle(fun, options.searchDelay); - } - $input.on('input', fun); - } else if (inArray(type, ['number', 'integer', 'date', 'time'])) { - var $x0 = $x; - $x = $x0.children('div').first(); - $x0.css({ - 'background-color': '#fff', - 'border': '1px #ddd solid', - 'border-radius': '4px', - 'padding': '20px 20px 10px 20px' - }); - var $spans = $x0.children('span').css({ - 'margin-top': '10px', - 'white-space': 'nowrap' - }); - var $span1 = $spans.first(), $span2 = $spans.last(); - var r1 = +$x.data('min'), r2 = +$x.data('max'); - // when the numbers are too small or have many decimal places, the - // slider may have numeric precision problems (#150) - var scale = Math.pow(10, Math.max(0, +$x.data('scale') || 0)); - r1 = Math.round(r1 * scale); r2 = Math.round(r2 * scale); - var scaleBack = function(x, scale) { - if (scale === 1) return x; - var d = Math.round(Math.log(scale) / Math.log(10)); - // to avoid problems like 3.423/100 -> 0.034230000000000003 - return (x / scale).toFixed(d); - }; - var slider_min = function() { - return filter.noUiSlider('options').range.min; - }; - var slider_max = function() { - return filter.noUiSlider('options').range.max; - }; - $input.on({ - focus: function() { - $x0.show().trigger('show'); - // first, make sure the slider div leaves at least 20px between - // the two (slider value) span's - $x0.width(Math.max(160, $span1.outerWidth() + $span2.outerWidth() + 20)); - // then, if the input is really wide, make the slider the same - // width as the input - if ($x0.outerWidth() < $input.outerWidth()) { - $x0.outerWidth($input.outerWidth()); - } - // make sure the slider div does not reach beyond the right margin - if ($(window).width() < $x0.offset().left + $x0.width()) { - $x0.offset({ - 'left': $input.offset().left + $input.outerWidth() - $x0.outerWidth() - }); - } - }, - blur: function() { - $x0.hide().trigger('hide'); - }, - input: function() { - if ($input.val() === '') filter.val([slider_min(), slider_max()]); - }, - change: function() { - var v = $input.val().replace(/\s/g, ''); - if (v === '') return; - v = v.split('...'); - if (v.length !== 2) { - $input.parent().addClass('has-error'); - return; - } - if (v[0] === '') v[0] = slider_min(); - if (v[1] === '') v[1] = slider_max(); - $input.parent().removeClass('has-error'); - // treat date as UTC time at midnight - var strTime = function(x) { - var s = type === 'date' ? 'T00:00:00Z' : ''; - var t = new Date(x + s).getTime(); - // add 10 minutes to date since it does not hurt the date, and - // it helps avoid the tricky floating point arithmetic problems, - // e.g. sometimes the date may be a few milliseconds earlier - // than the midnight due to precision problems in noUiSlider - return type === 'date' ? t + 3600000 : t; - }; - if (inArray(type, ['date', 'time'])) { - v[0] = strTime(v[0]); - v[1] = strTime(v[1]); - } - if (v[0] != slider_min()) v[0] *= scale; - if (v[1] != slider_max()) v[1] *= scale; - filter.val(v); - } - }); - var formatDate = function(d, isoFmt) { - d = scaleBack(d, scale); - if (type === 'number') return d; - if (type === 'integer') return parseInt(d); - var x = new Date(+d); - var fmt = ('filterDateFmt' in data) ? data.filterDateFmt[i] : undefined; - if (fmt !== undefined && isoFmt === false) return x[fmt.method].apply(x, fmt.params); - if (type === 'date') { - var pad0 = function(x) { - return ('0' + x).substr(-2, 2); - }; - return x.getUTCFullYear() + '-' + pad0(1 + x.getUTCMonth()) - + '-' + pad0(x.getUTCDate()); - } else { - return x.toISOString(); - } - }; - var opts = type === 'date' ? { step: 60 * 60 * 1000 } : - type === 'integer' ? { step: 1 } : {}; - filter = $x.noUiSlider($.extend({ - start: [r1, r2], - range: {min: r1, max: r2}, - connect: true - }, opts)); - if (scale > 1) (function() { - var t1 = r1, t2 = r2; - var val = filter.val(); - while (val[0] > r1 || val[1] < r2) { - if (val[0] > r1) { - t1 -= val[0] - r1; - } - if (val[1] < r2) { - t2 += r2 - val[1]; - } - filter = $x.noUiSlider($.extend({ - start: [t1, t2], - range: {min: t1, max: t2}, - connect: true - }, opts), true); - val = filter.val(); - } - r1 = t1; r2 = t2; - })(); - var updateSliderText = function(v1, v2) { - $span1.text(formatDate(v1, false)); $span2.text(formatDate(v2, false)); - }; - updateSliderText(r1, r2); - var updateSlider = function(e) { - var val = filter.val(); - // turn off filter if in full range - $td.data('filter', val[0] > slider_min() || val[1] < slider_max()); - var v1 = formatDate(val[0]), v2 = formatDate(val[1]), ival; - if ($td.data('filter')) { - ival = v1 + ' ... ' + v2; - $input.attr('title', ival).val(ival).trigger('input'); - } else { - $input.attr('title', '').val(''); - } - updateSliderText(val[0], val[1]); - if (e.type === 'slide') return; // no searching when sliding only - if (server) { - table.column(i).search($td.data('filter') ? ival : '').draw(); - return; - } - table.draw(); - }; - filter.on({ - set: updateSlider, - slide: updateSlider - }); - } - - // server-side processing will be handled by R (or whatever server - // language you use); the following code is only needed for client-side - // processing - if (server) { - // if a search string has been pre-set, search now - if (searchCol) searchColumn(i, searchCol).draw(); - return; - } - - var customFilter = function(settings, data, dataIndex) { - // there is no way to attach a search function to a specific table, - // and we need to make sure a global search function is not applied to - // all tables (i.e. a range filter in a previous table should not be - // applied to the current table); we use the settings object to - // determine if we want to perform searching on the current table, - // since settings.sTableId will be different to different tables - if (table.settings()[0] !== settings) return true; - // no filter on this column or no need to filter this column - if (typeof filter === 'undefined' || !$td.data('filter')) return true; - - var r = filter.val(), v, r0, r1; - var i_data = function(i) { - if (!colReorderEnabled()) return i; - var order = table.colReorder.order(), k; - for (k = 0; k < order.length; ++k) if (order[k] === i) return k; - return i; // in theory it will never be here... - } - v = data[i_data(i)]; - if (type === 'number' || type === 'integer') { - v = parseFloat(v); - // how to handle NaN? currently exclude these rows - if (isNaN(v)) return(false); - r0 = parseFloat(scaleBack(r[0], scale)) - r1 = parseFloat(scaleBack(r[1], scale)); - if (v >= r0 && v <= r1) return true; - } else if (type === 'date' || type === 'time') { - v = new Date(v); - r0 = new Date(r[0] / scale); r1 = new Date(r[1] / scale); - if (v >= r0 && v <= r1) return true; - } else if (type === 'factor') { - if (r.length === 0 || inArray(v, r)) return true; - } else if (type === 'logical') { - if (r.length === 0) return true; - if (inArray(v === '' ? 'na' : v, r)) return true; - } - return false; - }; - - $.fn.dataTable.ext.search.push(customFilter); - - // search for the preset search strings if it is non-empty - if (searchCol) { - if (inArray(type, ['factor', 'logical'])) { - filter[0].selectize.setValue(JSON.parse(searchCol)); - } else if (type === 'character') { - $input.trigger('input'); - } else if (inArray(type, ['number', 'integer', 'date', 'time'])) { - $input.trigger('change'); - } - } - - }); - - } - - // highlight search keywords - var highlight = function() { - var body = $(table.table().body()); - // removing the old highlighting first - body.unhighlight(); - - // don't highlight the "not found" row, so we get the rows using the api - if (table.rows({ filter: 'applied' }).data().length === 0) return; - // highlight global search keywords - body.highlight($.trim(table.search()).split(/\s+/)); - // then highlight keywords from individual column filters - if (filterRow) filterRow.each(function(i, td) { - var $td = $(td), type = $td.data('type'); - if (type !== 'character') return; - var $input = $td.children('div').first().children('input'); - var column = table.column(i).nodes().to$(), - val = $.trim($input.val()); - if (type !== 'character' || val === '') return; - column.highlight(val.split(/\s+/)); - }); - }; - - if (options.searchHighlight) { - table - .on('draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth', highlight) - .on('destroy', function() { - // remove event handler - table.off('draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth'); - }); - - // Set the option for escaping regex characters in our search string. This will be used - // for all future matching. - jQuery.fn.highlight.options.escapeRegex = (!options.search || !options.search.regex); - - // initial highlight for state saved conditions and initial states - highlight(); - } - - // run the callback function on the table instance - if (typeof data.callback === 'function') data.callback(table); - - // double click to edit the cell, row, column, or all cells - if (data.editable) table.on('dblclick.dt', 'tbody td', function(e) { - // only bring up the editor when the cell itself is dbclicked, and ignore - // other dbclick events bubbled up (e.g. from the ) - if (e.target !== this) return; - var target = [], immediate = false; - switch (data.editable.target) { - case 'cell': - target = [this]; - immediate = true; // edit will take effect immediately - break; - case 'row': - target = table.cells(table.cell(this).index().row, '*').nodes(); - break; - case 'column': - target = table.cells('*', table.cell(this).index().column).nodes(); - break; - case 'all': - target = table.cells().nodes(); - break; - default: - throw 'The editable parameter must be "cell", "row", "column", or "all"'; - } - var disableCols = data.editable.disable ? data.editable.disable.columns : null; - for (var i = 0; i < target.length; i++) { - (function(cell, current) { - var $cell = $(cell), html = $cell.html(); - var _cell = table.cell(cell), value = _cell.data(); - var $input = $(''), changed = false; - if (!immediate) { - $cell.data('input', $input).data('html', html); - $input.attr('title', 'Hit Ctrl+Enter to finish editing, or Esc to cancel'); - } - $input.val(value); - if (inArray(_cell.index().column, disableCols)) { - $input.attr('readonly', '').css('filter', 'invert(25%)'); - } - $cell.empty().append($input); - if (cell === current) $input.focus(); - $input.css('width', '100%'); - - if (immediate) $input.on('change', function() { - changed = true; - var valueNew = $input.val(); - if (valueNew != value) { - _cell.data(valueNew); - if (HTMLWidgets.shinyMode) { - changeInput('cell_edit', [cellInfo(cell)], 'DT.cellInfo', null, {priority: "event"}); - } - // for server-side processing, users have to call replaceData() to update the table - if (!server) table.draw(false); - } else { - $cell.html(html); - } - $input.remove(); - }).on('blur', function() { - if (!changed) $input.trigger('change'); - }).on('keyup', function(e) { - // hit Escape to cancel editing - if (e.keyCode === 27) $input.trigger('blur'); - }); - - // bulk edit (row, column, or all) - if (!immediate) $input.on('keyup', function(e) { - var removeInput = function($cell, restore) { - $cell.data('input').remove(); - if (restore) $cell.html($cell.data('html')); - } - if (e.keyCode === 27) { - for (var i = 0; i < target.length; i++) { - removeInput($(target[i]), true); - } - } else if (e.keyCode === 13 && e.ctrlKey) { - // Ctrl + Enter - var cell, $cell, _cell, cellData = []; - for (var i = 0; i < target.length; i++) { - cell = target[i]; $cell = $(cell); _cell = table.cell(cell); - _cell.data($cell.data('input').val()); - HTMLWidgets.shinyMode && cellData.push(cellInfo(cell)); - removeInput($cell, false); - } - if (HTMLWidgets.shinyMode) { - changeInput('cell_edit', cellData, 'DT.cellInfo', null, {priority: "event"}); - } - if (!server) table.draw(false); - } - }); - })(target[i], this); - } - }); - - // interaction with shiny - if (!HTMLWidgets.shinyMode && !crosstalkOptions.group) return; - - var methods = {}; - var shinyData = {}; - - methods.updateCaption = function(caption) { - if (!caption) return; - $table.children('caption').replaceWith(caption); - } - - // register clear functions to remove input values when the table is removed - instance.clearInputs = {}; - - var changeInput = function(id, value, type, noCrosstalk, opts) { - var event = id; - id = el.id + '_' + id; - if (type) id = id + ':' + type; - // do not update if the new value is the same as old value - if (event !== 'cell_edit' && !/_clicked$/.test(event) && shinyData.hasOwnProperty(id) && shinyData[id] === JSON.stringify(value)) - return; - shinyData[id] = JSON.stringify(value); - if (HTMLWidgets.shinyMode && Shiny.setInputValue) { - Shiny.setInputValue(id, value, opts); - if (!instance.clearInputs[id]) instance.clearInputs[id] = function() { - Shiny.setInputValue(id, null); - } - } - - // HACK - if (event === "rows_selected" && !noCrosstalk) { - if (crosstalkOptions.group) { - var keys = crosstalkOptions.key; - var selectedKeys = null; - if (value) { - selectedKeys = []; - for (var i = 0; i < value.length; i++) { - // The value array's contents use 1-based row numbers, so we must - // convert to 0-based before indexing into the keys array. - selectedKeys.push(keys[value[i] - 1]); - } - } - instance.ctselectHandle.set(selectedKeys); - } - } - }; - - var addOne = function(x) { - return x.map(function(i) { return 1 + i; }); - }; - - var unique = function(x) { - var ux = []; - $.each(x, function(i, el){ - if ($.inArray(el, ux) === -1) ux.push(el); - }); - return ux; - } - - // change the row index of a cell - var tweakCellIndex = function(cell) { - var info = cell.index(); - // some cell may not be valid. e.g, #759 - // when using the RowGroup extension, datatables will - // generate the row label and the cells are not part of - // the data thus contain no row/col info - if (info === undefined) - return {row: null, col: null}; - if (server) { - info.row = DT_rows_current[info.row]; - } else { - info.row += 1; - } - return {row: info.row, col: info.column}; - } - - var cleanSelectedValues = function() { - changeInput('rows_selected', []); - changeInput('columns_selected', []); - changeInput('cells_selected', transposeArray2D([]), 'shiny.matrix'); - } - // #828 we should clean the selection on the server-side when the table reloads - cleanSelectedValues(); - - // a flag to indicates if select extension is initialized or not - var flagSelectExt = table.settings()[0]._select !== undefined; - // the Select extension should only be used in the client mode and - // when the selection.mode is set to none - if (data.selection.mode === 'none' && !server && flagSelectExt) { - var updateRowsSelected = function() { - var rows = table.rows({selected: true}); - var selected = []; - $.each(rows.indexes().toArray(), function(i, v) { - selected.push(v + 1); - }); - changeInput('rows_selected', selected); - } - var updateColsSelected = function() { - var columns = table.columns({selected: true}); - changeInput('columns_selected', columns.indexes().toArray()); - } - var updateCellsSelected = function() { - var cells = table.cells({selected: true}); - var selected = []; - cells.every(function() { - var row = this.index().row; - var col = this.index().column; - selected = selected.concat([[row + 1, col]]); - }); - changeInput('cells_selected', transposeArray2D(selected), 'shiny.matrix'); - } - table.on('select deselect', function(e, dt, type, indexes) { - updateRowsSelected(); - updateColsSelected(); - updateCellsSelected(); - }) - } - - var selMode = data.selection.mode, selTarget = data.selection.target; - var selDisable = data.selection.selectable === false; - if (inArray(selMode, ['single', 'multiple'])) { - var selClass = inArray(data.style, ['bootstrap', 'bootstrap4']) ? 'active' : 'selected'; - // selected1: row indices; selected2: column indices - var initSel = function(x) { - if (x === null || typeof x === 'boolean' || selTarget === 'cell') { - return {rows: [], cols: []}; - } else if (selTarget === 'row') { - return {rows: $.makeArray(x), cols: []}; - } else if (selTarget === 'column') { - return {rows: [], cols: $.makeArray(x)}; - } else if (selTarget === 'row+column') { - return {rows: $.makeArray(x.rows), cols: $.makeArray(x.cols)}; - } - } - var selected = data.selection.selected; - var selected1 = initSel(selected).rows, selected2 = initSel(selected).cols; - // selectable should contain either all positive or all non-positive values, not both - // positive values indicate "selectable" while non-positive values means "nonselectable" - // the assertion is performed on R side. (only column indicides could be zero which indicates - // the row name) - var selectable = data.selection.selectable; - var selectable1 = initSel(selectable).rows, selectable2 = initSel(selectable).cols; - - // After users reorder the rows or filter the table, we cannot use the table index - // directly. Instead, we need this function to find out the rows between the two clicks. - // If user filter the table again between the start click and the end click, the behavior - // would be undefined, but it should not be a problem. - var shiftSelRowsIndex = function(start, end) { - var indexes = server ? DT_rows_all : table.rows({ search: 'applied' }).indexes().toArray(); - start = indexes.indexOf(start); end = indexes.indexOf(end); - // if start is larger than end, we need to swap - if (start > end) { - var tmp = end; end = start; start = tmp; - } - return indexes.slice(start, end + 1); - } - - var serverRowIndex = function(clientRowIndex) { - return server ? DT_rows_current[clientRowIndex] : clientRowIndex + 1; - } - - // row, column, or cell selection - var lastClickedRow; - if (inArray(selTarget, ['row', 'row+column'])) { - // Get the current selected rows. It will also - // update the selected1's value based on the current row selection state - // Note we can't put this function inside selectRows() directly, - // the reason is method.selectRows() will override selected1's value but this - // function will add rows to selected1 (keep the existing selection), which is - // inconsistent with column and cell selection. - var selectedRows = function() { - var rows = table.rows('.' + selClass); - var idx = rows.indexes().toArray(); - if (!server) { - selected1 = addOne(idx); - return selected1; - } - idx = idx.map(function(i) { - return DT_rows_current[i]; - }); - selected1 = selMode === 'multiple' ? unique(selected1.concat(idx)) : idx; - return selected1; - } - // Change selected1's value based on selectable1, then refresh the row state - var onlyKeepSelectableRows = function() { - if (selDisable) { // users can't select; useful when only want backend select - selected1 = []; - return; - } - if (selectable1.length === 0) return; - var nonselectable = selectable1[0] <= 0; - if (nonselectable) { - // should make selectable1 positive - selected1 = $(selected1).not(selectable1.map(function(i) { return -i; })).get(); - } else { - selected1 = $(selected1).filter(selectable1).get(); - } - } - // Change selected1's value based on selectable1, then - // refresh the row selection state according to values in selected1 - var selectRows = function(ignoreSelectable) { - if (!ignoreSelectable) onlyKeepSelectableRows(); - table.$('tr.' + selClass).removeClass(selClass); - if (selected1.length === 0) return; - if (server) { - table.rows({page: 'current'}).every(function() { - if (inArray(DT_rows_current[this.index()], selected1)) { - $(this.node()).addClass(selClass); - } - }); - } else { - var selected0 = selected1.map(function(i) { return i - 1; }); - $(table.rows(selected0).nodes()).addClass(selClass); - } - } - table.on('mousedown.dt', 'tbody tr', function(e) { - var $this = $(this), thisRow = table.row(this); - if (selMode === 'multiple') { - if (e.shiftKey && lastClickedRow !== undefined) { - // select or de-select depends on the last clicked row's status - var flagSel = !$this.hasClass(selClass); - var crtClickedRow = serverRowIndex(thisRow.index()); - if (server) { - var rowsIndex = shiftSelRowsIndex(lastClickedRow, crtClickedRow); - // update current page's selClass - rowsIndex.map(function(i) { - var rowIndex = DT_rows_current.indexOf(i); - if (rowIndex >= 0) { - var row = table.row(rowIndex).nodes().to$(); - var flagRowSel = !row.hasClass(selClass); - if (flagSel === flagRowSel) row.toggleClass(selClass); - } - }); - // update selected1 - if (flagSel) { - selected1 = unique(selected1.concat(rowsIndex)); - } else { - selected1 = selected1.filter(function(index) { - return !inArray(index, rowsIndex); - }); - } - } else { - // js starts from 0 - shiftSelRowsIndex(lastClickedRow - 1, crtClickedRow - 1).map(function(value) { - var row = table.row(value).nodes().to$(); - var flagRowSel = !row.hasClass(selClass); - if (flagSel === flagRowSel) row.toggleClass(selClass); - }); - } - e.preventDefault(); - } else { - $this.toggleClass(selClass); - } - } else { - if ($this.hasClass(selClass)) { - $this.removeClass(selClass); - } else { - table.$('tr.' + selClass).removeClass(selClass); - $this.addClass(selClass); - } - } - if (server && !$this.hasClass(selClass)) { - var id = DT_rows_current[thisRow.index()]; - // remove id from selected1 since its class .selected has been removed - if (inArray(id, selected1)) selected1.splice($.inArray(id, selected1), 1); - } - selectedRows(); // update selected1's value based on selClass - selectRows(false); // only keep the selectable rows - changeInput('rows_selected', selected1); - changeInput('row_last_clicked', serverRowIndex(thisRow.index()), null, null, {priority: 'event'}); - lastClickedRow = serverRowIndex(thisRow.index()); - }); - selectRows(false); // in case users have specified pre-selected rows - // restore selected rows after the table is redrawn (e.g. sort/search/page); - // client-side tables will preserve the selections automatically; for - // server-side tables, we have to *real* row indices are in `selected1` - changeInput('rows_selected', selected1); - if (server) table.on('draw.dt', function(e) { selectRows(false); }); - methods.selectRows = function(selected, ignoreSelectable) { - selected1 = $.makeArray(selected); - selectRows(ignoreSelectable); - changeInput('rows_selected', selected1); - } - } - - if (inArray(selTarget, ['column', 'row+column'])) { - if (selTarget === 'row+column') { - $(table.columns().footer()).css('cursor', 'pointer'); - } - // update selected2's value based on selectable2 - var onlyKeepSelectableCols = function() { - if (selDisable) { // users can't select; useful when only want backend select - selected2 = []; - return; - } - if (selectable2.length === 0) return; - var nonselectable = selectable2[0] <= 0; - if (nonselectable) { - // need to make selectable2 positive - selected2 = $(selected2).not(selectable2.map(function(i) { return -i; })).get(); - } else { - selected2 = $(selected2).filter(selectable2).get(); - } - } - // update selected2 and then - // refresh the col selection state according to values in selected2 - var selectCols = function(ignoreSelectable) { - if (!ignoreSelectable) onlyKeepSelectableCols(); - // if selected2 is not a valide index (e.g., larger than the column number) - // table.columns(selected2) will fail and result in a blank table - // this is different from the table.rows(), where the out-of-range indexes - // doesn't affect at all - selected2 = $(selected2).filter(table.columns().indexes()).get(); - table.columns().nodes().flatten().to$().removeClass(selClass); - if (selected2.length > 0) - table.columns(selected2).nodes().flatten().to$().addClass(selClass); - } - var callback = function() { - var colIdx = selTarget === 'column' ? table.cell(this).index().column : - $.inArray(this, table.columns().footer()), - thisCol = $(table.column(colIdx).nodes()); - if (colIdx === -1) return; - if (thisCol.hasClass(selClass)) { - thisCol.removeClass(selClass); - selected2.splice($.inArray(colIdx, selected2), 1); - } else { - if (selMode === 'single') $(table.cells().nodes()).removeClass(selClass); - thisCol.addClass(selClass); - selected2 = selMode === 'single' ? [colIdx] : unique(selected2.concat([colIdx])); - } - selectCols(false); // update selected2 based on selectable - changeInput('columns_selected', selected2); - } - if (selTarget === 'column') { - $(table.table().body()).on('click.dt', 'td', callback); - } else { - $(table.table().footer()).on('click.dt', 'tr th', callback); - } - selectCols(false); // in case users have specified pre-selected columns - changeInput('columns_selected', selected2); - if (server) table.on('draw.dt', function(e) { selectCols(false); }); - methods.selectColumns = function(selected, ignoreSelectable) { - selected2 = $.makeArray(selected); - selectCols(ignoreSelectable); - changeInput('columns_selected', selected2); - } - } - - if (selTarget === 'cell') { - var selected3 = [], selectable3 = []; - if (selected !== null) selected3 = selected; - if (selectable !== null && typeof selectable !== 'boolean') selectable3 = selectable; - var findIndex = function(ij, sel) { - for (var i = 0; i < sel.length; i++) { - if (ij[0] === sel[i][0] && ij[1] === sel[i][1]) return i; - } - return -1; - } - // Change selected3's value based on selectable3, then refresh the cell state - var onlyKeepSelectableCells = function() { - if (selDisable) { // users can't select; useful when only want backend select - selected3 = []; - return; - } - if (selectable3.length === 0) return; - var nonselectable = selectable3[0][0] <= 0; - var out = []; - if (nonselectable) { - selected3.map(function(ij) { - // should make selectable3 positive - if (findIndex([-ij[0], -ij[1]], selectable3) === -1) { out.push(ij); } - }); - } else { - selected3.map(function(ij) { - if (findIndex(ij, selectable3) > -1) { out.push(ij); } - }); - } - selected3 = out; - } - // Change selected3's value based on selectable3, then - // refresh the cell selection state according to values in selected3 - var selectCells = function(ignoreSelectable) { - if (!ignoreSelectable) onlyKeepSelectableCells(); - table.$('td.' + selClass).removeClass(selClass); - if (selected3.length === 0) return; - if (server) { - table.cells({page: 'current'}).every(function() { - var info = tweakCellIndex(this); - if (findIndex([info.row, info.col], selected3) > -1) - $(this.node()).addClass(selClass); - }); - } else { - selected3.map(function(ij) { - $(table.cell(ij[0] - 1, ij[1]).node()).addClass(selClass); - }); - } - }; - table.on('click.dt', 'tbody td', function() { - var $this = $(this), info = tweakCellIndex(table.cell(this)); - if ($this.hasClass(selClass)) { - $this.removeClass(selClass); - selected3.splice(findIndex([info.row, info.col], selected3), 1); - } else { - if (selMode === 'single') $(table.cells().nodes()).removeClass(selClass); - $this.addClass(selClass); - selected3 = selMode === 'single' ? [[info.row, info.col]] : - unique(selected3.concat([[info.row, info.col]])); - } - selectCells(false); // must call this to update selected3 based on selectable3 - changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); - }); - selectCells(false); // in case users have specified pre-selected columns - changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); - - if (server) table.on('draw.dt', function(e) { selectCells(false); }); - methods.selectCells = function(selected, ignoreSelectable) { - selected3 = selected ? selected : []; - selectCells(ignoreSelectable); - changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); - } - } - } - - // expose some table info to Shiny - var updateTableInfo = function(e, settings) { - // TODO: is anyone interested in the page info? - // changeInput('page_info', table.page.info()); - var updateRowInfo = function(id, modifier) { - var idx; - if (server) { - idx = modifier.page === 'current' ? DT_rows_current : DT_rows_all; - } else { - var rows = table.rows($.extend({ - search: 'applied', - page: 'all' - }, modifier)); - idx = addOne(rows.indexes().toArray()); - } - changeInput('rows' + '_' + id, idx); - }; - updateRowInfo('current', {page: 'current'}); - updateRowInfo('all', {}); - } - table.on('draw.dt', updateTableInfo); - updateTableInfo(); - - // state info - table.on('draw.dt column-visibility.dt', function() { - changeInput('state', table.state()); - }); - changeInput('state', table.state()); - - // search info - var updateSearchInfo = function() { - changeInput('search', table.search()); - if (filterRow) changeInput('search_columns', filterRow.toArray().map(function(td) { - return $(td).find('input').first().val(); - })); - } - table.on('draw.dt', updateSearchInfo); - updateSearchInfo(); - - var cellInfo = function(thiz) { - var info = tweakCellIndex(table.cell(thiz)); - info.value = table.cell(thiz).data(); - return info; - } - // the current cell clicked on - table.on('click.dt', 'tbody td', function() { - changeInput('cell_clicked', cellInfo(this), null, null, {priority: 'event'}); - }) - changeInput('cell_clicked', {}); - - // do not trigger table selection when clicking on links unless they have classes - table.on('click.dt', 'tbody td a', function(e) { - if (this.className === '') e.stopPropagation(); - }); - - methods.addRow = function(data, rowname, resetPaging) { - var n = table.columns().indexes().length, d = n - data.length; - if (d === 1) { - data = rowname.concat(data) - } else if (d !== 0) { - console.log(data); - console.log(table.columns().indexes()); - throw 'New data must be of the same length as current data (' + n + ')'; - }; - table.row.add(data).draw(resetPaging); - } - - methods.updateSearch = function(keywords) { - if (keywords.global !== null) - $(table.table().container()).find('input[type=search]').first() - .val(keywords.global).trigger('input'); - var columns = keywords.columns; - if (!filterRow || columns === null) return; - filterRow.toArray().map(function(td, i) { - var v = typeof columns === 'string' ? columns : columns[i]; - if (typeof v === 'undefined') { - console.log('The search keyword for column ' + i + ' is undefined') - return; - } - $(td).find('input').first().val(v); - searchColumn(i, v); - }); - table.draw(); - } - - methods.hideCols = function(hide, reset) { - if (reset) table.columns().visible(true, false); - table.columns(hide).visible(false); - } - - methods.showCols = function(show, reset) { - if (reset) table.columns().visible(false, false); - table.columns(show).visible(true); - } - - methods.colReorder = function(order, origOrder) { - table.colReorder.order(order, origOrder); - } - - methods.selectPage = function(page) { - if (table.page.info().pages < page || page < 1) { - throw 'Selected page is out of range'; - }; - table.page(page - 1).draw(false); - } - - methods.reloadData = function(resetPaging, clearSelection) { - // empty selections first if necessary - if (methods.selectRows && inArray('row', clearSelection)) methods.selectRows([]); - if (methods.selectColumns && inArray('column', clearSelection)) methods.selectColumns([]); - if (methods.selectCells && inArray('cell', clearSelection)) methods.selectCells([]); - table.ajax.reload(null, resetPaging); - } - - table.shinyMethods = methods; - }, - resize: function(el, width, height, instance) { - if (instance.data) this.renderValue(el, instance.data, instance); - - // dynamically adjust height if fillContainer = TRUE - if (instance.fillContainer) - this.fillAvailableHeight(el, height); - - this.adjustWidth(el); - }, - - // dynamically set the scroll body to fill available height - // (used with fillContainer = TRUE) - fillAvailableHeight: function(el, availableHeight) { - - // see how much of the table is occupied by header/footer elements - // and use that to compute a target scroll body height - var dtWrapper = $(el).find('div.dataTables_wrapper'); - var dtScrollBody = $(el).find($('div.dataTables_scrollBody')); - var framingHeight = dtWrapper.innerHeight() - dtScrollBody.innerHeight(); - var scrollBodyHeight = availableHeight - framingHeight; - - // set the height - dtScrollBody.height(scrollBodyHeight + 'px'); - }, - - // adjust the width of columns; remove the hard-coded widths on table and the - // scroll header when scrollX/Y are enabled - adjustWidth: function(el) { - var $el = $(el), table = $el.data('datatable'); - if (table) table.columns.adjust(); - $el.find('.dataTables_scrollHeadInner').css('width', '') - .children('table').css('margin-left', ''); - } -}); - - if (!HTMLWidgets.shinyMode) return; - - Shiny.addCustomMessageHandler('datatable-calls', function(data) { - var id = data.id; - var el = document.getElementById(id); - var table = el ? $(el).data('datatable') : null; - if (!table) { - console.log("Couldn't find table with id " + id); - return; - } - - var methods = table.shinyMethods, call = data.call; - if (methods[call.method]) { - methods[call.method].apply(table, call.args); - } else { - console.log("Unknown method " + call.method); - } - }); - -})(); diff --git a/docs/articles/xportr_files/datatables-css-0.0.0/datatables-crosstalk.css b/docs/articles/xportr_files/datatables-css-0.0.0/datatables-crosstalk.css deleted file mode 100644 index fb5bae84..00000000 --- a/docs/articles/xportr_files/datatables-css-0.0.0/datatables-crosstalk.css +++ /dev/null @@ -1,23 +0,0 @@ -.dt-crosstalk-fade { - opacity: 0.2; -} - -html body div.DTS div.dataTables_scrollBody { - background: none; -} - - -/* -Fix https://github.com/rstudio/DT/issues/563 -If the `table.display` is set to "block" (e.g., pkgdown), the browser will display -datatable objects strangely. The search panel and the page buttons will still be -in full-width but the table body will be "compact" and shorter. -In therory, having this attributes will affect `dom="t"` -with `display: block` users. But in reality, there should be no one. -We may remove the below lines in the future if the upstream agree to have this there. -See https://github.com/DataTables/DataTablesSrc/issues/160 -*/ - -table.dataTable { - display: table; -} diff --git a/docs/articles/xportr_files/dt-core-1.10.20/css/jquery.dataTables.extra.css b/docs/articles/xportr_files/dt-core-1.10.20/css/jquery.dataTables.extra.css deleted file mode 100644 index b2dd141f..00000000 --- a/docs/articles/xportr_files/dt-core-1.10.20/css/jquery.dataTables.extra.css +++ /dev/null @@ -1,28 +0,0 @@ -/* Selected rows/cells */ -table.dataTable tr.selected td, table.dataTable td.selected { - background-color: #b0bed9 !important; -} -/* In case of scrollX/Y or FixedHeader */ -.dataTables_scrollBody .dataTables_sizing { - visibility: hidden; -} - -/* The datatables' theme CSS file doesn't define -the color but with white background. It leads to an issue that -when the HTML's body color is set to 'white', the user can't -see the text since the background is white. One case happens in the -RStudio's IDE when inline viewing the DT table inside an Rmd file, -if the IDE theme is set to "Cobalt". - -See https://github.com/rstudio/DT/issues/447 for more info - -This fixes should have little side-effects because all the other elements -of the default theme use the #333 font color. - -TODO: The upstream may use relative colors for both the table background -and the color. It means the table can display well without this patch -then. At that time, we need to remove the below CSS attributes. -*/ -div.datatables { - color: #333; -} diff --git a/docs/articles/xportr_files/dt-core-1.10.20/css/jquery.dataTables.min.css b/docs/articles/xportr_files/dt-core-1.10.20/css/jquery.dataTables.min.css deleted file mode 100644 index 71ae98a4..00000000 --- a/docs/articles/xportr_files/dt-core-1.10.20/css/jquery.dataTables.min.css +++ /dev/null @@ -1 +0,0 @@ -table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7XQMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC)}table.dataTable thead .sorting_asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==)}table.dataTable thead .sorting_desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=)}table.dataTable thead .sorting_asc_disabled{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAW0lEQVQoz2NgoCm4w3Vnwh02wspK7/y6k01Ikdadx3f+37l9RxmfIsY7c4GKQHDiHUbcyhzvvIMq+3THBpci3jv7oIpAcMcdduzKEu/8vPMdDn/eiWQYBYMKAAC3ykIEuYQJUgAAAABJRU5ErkJggg==)}table.dataTable thead .sorting_desc_disabled{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAWUlEQVQoz2NgGAWDCtyJvPPzznc4/HknEbsy9js77vyHw313eHGZZ3PnE1TRuzuOuK1lvDMRqmzuHUZ87lO+cxuo6PEdLUIeyb7z604pYf+y3Zlwh4u2YQoAc7ZCBHH4jigAAAAASUVORK5CYII=)}table.dataTable tbody tr{background-color:#ffffff}table.dataTable tbody tr.selected{background-color:#B0BED9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:0.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:0.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:0.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:0.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-o-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:linear-gradient(to bottom, #fff 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0)));background:-webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:0.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:0.5em}} diff --git a/docs/articles/xportr_files/dt-core-1.10.20/js/jquery.dataTables.min.js b/docs/articles/xportr_files/dt-core-1.10.20/js/jquery.dataTables.min.js deleted file mode 100644 index d297f256..00000000 --- a/docs/articles/xportr_files/dt-core-1.10.20/js/jquery.dataTables.min.js +++ /dev/null @@ -1,180 +0,0 @@ -/*! - Copyright 2008-2019 SpryMedia Ltd. - - This source file is free software, available under the following license: - MIT license - http://datatables.net/license - - This source file is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. - - For details please refer to: http://www.datatables.net - DataTables 1.10.20 - ยฉ2008-2019 SpryMedia Ltd - datatables.net/license -*/ -var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(f,z,y){f instanceof String&&(f=String(f));for(var p=f.length,H=0;H").css({position:"fixed",top:0,left:-1*f(z).scrollLeft(),height:1,width:1, -overflow:"hidden"}).append(f("
").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(f("
").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}f.extend(a.oBrowser,q.__browser);a.oScroll.iBarWidth=q.__browser.barWidth} -function mb(a,b,c,d,e,h){var g=!1;if(c!==p){var k=c;g=!0}for(;d!==e;)a.hasOwnProperty(d)&&(k=g?b(k,a[d],d,a):a[d],g=!0,d+=h);return k}function Ia(a,b){var c=q.defaults.column,d=a.aoColumns.length;c=f.extend({},q.models.oColumn,c,{nTh:b?b:y.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=f.extend({},q.models.oSearch,c[d]);ma(a,d,f(b).data())}function ma(a,b,c){b=a.aoColumns[b]; -var d=a.oClasses,e=f(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var h=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);h&&(b.sWidthOrig=h[1])}c!==p&&null!==c&&(kb(c),L(q.defaults.column,c,!0),c.mDataProp===p||c.mData||(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),c.sClass&&e.addClass(c.sClass),f.extend(b,c),M(b,c,"sWidth","sWidthOrig"),c.iDataSort!==p&&(b.aDataSort=[c.iDataSort]),M(b,c,"aDataSort"));var g=b.mData,k=U(g), -l=b.mRender?U(b.mRender):null;c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=f.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=k(a,b,p,c);return l&&b?l(d,b,a,c):d};b.fnSetData=function(a,b,c){return Q(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==f.inArray("asc",b.asSorting);c=-1!==f.inArray("desc",b.asSorting);b.bSortable&&(a||c)?a&&!c?(b.sSortingClass= -d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI):(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI="")}function aa(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ja(a);for(var c=0,d=b.length;cn[m])d(k.length+ -n[m],l);else if("string"===typeof n[m]){var w=0;for(g=k.length;wb&&a[e]--; -1!=d&&c===p&&a.splice(d,1)}function ea(a,b,c,d){var e=a.aoData[b],h,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);c.innerHTML=I(a,b,d,"display")};if("dom"!==c&&(c&&"auto"!==c||"dom"!==e.src)){var k=e.anCells;if(k)if(d!==p)g(k[d],d);else for(c=0,h=k.length;c").appendTo(d));var l=0;for(b=k.length;ltr").attr("role","row");f(d).find(">tr>th, >tr>td").addClass(g.sHeaderTH);f(e).find(">tr>th, >tr>td").addClass(g.sFooterTH);if(null!==e)for(a=a.aoFooter[0],l=0,b=a.length;l=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);g=a._iDisplayStart;var n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,K(a,!1);else if(!k)a.iDraw++;else if(!a.bDestroying&&!qb(a))return;if(0!==l.length)for(h=k?a.aoData.length:n,k=k?0:g;k",{"class":e?d[0]:""}).append(f("",{valign:"top",colSpan:W(a),"class":a.oClasses.sRowEmpty}).html(c))[0];A(a,"aoHeaderCallback","header",[f(a.nTHead).children("tr")[0], -Oa(a),g,n,l]);A(a,"aoFooterCallback","footer",[f(a.nTFoot).children("tr")[0],Oa(a),g,n,l]);d=f(a.nTBody);d.children().detach();d.append(f(b));A(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function V(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&rb(a);d?ia(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;S(a);a._drawHold=!1}function sb(a){var b=a.oClasses,c=f(a.nTable);c=f("
").insertBefore(c);var d=a.oFeatures,e= -f("
",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var h=a.sDom.split(""),g,k,l,n,m,p,u=0;u")[0];n=h[u+1];if("'"==n||'"'==n){m="";for(p=2;h[u+p]!=n;)m+=h[u+p],p++;"H"==m?m=b.sJUIHeader:"F"==m&&(m=b.sJUIFooter);-1!=m.indexOf(".")?(n=m.split("."),l.id=n[0].substr(1,n[0].length-1),l.className=n[1]):"#"==m.charAt(0)?l.id=m.substr(1, -m.length-1):l.className=m;u+=p}e.append(l);e=f(l)}else if(">"==k)e=e.parent();else if("l"==k&&d.bPaginate&&d.bLengthChange)g=tb(a);else if("f"==k&&d.bFilter)g=ub(a);else if("r"==k&&d.bProcessing)g=vb(a);else if("t"==k)g=wb(a);else if("i"==k&&d.bInfo)g=xb(a);else if("p"==k&&d.bPaginate)g=yb(a);else if(0!==q.ext.feature.length)for(l=q.ext.feature,p=0,n=l.length;p',k=d.sSearch;k=k.match(/_INPUT_/)?k.replace("_INPUT_",g):k+g;b=f("
",{id:h.f?null:c+"_filter","class":b.sFilter}).append(f("
").addClass(b.sLength);a.aanFeatures.l||(l[0].id=c+"_length");l.children().append(a.oLanguage.sLengthMenu.replace("_MENU_", -e[0].outerHTML));f("select",l).val(a._iDisplayLength).on("change.DT",function(b){Va(a,f(this).val());S(a)});f(a.nTable).on("length.dt.DT",function(b,c,d){a===c&&f("select",l).val(d)});return l[0]}function yb(a){var b=a.sPaginationType,c=q.ext.pager[b],d="function"===typeof c,e=function(a){S(a)};b=f("
").addClass(a.oClasses.sPaging+b)[0];var h=a.aanFeatures;d||c.fnInit(a,b,e);h.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,g=a._iDisplayLength, -f=a.fnRecordsDisplay(),m=-1===g;b=m?0:Math.ceil(b/g);g=m?1:Math.ceil(f/g);f=c(b,g);var p;m=0;for(p=h.p.length;mh&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e",{id:a.aanFeatures.r?null:a.sTableId+"_processing","class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function K(a,b){a.oFeatures.bProcessing&&f(a.aanFeatures.r).css("display",b?"block":"none");A(a,null,"processing",[a,b])}function wb(a){var b=f(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY, -h=a.oClasses,g=b.children("caption"),k=g.length?g[0]._captionSide:null,l=f(b[0].cloneNode(!1)),n=f(b[0].cloneNode(!1)),m=b.children("tfoot");m.length||(m=null);l=f("
",{"class":h.sScrollWrapper}).append(f("
",{"class":h.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?d?B(d):null:"100%"}).append(f("
",{"class":h.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(l.removeAttr("id").css("margin-left",0).append("top"===k?g:null).append(b.children("thead"))))).append(f("
", -{"class":h.sScrollBody}).css({position:"relative",overflow:"auto",width:d?B(d):null}).append(b));m&&l.append(f("
",{"class":h.sScrollFoot}).css({overflow:"hidden",border:0,width:d?d?B(d):null:"100%"}).append(f("
",{"class":h.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===k?g:null).append(b.children("tfoot")))));b=l.children();var p=b[0];h=b[1];var u=m?b[2]:null;if(d)f(h).on("scroll.DT",function(a){a=this.scrollLeft;p.scrollLeft=a;m&&(u.scrollLeft=a)}); -f(h).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=p;a.nScrollBody=h;a.nScrollFoot=u;a.aoDrawCallback.push({fn:na,sName:"scrolling"});return l[0]}function na(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY;b=b.iBarWidth;var h=f(a.nScrollHead),g=h[0].style,k=h.children("div"),l=k[0].style,n=k.children("table");k=a.nScrollBody;var m=f(k),w=k.style,u=f(a.nScrollFoot).children("div"),q=u.children("table"),t=f(a.nTHead),r=f(a.nTable),v=r[0],za=v.style,T=a.nTFoot?f(a.nTFoot):null,A=a.oBrowser, -x=A.bScrollOversize,ac=J(a.aoColumns,"nTh"),Ya=[],y=[],z=[],C=[],G,H=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};var D=k.scrollHeight>k.clientHeight;if(a.scrollBarVis!==D&&a.scrollBarVis!==p)a.scrollBarVis=D,aa(a);else{a.scrollBarVis=D;r.children("thead, tfoot").remove();if(T){var E=T.clone().prependTo(r);var F=T.find("tr");E=E.find("tr")}var I=t.clone().prependTo(r);t=t.find("tr");D=I.find("tr");I.find("th, td").removeAttr("tabindex"); -c||(w.width="100%",h[0].style.width="100%");f.each(ua(a,I),function(b,c){G=ba(a,b);c.style.width=a.aoColumns[G].sWidth});T&&N(function(a){a.style.width=""},E);h=r.outerWidth();""===c?(za.width="100%",x&&(r.find("tbody").height()>k.offsetHeight||"scroll"==m.css("overflow-y"))&&(za.width=B(r.outerWidth()-b)),h=r.outerWidth()):""!==d&&(za.width=B(d),h=r.outerWidth());N(H,D);N(function(a){z.push(a.innerHTML);Ya.push(B(f(a).css("width")))},D);N(function(a,b){-1!==f.inArray(a,ac)&&(a.style.width=Ya[b])}, -t);f(D).height(0);T&&(N(H,E),N(function(a){C.push(a.innerHTML);y.push(B(f(a).css("width")))},E),N(function(a,b){a.style.width=y[b]},F),f(E).height(0));N(function(a,b){a.innerHTML='
'+z[b]+"
";a.childNodes[0].style.height="0";a.childNodes[0].style.overflow="hidden";a.style.width=Ya[b]},D);T&&N(function(a,b){a.innerHTML='
'+C[b]+"
";a.childNodes[0].style.height="0";a.childNodes[0].style.overflow="hidden";a.style.width=y[b]},E);r.outerWidth()< -h?(F=k.scrollHeight>k.offsetHeight||"scroll"==m.css("overflow-y")?h+b:h,x&&(k.scrollHeight>k.offsetHeight||"scroll"==m.css("overflow-y"))&&(za.width=B(F-b)),""!==c&&""===d||O(a,1,"Possible column misalignment",6)):F="100%";w.width=B(F);g.width=B(F);T&&(a.nScrollFoot.style.width=B(F));!e&&x&&(w.height=B(v.offsetHeight+b));c=r.outerWidth();n[0].style.width=B(c);l.width=B(c);d=r.height()>k.clientHeight||"scroll"==m.css("overflow-y");e="padding"+(A.bScrollbarLeft?"Left":"Right");l[e]=d?b+"px":"0px";T&& -(q[0].style.width=B(c),u[0].style.width=B(c),u[0].style[e]=d?b+"px":"0px");r.children("colgroup").insertBefore(r.children("thead"));m.trigger("scroll");!a.bSorted&&!a.bFiltered||a._drawHold||(k.scrollTop=0)}}function N(a,b,c){for(var d=0,e=0,h=b.length,g,k;e").appendTo(k.find("tbody"));k.find("thead, tfoot").remove(); -k.append(f(a.nTHead).clone()).append(f(a.nTFoot).clone());k.find("tfoot th, tfoot td").css("width","");n=ua(a,k.find("thead")[0]);for(q=0;q").css({width:r.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(q=0;q").css(h|| -e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(k).appendTo(p);h&&g?k.width(g):h?(k.css("width","auto"),k.removeAttr("width"),k.width()").css("width",B(a)).appendTo(b||y.body);b=a[0].offsetWidth;a.remove();return b}function Kb(a,b){var c=Lb(a,b);if(0>c)return null;var d=a.aoData[c];return d.nTr?d.anCells[b]:f("").html(I(a,c,b,"display"))[0]}function Lb(a,b){for(var c,d=-1,e=-1,h=0,g=a.aoData.length;hd&&(d=c.length,e=h);return e} -function B(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function Y(a){var b=[],c=a.aoColumns;var d=a.aaSortingFixed;var e=f.isPlainObject(d);var h=[];var g=function(a){a.length&&!f.isArray(a[0])?h.push(a):f.merge(h,a)};f.isArray(d)&&g(d);e&&d.pre&&g(d.pre);g(a.aaSorting);e&&d.post&&g(d.post);for(a=0;an?1:0; -if(0!==m)return"asc"===l.dir?m:-m}m=c[a];n=c[b];return mn?1:0}):g.sort(function(a,b){var h,g=k.length,f=e[a]._aSortData,l=e[b]._aSortData;for(h=0;hp?1:0})}a.bSorted=!0}function Nb(a){var b=a.aoColumns,c=Y(a);a=a.oLanguage.oAria;for(var d=0,e=b.length;d/g,"");var f=h.nTh;f.removeAttribute("aria-sort"); -h.bSortable&&(0e?e+1:3))}e=0;for(h=d.length;ee?e+1:3))}a.aLastSort=d}function Mb(a,b){var c=a.aoColumns[b],d=q.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,ca(a,b)));for(var h,g=q.ext.type.order[c.sType+"-pre"],k=0,f=a.aoData.length;k=h.length?[0,c[1]]:c)}));b.search!==p&&f.extend(a.oPreviousSearch, -Gb(b.search));if(b.columns)for(d=0,e=b.columns.length;d=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Ra(a,b){a=a.renderer;var c=q.ext.renderer[b];return f.isPlainObject(a)&&a[b]?c[a[b]]||c._:"string"===typeof a?c[a]||c._:c._}function D(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function ka(a,b){var c=Pb.numbers_length,d=Math.floor(c/2);b<=c?a=Z(0,b):a<=d?(a=Z(0,c-2),a.push("ellipsis"),a.push(b-1)):(a>=b-1-d?a=Z(b-(c-2),b):(a=Z(a-d+2,a+d-1),a.push("ellipsis"), -a.push(b-1)),a.splice(0,0,"ellipsis"),a.splice(0,0,0));a.DT_el="span";return a}function Ha(a){f.each({num:function(b){return Da(b,a)},"num-fmt":function(b){return Da(b,a,bb)},"html-num":function(b){return Da(b,a,Ea)},"html-num-fmt":function(b){return Da(b,a,Ea,bb)}},function(b,c){C.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(C.type.search[b+a]=C.type.search.html)})}function Qb(a){return function(){var b=[Ca(this[q.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return q.ext.internal[a].apply(this, -b)}}var q=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new v(Ca(this[C.iApiIndex])):new v(this)};this.fnAddData=function(a,b){var c=this.api(!0);a=f.isArray(a)&&(f.isArray(a[0])||f.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===p||b)&&c.draw();return a.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===p||a?b.draw(!1): -(""!==d.sX||""!==d.sY)&&na(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===p||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0);a=d.rows(a);var e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===p||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,f){e=this.api(!0);null===b||b===p? -e.search(a,c,d,f):e.column(b).search(a,c,d,f);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==p){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==p||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==p?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(), -[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){a=this.api(!0).page(a);(b===p||b)&&a.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===p||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return Ca(this[C.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener= -function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===p||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===p||e)&&h.columns.adjust();(d===p||d)&&h.draw();return 0};this.fnVersionCheck=C.fnVersionCheck;var b=this,c=a===p,d=this.length;c&&(a={});this.oApi=this.internal=C.internal;for(var e in q.ext.internal)e&&(this[e]=Qb(e));this.each(function(){var e={},g=1").appendTo(w));r.nTHead=b[0];b=w.children("tbody");0===b.length&&(b=f("").appendTo(w));r.nTBody=b[0];b=w.children("tfoot");0===b.length&&0").appendTo(w));0===b.length||0===b.children().length?w.addClass(x.sNoFooter):0/g,cc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,dc=/(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^|\-)/g,bb=/[',$ยฃโ‚ฌยฅ%\u2009\u202F\u20BD\u20a9\u20BArfkษƒฮž]/gi,P=function(a){return a&&!0!==a&&"-"!==a?!1: -!0},Sb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Tb=function(a,b){cb[b]||(cb[b]=new RegExp(Ua(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(cb[b],"."):a},db=function(a,b,c){var d="string"===typeof a;if(P(a))return!0;b&&d&&(a=Tb(a,b));c&&d&&(a=a.replace(bb,""));return!isNaN(parseFloat(a))&&isFinite(a)},Ub=function(a,b,c){return P(a)?!0:P(a)||"string"===typeof a?db(a.replace(Ea,""),b,c)?!0:null:null},J=function(a,b,c){var d=[],e=0,h=a.length;if(c!== -p)for(;ea.length)){var b=a.slice().sort();for(var c=b[0],d=1, -e=b.length;d")[0],$b=ya.textContent!==p,bc=/<.*?>/g,Sa=q.util.throttle,Wb=[],G=Array.prototype,ec=function(a){var b,c=q.settings,d=f.map(c,function(a,b){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase()){var e=f.inArray(a,d);return-1!==e?[c[e]]:null}if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?b=f(a):a instanceof f&&(b=a)}else return[];if(b)return b.map(function(a){e=f.inArray(this, -d);return-1!==e?c[e]:null}).toArray()};var v=function(a,b){if(!(this instanceof v))return new v(a,b);var c=[],d=function(a){(a=ec(a))&&c.push.apply(c,a)};if(f.isArray(a))for(var e=0,h=a.length;ea?new v(b[a],this[a]):null},filter:function(a){var b=[];if(G.filter)b=G.filter.call(this,a,this);else for(var c=0,d=this.length;c").addClass(c),f("td",d).addClass(c).html(b)[0].colSpan=W(a),e.push(d[0]))};h(c,d);b._details&&b._details.detach();b._details=f(e);b._detailsShow&&b._details.insertAfter(b.nTr)},hb=function(a,b){var c=a.context;c.length&&(a=c[0].aoData[b!==p?b:a[0]])&&a._details&&(a._details.remove(),a._detailsShow=p,a._details=p)},Yb=function(a,b){var c=a.context;c.length&&a.length&&(a=c[0].aoData[a[0]],a._details&&((a._detailsShow=b)?a._details.insertAfter(a.nTr): -a._details.detach(),ic(c[0])))},ic=function(a){var b=new v(a),c=a.aoData;b.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0g){var m=f.map(d,function(a,b){return a.bVisible?b:null});return[m[m.length+g]]}return[ba(a,g)];case "name":return f.map(e,function(a,b){return a===n[1]?b:null});default:return[]}if(b.nodeName&&b._DT_CellIndex)return[b._DT_CellIndex.column];g=f(h).filter(b).map(function(){return f.inArray(this, -h)}).toArray();if(g.length||!b.nodeName)return g;g=f(b).closest("*[data-dt-column]");return g.length?[g.data("dt-column")]:[]},a,c)};t("columns()",function(a,b){a===p?a="":f.isPlainObject(a)&&(b=a,a="");b=fb(b);var c=this.iterator("table",function(c){return kc(c,a,b)},1);c.selector.cols=a;c.selector.opts=b;return c});x("columns().header()","column().header()",function(a,b){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});x("columns().footer()","column().footer()",function(a, -b){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});x("columns().data()","column().data()",function(){return this.iterator("column-rows",Zb,1)});x("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});x("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,f){return la(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});x("columns().nodes()", -"column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return la(a.aoData,e,"anCells",b)},1)});x("columns().visible()","column().visible()",function(a,b){var c=this,d=this.iterator("column",function(b,c){if(a===p)return b.aoColumns[c].bVisible;var d=b.aoColumns,e=d[c],h=b.aoData,n;if(a!==p&&e.bVisible!==a){if(a){var m=f.inArray(!0,J(d,"bVisible"),c+1);d=0;for(n=h.length;dd;return!0};q.isDataTable=q.fnIsDataTable=function(a){var b=f(a).get(0),c=!1;if(a instanceof -q.Api)return!0;f.each(q.settings,function(a,e){a=e.nScrollHead?f("table",e.nScrollHead)[0]:null;var d=e.nScrollFoot?f("table",e.nScrollFoot)[0]:null;if(e.nTable===b||a===b||d===b)c=!0});return c};q.tables=q.fnTables=function(a){var b=!1;f.isPlainObject(a)&&(b=a.api,a=a.visible);var c=f.map(q.settings,function(b){if(!a||a&&f(b.nTable).is(":visible"))return b.nTable});return b?new v(c):c};q.camelToHungarian=L;t("$()",function(a,b){b=this.rows(b).nodes();b=f(b);return f([].concat(b.filter(a).toArray(), -b.find(a).toArray()))});f.each(["on","one","off"],function(a,b){t(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0]=f.map(a[0].split(/\s/),function(a){return a.match(/\.dt\b/)?a:a+".dt"}).join(" ");var d=f(this.tables().nodes());d[b].apply(d,a);return this})});t("clear()",function(){return this.iterator("table",function(a){qa(a)})});t("settings()",function(){return new v(this.context,this.context)});t("init()",function(){var a=this.context;return a.length?a[0].oInit:null});t("data()", -function(){return this.iterator("table",function(a){return J(a.aoData,"_aData")}).flatten()});t("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,h=b.nTBody,g=b.nTHead,k=b.nTFoot,l=f(e);h=f(h);var n=f(b.nTableWrapper),m=f.map(b.aoData,function(a){return a.nTr}),p;b.bDestroying=!0;A(b,"aoDestroyCallback","destroy",[b]);a||(new v(b)).columns().visible(!0);n.off(".DT").find(":not(tbody *)").off(".DT");f(z).off(".DT-"+b.sInstance); -e!=g.parentNode&&(l.children("thead").detach(),l.append(g));k&&e!=k.parentNode&&(l.children("tfoot").detach(),l.append(k));b.aaSorting=[];b.aaSortingFixed=[];Aa(b);f(m).removeClass(b.asStripeClasses.join(" "));f("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);h.children().detach();h.append(m);g=a?"remove":"detach";l[g]();n[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),l.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&& -h.children().each(function(a){f(this).addClass(b.asDestroyStripes[a%p])}));c=f.inArray(b,q.settings);-1!==c&&q.settings.splice(c,1)})});f.each(["column","row","cell"],function(a,b){t(b+"s().every()",function(a){var c=this.selector.opts,e=this;return this.iterator(b,function(d,f,k,l,n){a.call(e[b](f,"cell"===b?k:c,"cell"===b?c:p),f,k,l,n)})})});t("i18n()",function(a,b,c){var d=this.context[0];a=U(a)(d.oLanguage);a===p&&(a=b);c!==p&&f.isPlainObject(a)&&(a=a[c]!==p?a[c]:a._);return a.replace("%d",c)}); -q.version="1.10.20";q.settings=[];q.models={};q.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};q.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};q.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null, -sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};q.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1, -bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}}, -fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last", -sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:f.extend({},q.models.oSearch),sAjaxDataProp:"data", -sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};H(q.defaults);q.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};H(q.defaults.column);q.models.oSettings= -{oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{}, -aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0, -aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:p,oAjaxData:p,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==D(this)?1*this._iRecordsTotal: -this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==D(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};q.ext=C={buttons:{}, -classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:q.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:q.version};f.extend(C,{afnFiltering:C.search,aTypes:C.type.detect,ofnSearch:C.type.search,oSort:C.type.order,afnSortData:C.order,aoFeatures:C.feature,oApi:C.internal,oStdClasses:C.classes,oPagination:C.pager}); -f.extend(q.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled", -sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"", -sJUIHeader:"",sJUIFooter:""});var Pb=q.ext.pager;f.extend(Pb,{simple:function(a,b){return["previous","next"]},full:function(a,b){return["first","previous","next","last"]},numbers:function(a,b){return[ka(a,b)]},simple_numbers:function(a,b){return["previous",ka(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ka(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",ka(a,b),"last"]},_numbers:ka,numbers_length:7});f.extend(!0,q.ext.renderer,{pageButton:{_:function(a,b, -c,d,e,h){var g=a.oClasses,k=a.oLanguage.oPaginate,l=a.oLanguage.oAria.paginate||{},n,m,q=0,t=function(b,d){var p,r=g.sPageButtonDisabled,u=function(b){Xa(a,b.data.action,!0)};var w=0;for(p=d.length;w").appendTo(b);t(x,v)}else{n=null;m=v;x=a.iTabIndex;switch(v){case "ellipsis":b.append('');break;case "first":n=k.sFirst;0===e&&(x=-1,m+=" "+r);break;case "previous":n=k.sPrevious;0===e&&(x=-1,m+= -" "+r);break;case "next":n=k.sNext;e===h-1&&(x=-1,m+=" "+r);break;case "last":n=k.sLast;e===h-1&&(x=-1,m+=" "+r);break;default:n=v+1,m=e===v?g.sPageButtonActive:""}null!==n&&(x=f("",{"class":g.sPageButton+" "+m,"aria-controls":a.sTableId,"aria-label":l[v],"data-dt-idx":q,tabindex:x,id:0===c&&"string"===typeof v?a.sTableId+"_"+v:null}).html(n).appendTo(b),$a(x,{action:v},u),q++)}}};try{var v=f(b).find(y.activeElement).data("dt-idx")}catch(mc){}t(f(b).empty(),d);v!==p&&f(b).find("[data-dt-idx="+ -v+"]").focus()}}});f.extend(q.ext.type.detect,[function(a,b){b=b.oLanguage.sDecimal;return db(a,b)?"num"+b:null},function(a,b){if(a&&!(a instanceof Date)&&!cc.test(a))return null;b=Date.parse(a);return null!==b&&!isNaN(b)||P(a)?"date":null},function(a,b){b=b.oLanguage.sDecimal;return db(a,b,!0)?"num-fmt"+b:null},function(a,b){b=b.oLanguage.sDecimal;return Ub(a,b)?"html-num"+b:null},function(a,b){b=b.oLanguage.sDecimal;return Ub(a,b,!0)?"html-num-fmt"+b:null},function(a,b){return P(a)||"string"=== -typeof a&&-1!==a.indexOf("<")?"html":null}]);f.extend(q.ext.type.search,{html:function(a){return P(a)?a:"string"===typeof a?a.replace(Rb," ").replace(Ea,""):""},string:function(a){return P(a)?a:"string"===typeof a?a.replace(Rb," "):a}});var Da=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Tb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};f.extend(C.type.order,{"date-pre":function(a){a=Date.parse(a);return isNaN(a)?-Infinity:a},"html-pre":function(a){return P(a)? -"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return P(a)?"":"string"===typeof a?a.toLowerCase():a.toString?a.toString():""},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a,b){return ab?-1:0}});Ha("");f.extend(!0,q.ext.renderer,{header:{_:function(a,b,c,d){f(a.nTable).on("order.dt.DT",function(e,f,g,k){a===f&&(e=c.idx,b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass("asc"==k[e]?d.sSortAsc:"desc"==k[e]?d.sSortDesc: -c.sSortingClass))})},jqueryui:function(a,b,c,d){f("
").addClass(d.sSortJUIWrapper).append(b.contents()).append(f("").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);f(a.nTable).on("order.dt.DT",function(e,f,g,k){a===f&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==k[e]?d.sSortAsc:"desc"==k[e]?d.sSortDesc:c.sSortingClass),b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass("asc"== -k[e]?d.sSortJUIAsc:"desc"==k[e]?d.sSortJUIDesc:c.sSortingClassJUI))})}}});var ib=function(a){return"string"===typeof a?a.replace(//g,">").replace(/"/g,"""):a};q.render={number:function(a,b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return ib(f);h=h.toFixed(c);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g, -a)+f+(e||"")}}},text:function(){return{display:ib,filter:ib}}};f.extend(q.ext.internal,{_fnExternApiFunc:Qb,_fnBuildAjax:va,_fnAjaxUpdate:qb,_fnAjaxParameters:zb,_fnAjaxUpdateDraw:Ab,_fnAjaxDataSrc:wa,_fnAddColumn:Ia,_fnColumnOptions:ma,_fnAdjustColumnSizing:aa,_fnVisibleToColumnIndex:ba,_fnColumnIndexToVisible:ca,_fnVisbleColumns:W,_fnGetColumns:oa,_fnColumnTypes:Ka,_fnApplyColumnDefs:nb,_fnHungarianMap:H,_fnCamelToHungarian:L,_fnLanguageCompat:Ga,_fnBrowserDetect:lb,_fnAddData:R,_fnAddTr:pa,_fnNodeToDataIndex:function(a, -b){return b._DT_RowIndex!==p?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return f.inArray(c,a.aoData[b].anCells)},_fnGetCellData:I,_fnSetCellData:ob,_fnSplitObjNotation:Na,_fnGetObjectDataFn:U,_fnSetObjectDataFn:Q,_fnGetDataMaster:Oa,_fnClearTable:qa,_fnDeleteIndex:ra,_fnInvalidate:ea,_fnGetRowElements:Ma,_fnCreateTr:La,_fnBuildHead:pb,_fnDrawHead:ha,_fnDraw:S,_fnReDraw:V,_fnAddOptionsHtml:sb,_fnDetectHeader:fa,_fnGetUniqueThs:ua,_fnFeatureHtmlFilter:ub,_fnFilterComplete:ia,_fnFilterCustom:Db, -_fnFilterColumn:Cb,_fnFilter:Bb,_fnFilterCreateSearch:Ta,_fnEscapeRegex:Ua,_fnFilterData:Eb,_fnFeatureHtmlInfo:xb,_fnUpdateInfo:Hb,_fnInfoMacros:Ib,_fnInitialise:ja,_fnInitComplete:xa,_fnLengthChange:Va,_fnFeatureHtmlLength:tb,_fnFeatureHtmlPaginate:yb,_fnPageChange:Xa,_fnFeatureHtmlProcessing:vb,_fnProcessingDisplay:K,_fnFeatureHtmlTable:wb,_fnScrollDraw:na,_fnApplyToChildren:N,_fnCalculateColumnWidths:Ja,_fnThrottle:Sa,_fnConvertToWidth:Jb,_fnGetWidestNode:Kb,_fnGetMaxLenString:Lb,_fnStringToCss:B, -_fnSortFlatten:Y,_fnSort:rb,_fnSortAria:Nb,_fnSortListener:Za,_fnSortAttachListener:Qa,_fnSortingClasses:Aa,_fnSortData:Mb,_fnSaveState:Ba,_fnLoadState:Ob,_fnSettingsFromNode:Ca,_fnLog:O,_fnMap:M,_fnBindAction:$a,_fnCallbackReg:E,_fnCallbackFire:A,_fnLengthOverflow:Wa,_fnRenderer:Ra,_fnDataSource:D,_fnRowAttributes:Pa,_fnExtend:ab,_fnCalculateEnd:function(){}});f.fn.dataTable=q;q.$=f;f.fn.dataTableSettings=q.settings;f.fn.dataTableExt=q.ext;f.fn.DataTable=function(a){return f(this).dataTable(a).api()}; -f.each(q,function(a,b){f.fn.DataTable[a]=b});return f.fn.dataTable}); diff --git a/docs/articles/xportr_files/header-attrs-2.9/header-attrs.js b/docs/articles/xportr_files/header-attrs-2.9/header-attrs.js deleted file mode 100644 index dd57d92e..00000000 --- a/docs/articles/xportr_files/header-attrs-2.9/header-attrs.js +++ /dev/null @@ -1,12 +0,0 @@ -// Pandoc 2.9 adds attributes on both header and div. We remove the former (to -// be compatible with the behavior of Pandoc < 2.8). -document.addEventListener('DOMContentLoaded', function(e) { - var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); - var i, h, a; - for (i = 0; i < hs.length; i++) { - h = hs[i]; - if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 - a = h.attributes; - while (a.length > 0) h.removeAttribute(a[0].name); - } -}); diff --git a/docs/articles/xportr_files/htmlwidgets-1.5.3/htmlwidgets.js b/docs/articles/xportr_files/htmlwidgets-1.5.3/htmlwidgets.js deleted file mode 100644 index 3d227624..00000000 --- a/docs/articles/xportr_files/htmlwidgets-1.5.3/htmlwidgets.js +++ /dev/null @@ -1,903 +0,0 @@ -(function() { - // If window.HTMLWidgets is already defined, then use it; otherwise create a - // new object. This allows preceding code to set options that affect the - // initialization process (though none currently exist). - window.HTMLWidgets = window.HTMLWidgets || {}; - - // See if we're running in a viewer pane. If not, we're in a web browser. - var viewerMode = window.HTMLWidgets.viewerMode = - /\bviewer_pane=1\b/.test(window.location); - - // See if we're running in Shiny mode. If not, it's a static document. - // Note that static widgets can appear in both Shiny and static modes, but - // obviously, Shiny widgets can only appear in Shiny apps/documents. - var shinyMode = window.HTMLWidgets.shinyMode = - typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; - - // We can't count on jQuery being available, so we implement our own - // version if necessary. - function querySelectorAll(scope, selector) { - if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) { - return scope.find(selector); - } - if (scope.querySelectorAll) { - return scope.querySelectorAll(selector); - } - } - - function asArray(value) { - if (value === null) - return []; - if ($.isArray(value)) - return value; - return [value]; - } - - // Implement jQuery's extend - function extend(target /*, ... */) { - if (arguments.length == 1) { - return target; - } - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - for (var prop in source) { - if (source.hasOwnProperty(prop)) { - target[prop] = source[prop]; - } - } - } - return target; - } - - // IE8 doesn't support Array.forEach. - function forEach(values, callback, thisArg) { - if (values.forEach) { - values.forEach(callback, thisArg); - } else { - for (var i = 0; i < values.length; i++) { - callback.call(thisArg, values[i], i, values); - } - } - } - - // Replaces the specified method with the return value of funcSource. - // - // Note that funcSource should not BE the new method, it should be a function - // that RETURNS the new method. funcSource receives a single argument that is - // the overridden method, it can be called from the new method. The overridden - // method can be called like a regular function, it has the target permanently - // bound to it so "this" will work correctly. - function overrideMethod(target, methodName, funcSource) { - var superFunc = target[methodName] || function() {}; - var superFuncBound = function() { - return superFunc.apply(target, arguments); - }; - target[methodName] = funcSource(superFuncBound); - } - - // Add a method to delegator that, when invoked, calls - // delegatee.methodName. If there is no such method on - // the delegatee, but there was one on delegator before - // delegateMethod was called, then the original version - // is invoked instead. - // For example: - // - // var a = { - // method1: function() { console.log('a1'); } - // method2: function() { console.log('a2'); } - // }; - // var b = { - // method1: function() { console.log('b1'); } - // }; - // delegateMethod(a, b, "method1"); - // delegateMethod(a, b, "method2"); - // a.method1(); - // a.method2(); - // - // The output would be "b1", "a2". - function delegateMethod(delegator, delegatee, methodName) { - var inherited = delegator[methodName]; - delegator[methodName] = function() { - var target = delegatee; - var method = delegatee[methodName]; - - // The method doesn't exist on the delegatee. Instead, - // call the method on the delegator, if it exists. - if (!method) { - target = delegator; - method = inherited; - } - - if (method) { - return method.apply(target, arguments); - } - }; - } - - // Implement a vague facsimilie of jQuery's data method - function elementData(el, name, value) { - if (arguments.length == 2) { - return el["htmlwidget_data_" + name]; - } else if (arguments.length == 3) { - el["htmlwidget_data_" + name] = value; - return el; - } else { - throw new Error("Wrong number of arguments for elementData: " + - arguments.length); - } - } - - // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex - function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - } - - function hasClass(el, className) { - var re = new RegExp("\\b" + escapeRegExp(className) + "\\b"); - return re.test(el.className); - } - - // elements - array (or array-like object) of HTML elements - // className - class name to test for - // include - if true, only return elements with given className; - // if false, only return elements *without* given className - function filterByClass(elements, className, include) { - var results = []; - for (var i = 0; i < elements.length; i++) { - if (hasClass(elements[i], className) == include) - results.push(elements[i]); - } - return results; - } - - function on(obj, eventName, func) { - if (obj.addEventListener) { - obj.addEventListener(eventName, func, false); - } else if (obj.attachEvent) { - obj.attachEvent(eventName, func); - } - } - - function off(obj, eventName, func) { - if (obj.removeEventListener) - obj.removeEventListener(eventName, func, false); - else if (obj.detachEvent) { - obj.detachEvent(eventName, func); - } - } - - // Translate array of values to top/right/bottom/left, as usual with - // the "padding" CSS property - // https://developer.mozilla.org/en-US/docs/Web/CSS/padding - function unpackPadding(value) { - if (typeof(value) === "number") - value = [value]; - if (value.length === 1) { - return {top: value[0], right: value[0], bottom: value[0], left: value[0]}; - } - if (value.length === 2) { - return {top: value[0], right: value[1], bottom: value[0], left: value[1]}; - } - if (value.length === 3) { - return {top: value[0], right: value[1], bottom: value[2], left: value[1]}; - } - if (value.length === 4) { - return {top: value[0], right: value[1], bottom: value[2], left: value[3]}; - } - } - - // Convert an unpacked padding object to a CSS value - function paddingToCss(paddingObj) { - return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px"; - } - - // Makes a number suitable for CSS - function px(x) { - if (typeof(x) === "number") - return x + "px"; - else - return x; - } - - // Retrieves runtime widget sizing information for an element. - // The return value is either null, or an object with fill, padding, - // defaultWidth, defaultHeight fields. - function sizingPolicy(el) { - var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']"); - if (!sizingEl) - return null; - var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}"); - if (viewerMode) { - return sp.viewer; - } else { - return sp.browser; - } - } - - // @param tasks Array of strings (or falsy value, in which case no-op). - // Each element must be a valid JavaScript expression that yields a - // function. Or, can be an array of objects with "code" and "data" - // properties; in this case, the "code" property should be a string - // of JS that's an expr that yields a function, and "data" should be - // an object that will be added as an additional argument when that - // function is called. - // @param target The object that will be "this" for each function - // execution. - // @param args Array of arguments to be passed to the functions. (The - // same arguments will be passed to all functions.) - function evalAndRun(tasks, target, args) { - if (tasks) { - forEach(tasks, function(task) { - var theseArgs = args; - if (typeof(task) === "object") { - theseArgs = theseArgs.concat([task.data]); - task = task.code; - } - var taskFunc = tryEval(task); - if (typeof(taskFunc) !== "function") { - throw new Error("Task must be a function! Source:\n" + task); - } - taskFunc.apply(target, theseArgs); - }); - } - } - - // Attempt eval() both with and without enclosing in parentheses. - // Note that enclosing coerces a function declaration into - // an expression that eval() can parse - // (otherwise, a SyntaxError is thrown) - function tryEval(code) { - var result = null; - try { - result = eval("(" + code + ")"); - } catch(error) { - if (!error instanceof SyntaxError) { - throw error; - } - try { - result = eval(code); - } catch(e) { - if (e instanceof SyntaxError) { - throw error; - } else { - throw e; - } - } - } - return result; - } - - function initSizing(el) { - var sizing = sizingPolicy(el); - if (!sizing) - return; - - var cel = document.getElementById("htmlwidget_container"); - if (!cel) - return; - - if (typeof(sizing.padding) !== "undefined") { - document.body.style.margin = "0"; - document.body.style.padding = paddingToCss(unpackPadding(sizing.padding)); - } - - if (sizing.fill) { - document.body.style.overflow = "hidden"; - document.body.style.width = "100%"; - document.body.style.height = "100%"; - document.documentElement.style.width = "100%"; - document.documentElement.style.height = "100%"; - if (cel) { - cel.style.position = "absolute"; - var pad = unpackPadding(sizing.padding); - cel.style.top = pad.top + "px"; - cel.style.right = pad.right + "px"; - cel.style.bottom = pad.bottom + "px"; - cel.style.left = pad.left + "px"; - el.style.width = "100%"; - el.style.height = "100%"; - } - - return { - getWidth: function() { return cel.offsetWidth; }, - getHeight: function() { return cel.offsetHeight; } - }; - - } else { - el.style.width = px(sizing.width); - el.style.height = px(sizing.height); - - return { - getWidth: function() { return el.offsetWidth; }, - getHeight: function() { return el.offsetHeight; } - }; - } - } - - // Default implementations for methods - var defaults = { - find: function(scope) { - return querySelectorAll(scope, "." + this.name); - }, - renderError: function(el, err) { - var $el = $(el); - - this.clearError(el); - - // Add all these error classes, as Shiny does - var errClass = "shiny-output-error"; - if (err.type !== null) { - // use the classes of the error condition as CSS class names - errClass = errClass + " " + $.map(asArray(err.type), function(type) { - return errClass + "-" + type; - }).join(" "); - } - errClass = errClass + " htmlwidgets-error"; - - // Is el inline or block? If inline or inline-block, just display:none it - // and add an inline error. - var display = $el.css("display"); - $el.data("restore-display-mode", display); - - if (display === "inline" || display === "inline-block") { - $el.hide(); - if (err.message !== "") { - var errorSpan = $("").addClass(errClass); - errorSpan.text(err.message); - $el.after(errorSpan); - } - } else if (display === "block") { - // If block, add an error just after the el, set visibility:none on the - // el, and position the error to be on top of the el. - // Mark it with a unique ID and CSS class so we can remove it later. - $el.css("visibility", "hidden"); - if (err.message !== "") { - var errorDiv = $("
").addClass(errClass).css("position", "absolute") - .css("top", el.offsetTop) - .css("left", el.offsetLeft) - // setting width can push out the page size, forcing otherwise - // unnecessary scrollbars to appear and making it impossible for - // the element to shrink; so use max-width instead - .css("maxWidth", el.offsetWidth) - .css("height", el.offsetHeight); - errorDiv.text(err.message); - $el.after(errorDiv); - - // Really dumb way to keep the size/position of the error in sync with - // the parent element as the window is resized or whatever. - var intId = setInterval(function() { - if (!errorDiv[0].parentElement) { - clearInterval(intId); - return; - } - errorDiv - .css("top", el.offsetTop) - .css("left", el.offsetLeft) - .css("maxWidth", el.offsetWidth) - .css("height", el.offsetHeight); - }, 500); - } - } - }, - clearError: function(el) { - var $el = $(el); - var display = $el.data("restore-display-mode"); - $el.data("restore-display-mode", null); - - if (display === "inline" || display === "inline-block") { - if (display) - $el.css("display", display); - $(el.nextSibling).filter(".htmlwidgets-error").remove(); - } else if (display === "block"){ - $el.css("visibility", "inherit"); - $(el.nextSibling).filter(".htmlwidgets-error").remove(); - } - }, - sizing: {} - }; - - // Called by widget bindings to register a new type of widget. The definition - // object can contain the following properties: - // - name (required) - A string indicating the binding name, which will be - // used by default as the CSS classname to look for. - // - initialize (optional) - A function(el) that will be called once per - // widget element; if a value is returned, it will be passed as the third - // value to renderValue. - // - renderValue (required) - A function(el, data, initValue) that will be - // called with data. Static contexts will cause this to be called once per - // element; Shiny apps will cause this to be called multiple times per - // element, as the data changes. - window.HTMLWidgets.widget = function(definition) { - if (!definition.name) { - throw new Error("Widget must have a name"); - } - if (!definition.type) { - throw new Error("Widget must have a type"); - } - // Currently we only support output widgets - if (definition.type !== "output") { - throw new Error("Unrecognized widget type '" + definition.type + "'"); - } - // TODO: Verify that .name is a valid CSS classname - - // Support new-style instance-bound definitions. Old-style class-bound - // definitions have one widget "object" per widget per type/class of - // widget; the renderValue and resize methods on such widget objects - // take el and instance arguments, because the widget object can't - // store them. New-style instance-bound definitions have one widget - // object per widget instance; the definition that's passed in doesn't - // provide renderValue or resize methods at all, just the single method - // factory(el, width, height) - // which returns an object that has renderValue(x) and resize(w, h). - // This enables a far more natural programming style for the widget - // author, who can store per-instance state using either OO-style - // instance fields or functional-style closure variables (I guess this - // is in contrast to what can only be called C-style pseudo-OO which is - // what we required before). - if (definition.factory) { - definition = createLegacyDefinitionAdapter(definition); - } - - if (!definition.renderValue) { - throw new Error("Widget must have a renderValue function"); - } - - // For static rendering (non-Shiny), use a simple widget registration - // scheme. We also use this scheme for Shiny apps/documents that also - // contain static widgets. - window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; - // Merge defaults into the definition; don't mutate the original definition. - var staticBinding = extend({}, defaults, definition); - overrideMethod(staticBinding, "find", function(superfunc) { - return function(scope) { - var results = superfunc(scope); - // Filter out Shiny outputs, we only want the static kind - return filterByClass(results, "html-widget-output", false); - }; - }); - window.HTMLWidgets.widgets.push(staticBinding); - - if (shinyMode) { - // Shiny is running. Register the definition with an output binding. - // The definition itself will not be the output binding, instead - // we will make an output binding object that delegates to the - // definition. This is because we foolishly used the same method - // name (renderValue) for htmlwidgets definition and Shiny bindings - // but they actually have quite different semantics (the Shiny - // bindings receive data that includes lots of metadata that it - // strips off before calling htmlwidgets renderValue). We can't - // just ignore the difference because in some widgets it's helpful - // to call this.renderValue() from inside of resize(), and if - // we're not delegating, then that call will go to the Shiny - // version instead of the htmlwidgets version. - - // Merge defaults with definition, without mutating either. - var bindingDef = extend({}, defaults, definition); - - // This object will be our actual Shiny binding. - var shinyBinding = new Shiny.OutputBinding(); - - // With a few exceptions, we'll want to simply use the bindingDef's - // version of methods if they are available, otherwise fall back to - // Shiny's defaults. NOTE: If Shiny's output bindings gain additional - // methods in the future, and we want them to be overrideable by - // HTMLWidget binding definitions, then we'll need to add them to this - // list. - delegateMethod(shinyBinding, bindingDef, "getId"); - delegateMethod(shinyBinding, bindingDef, "onValueChange"); - delegateMethod(shinyBinding, bindingDef, "onValueError"); - delegateMethod(shinyBinding, bindingDef, "renderError"); - delegateMethod(shinyBinding, bindingDef, "clearError"); - delegateMethod(shinyBinding, bindingDef, "showProgress"); - - // The find, renderValue, and resize are handled differently, because we - // want to actually decorate the behavior of the bindingDef methods. - - shinyBinding.find = function(scope) { - var results = bindingDef.find(scope); - - // Only return elements that are Shiny outputs, not static ones - var dynamicResults = results.filter(".html-widget-output"); - - // It's possible that whatever caused Shiny to think there might be - // new dynamic outputs, also caused there to be new static outputs. - // Since there might be lots of different htmlwidgets bindings, we - // schedule execution for later--no need to staticRender multiple - // times. - if (results.length !== dynamicResults.length) - scheduleStaticRender(); - - return dynamicResults; - }; - - // Wrap renderValue to handle initialization, which unfortunately isn't - // supported natively by Shiny at the time of this writing. - - shinyBinding.renderValue = function(el, data) { - Shiny.renderDependencies(data.deps); - // Resolve strings marked as javascript literals to objects - if (!(data.evals instanceof Array)) data.evals = [data.evals]; - for (var i = 0; data.evals && i < data.evals.length; i++) { - window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); - } - if (!bindingDef.renderOnNullValue) { - if (data.x === null) { - el.style.visibility = "hidden"; - return; - } else { - el.style.visibility = "inherit"; - } - } - if (!elementData(el, "initialized")) { - initSizing(el); - - elementData(el, "initialized", true); - if (bindingDef.initialize) { - var result = bindingDef.initialize(el, el.offsetWidth, - el.offsetHeight); - elementData(el, "init_result", result); - } - } - bindingDef.renderValue(el, data.x, elementData(el, "init_result")); - evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); - }; - - // Only override resize if bindingDef implements it - if (bindingDef.resize) { - shinyBinding.resize = function(el, width, height) { - // Shiny can call resize before initialize/renderValue have been - // called, which doesn't make sense for widgets. - if (elementData(el, "initialized")) { - bindingDef.resize(el, width, height, elementData(el, "init_result")); - } - }; - } - - Shiny.outputBindings.register(shinyBinding, bindingDef.name); - } - }; - - var scheduleStaticRenderTimerId = null; - function scheduleStaticRender() { - if (!scheduleStaticRenderTimerId) { - scheduleStaticRenderTimerId = setTimeout(function() { - scheduleStaticRenderTimerId = null; - window.HTMLWidgets.staticRender(); - }, 1); - } - } - - // Render static widgets after the document finishes loading - // Statically render all elements that are of this widget's class - window.HTMLWidgets.staticRender = function() { - var bindings = window.HTMLWidgets.widgets || []; - forEach(bindings, function(binding) { - var matches = binding.find(document.documentElement); - forEach(matches, function(el) { - var sizeObj = initSizing(el, binding); - - if (hasClass(el, "html-widget-static-bound")) - return; - el.className = el.className + " html-widget-static-bound"; - - var initResult; - if (binding.initialize) { - initResult = binding.initialize(el, - sizeObj ? sizeObj.getWidth() : el.offsetWidth, - sizeObj ? sizeObj.getHeight() : el.offsetHeight - ); - elementData(el, "init_result", initResult); - } - - if (binding.resize) { - var lastSize = { - w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, - h: sizeObj ? sizeObj.getHeight() : el.offsetHeight - }; - var resizeHandler = function(e) { - var size = { - w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, - h: sizeObj ? sizeObj.getHeight() : el.offsetHeight - }; - if (size.w === 0 && size.h === 0) - return; - if (size.w === lastSize.w && size.h === lastSize.h) - return; - lastSize = size; - binding.resize(el, size.w, size.h, initResult); - }; - - on(window, "resize", resizeHandler); - - // This is needed for cases where we're running in a Shiny - // app, but the widget itself is not a Shiny output, but - // rather a simple static widget. One example of this is - // an rmarkdown document that has runtime:shiny and widget - // that isn't in a render function. Shiny only knows to - // call resize handlers for Shiny outputs, not for static - // widgets, so we do it ourselves. - if (window.jQuery) { - window.jQuery(document).on( - "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", - resizeHandler - ); - window.jQuery(document).on( - "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", - resizeHandler - ); - } - - // This is needed for the specific case of ioslides, which - // flips slides between display:none and display:block. - // Ideally we would not have to have ioslide-specific code - // here, but rather have ioslides raise a generic event, - // but the rmarkdown package just went to CRAN so the - // window to getting that fixed may be long. - if (window.addEventListener) { - // It's OK to limit this to window.addEventListener - // browsers because ioslides itself only supports - // such browsers. - on(document, "slideenter", resizeHandler); - on(document, "slideleave", resizeHandler); - } - } - - var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); - if (scriptData) { - var data = JSON.parse(scriptData.textContent || scriptData.text); - // Resolve strings marked as javascript literals to objects - if (!(data.evals instanceof Array)) data.evals = [data.evals]; - for (var k = 0; data.evals && k < data.evals.length; k++) { - window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); - } - binding.renderValue(el, data.x, initResult); - evalAndRun(data.jsHooks.render, initResult, [el, data.x]); - } - }); - }); - - invokePostRenderHandlers(); - } - - - function has_jQuery3() { - if (!window.jQuery) { - return false; - } - var $version = window.jQuery.fn.jquery; - var $major_version = parseInt($version.split(".")[0]); - return $major_version >= 3; - } - - /* - / Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's - / on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now - / really means $(setTimeout(fn)). - / https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous - / - / Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny - / one tick later than it did before, which means staticRender() is - / called renderValue() earlier than (advanced) widget authors might be expecting. - / https://github.com/rstudio/shiny/issues/2630 - / - / For a concrete example, leaflet has some methods (e.g., updateBounds) - / which reference Shiny methods registered in initShiny (e.g., setInputValue). - / Since leaflet is privy to this life-cycle, it knows to use setTimeout() to - / delay execution of those methods (until Shiny methods are ready) - / https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268 - / - / Ideally widget authors wouldn't need to use this setTimeout() hack that - / leaflet uses to call Shiny methods on a staticRender(). In the long run, - / the logic initShiny should be broken up so that method registration happens - / right away, but binding happens later. - */ - function maybeStaticRenderLater() { - if (shinyMode && has_jQuery3()) { - window.jQuery(window.HTMLWidgets.staticRender); - } else { - window.HTMLWidgets.staticRender(); - } - } - - if (document.addEventListener) { - document.addEventListener("DOMContentLoaded", function() { - document.removeEventListener("DOMContentLoaded", arguments.callee, false); - maybeStaticRenderLater(); - }, false); - } else if (document.attachEvent) { - document.attachEvent("onreadystatechange", function() { - if (document.readyState === "complete") { - document.detachEvent("onreadystatechange", arguments.callee); - maybeStaticRenderLater(); - } - }); - } - - - window.HTMLWidgets.getAttachmentUrl = function(depname, key) { - // If no key, default to the first item - if (typeof(key) === "undefined") - key = 1; - - var link = document.getElementById(depname + "-" + key + "-attachment"); - if (!link) { - throw new Error("Attachment " + depname + "/" + key + " not found in document"); - } - return link.getAttribute("href"); - }; - - window.HTMLWidgets.dataframeToD3 = function(df) { - var names = []; - var length; - for (var name in df) { - if (df.hasOwnProperty(name)) - names.push(name); - if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { - throw new Error("All fields must be arrays"); - } else if (typeof(length) !== "undefined" && length !== df[name].length) { - throw new Error("All fields must be arrays of the same length"); - } - length = df[name].length; - } - var results = []; - var item; - for (var row = 0; row < length; row++) { - item = {}; - for (var col = 0; col < names.length; col++) { - item[names[col]] = df[names[col]][row]; - } - results.push(item); - } - return results; - }; - - window.HTMLWidgets.transposeArray2D = function(array) { - if (array.length === 0) return array; - var newArray = array[0].map(function(col, i) { - return array.map(function(row) { - return row[i] - }) - }); - return newArray; - }; - // Split value at splitChar, but allow splitChar to be escaped - // using escapeChar. Any other characters escaped by escapeChar - // will be included as usual (including escapeChar itself). - function splitWithEscape(value, splitChar, escapeChar) { - var results = []; - var escapeMode = false; - var currentResult = ""; - for (var pos = 0; pos < value.length; pos++) { - if (!escapeMode) { - if (value[pos] === splitChar) { - results.push(currentResult); - currentResult = ""; - } else if (value[pos] === escapeChar) { - escapeMode = true; - } else { - currentResult += value[pos]; - } - } else { - currentResult += value[pos]; - escapeMode = false; - } - } - if (currentResult !== "") { - results.push(currentResult); - } - return results; - } - // Function authored by Yihui/JJ Allaire - window.HTMLWidgets.evaluateStringMember = function(o, member) { - var parts = splitWithEscape(member, '.', '\\'); - for (var i = 0, l = parts.length; i < l; i++) { - var part = parts[i]; - // part may be a character or 'numeric' member name - if (o !== null && typeof o === "object" && part in o) { - if (i == (l - 1)) { // if we are at the end of the line then evalulate - if (typeof o[part] === "string") - o[part] = tryEval(o[part]); - } else { // otherwise continue to next embedded object - o = o[part]; - } - } - } - }; - - // Retrieve the HTMLWidget instance (i.e. the return value of an - // HTMLWidget binding's initialize() or factory() function) - // associated with an element, or null if none. - window.HTMLWidgets.getInstance = function(el) { - return elementData(el, "init_result"); - }; - - // Finds the first element in the scope that matches the selector, - // and returns the HTMLWidget instance (i.e. the return value of - // an HTMLWidget binding's initialize() or factory() function) - // associated with that element, if any. If no element matches the - // selector, or the first matching element has no HTMLWidget - // instance associated with it, then null is returned. - // - // The scope argument is optional, and defaults to window.document. - window.HTMLWidgets.find = function(scope, selector) { - if (arguments.length == 1) { - selector = scope; - scope = document; - } - - var el = scope.querySelector(selector); - if (el === null) { - return null; - } else { - return window.HTMLWidgets.getInstance(el); - } - }; - - // Finds all elements in the scope that match the selector, and - // returns the HTMLWidget instances (i.e. the return values of - // an HTMLWidget binding's initialize() or factory() function) - // associated with the elements, in an array. If elements that - // match the selector don't have an associated HTMLWidget - // instance, the returned array will contain nulls. - // - // The scope argument is optional, and defaults to window.document. - window.HTMLWidgets.findAll = function(scope, selector) { - if (arguments.length == 1) { - selector = scope; - scope = document; - } - - var nodes = scope.querySelectorAll(selector); - var results = []; - for (var i = 0; i < nodes.length; i++) { - results.push(window.HTMLWidgets.getInstance(nodes[i])); - } - return results; - }; - - var postRenderHandlers = []; - function invokePostRenderHandlers() { - while (postRenderHandlers.length) { - var handler = postRenderHandlers.shift(); - if (handler) { - handler(); - } - } - } - - // Register the given callback function to be invoked after the - // next time static widgets are rendered. - window.HTMLWidgets.addPostRenderHandler = function(callback) { - postRenderHandlers.push(callback); - }; - - // Takes a new-style instance-bound definition, and returns an - // old-style class-bound definition. This saves us from having - // to rewrite all the logic in this file to accomodate both - // types of definitions. - function createLegacyDefinitionAdapter(defn) { - var result = { - name: defn.name, - type: defn.type, - initialize: function(el, width, height) { - return defn.factory(el, width, height); - }, - renderValue: function(el, x, instance) { - return instance.renderValue(x); - }, - resize: function(el, width, height, instance) { - return instance.resize(width, height); - } - }; - - if (defn.find) - result.find = defn.find; - if (defn.renderError) - result.renderError = defn.renderError; - if (defn.clearError) - result.clearError = defn.clearError; - - return result; - } -})(); - diff --git a/docs/articles/xportr_files/jquery-3.5.1/jquery-AUTHORS.txt b/docs/articles/xportr_files/jquery-3.5.1/jquery-AUTHORS.txt deleted file mode 100644 index 06df1a53..00000000 --- a/docs/articles/xportr_files/jquery-3.5.1/jquery-AUTHORS.txt +++ /dev/null @@ -1,357 +0,0 @@ -Authors ordered by first contribution. - -John Resig -Gilles van den Hoven -Michael Geary -Stefan Petre -Yehuda Katz -Corey Jewett -Klaus Hartl -Franck Marcia -Jรถrn Zaefferer -Paul Bakaus -Brandon Aaron -Mike Alsup -Dave Methvin -Ed Engelhardt -Sean Catchpole -Paul Mclanahan -David Serduke -Richard D. Worth -Scott Gonzรกlez -Ariel Flesler -Cheah Chu Yeow -Andrew Chalkley -Fabio Buffoni -Stefanย Bauckmeierย  -Jon Evans -TJ Holowaychuk -Riccardo De Agostini -Michael Bensoussan -Louis-Rรฉmi Babรฉ -Robert Katiฤ‡ -Damian Janowski -Anton Kovalyov -Duลกan B. Jovanovic -Earle Castledine -Rich Dougherty -Kim Dalsgaard -Andrea Giammarchi -Fabian Jakobs -Mark Gibson -Karl Swedberg -Justin Meyer -Ben Alman -James Padolsey -David Petersen -Batiste Bieler -Jake Archibald -Alexander Farkas -Filipe Fortes -Rick Waldron -Neeraj Singh -Paul Irish -Iraรช Carvalho -Matt Curry -Michael Monteleone -Noah Sloan -Tom Viner -J. Ryan Stinnett -Douglas Neiner -Adam J. Sontag -Heungsub Lee -Dave Reed -Carl Fรผrstenberg -Jacob Wright -Ralph Whitbeck -unknown -temp01 -Colin Snover -Jared Grippe -Ryan W Tenney -Alex Sexton -Pinhook -Ron Otten -Jephte Clain -Anton Matzneller -Dan Heberden -Henri Wiechers -Russell Holbrook -Julian Aubourg -Gianni Alessandro Chiappetta -Scott Jehl -James Burke -Jonas Pfenniger -Xavi Ramirez -Sylvester Keil -Brandon Sterne -Mathias Bynens -Lee Carpenter -Timmy Willison <4timmywil@gmail.com> -Corey Frang -Digitalxero -David Murdoch -Josh Varner -Charles McNulty -Jordan Boesch -Jess Thrysoee -Michael Murray -Alexis Abril -Rob Morgan -John Firebaugh -Sam Bisbee -Gilmore Davidson -Brian Brennan -Xavier Montillet -Daniel Pihlstrom -Sahab Yazdani -avaly -Scott Hughes -Mike Sherov -Greg Hazel -Schalk Neethling -Denis Knauf -Timo Tijhof -Steen Nielsen -Anton Ryzhov -Shi Chuan -Matt Mueller -Berker Peksag -Toby Brain -Justin -Daniel Herman -Oleg Gaidarenko -Rock Hymas -Richard Gibson -Rafaรซl Blais Masson -cmc3cn <59194618@qq.com> -Joe Presbrey -Sindre Sorhus -Arne de Bree -Vladislav Zarakovsky -Andrew E Monat -Oskari -Joao Henrique de Andrade Bruni -tsinha -Dominik D. Geyer -Matt Farmer -Trey Hunner -Jason Moon -Jeffery To -Kris Borchers -Vladimir Zhuravlev -Jacob Thornton -Chad Killingsworth -Vitya Muhachev -Nowres Rafid -David Benjamin -Alan Plum -Uri Gilad -Chris Faulkner -Marcel Greter -Elijah Manor -Daniel Chatfield -Daniel Gรกlvez -Nikita Govorov -Wesley Walser -Mike Pennisi -Matthias Jรคggli -Devin Cooper -Markus Staab -Dave Riddle -Callum Macrae -Jonathan Sampson -Benjamin Truyman -Jay Merrifield -James Huston -Sai Lung Wong -Erick Ruiz de Chรกvez -David Bonner -Allen J Schmidt Jr -Akintayo Akinwunmi -MORGAN -Ismail Khair -Carl Danley -Mike Petrovich -Greg Lavallee -Tom H Fuertes -Roland Eckl -Yiming He -David Fox -Bennett Sorbo -Paul Ramos -Rod Vagg -Sebastian Burkhard -Zachary Adam Kaplan -Adam Coulombe -nanto_vi -nanto -Danil Somsikov -Ryunosuke SATO -Diego Tres -Jean Boussier -Andrew Plummer -Mark Raddatz -Pascal Borreli -Isaac Z. Schlueter -Karl Sieburg -Nguyen Phuc Lam -Dmitry Gusev -Steven Benner -Li Xudong -Michaล‚ Goล‚ฤ™biowski-Owczarek -Renato Oliveira dos Santos -Frederic Junod -Tom H Fuertes -Mitch Foley -ros3cin -Kyle Robinson Young -John Paul -Jason Bedard -Chris Talkington -Eddie Monge -Terry Jones -Jason Merino -Dan Burzo -Jeremy Dunck -Chris Price -Guy Bedford -njhamann -Goare Mao -Amey Sakhadeo -Mike Sidorov -Anthony Ryan -Lihan Li -George Kats -Dongseok Paeng -Ronny Springer -Ilya Kantor -Marian Sollmann -Chris Antaki -David Hong -Jakob Stoeck -Christopher Jones -Forbes Lindesay -S. Andrew Sheppard -Leonardo Balter -Rodrigo Rosenfeld Rosas -Daniel Husar -Philip Jรคgenstedt -John Hoven -Roman ReiรŸ -Benjy Cui -Christian Kosmowski -David Corbacho -Liang Peng -TJ VanToll -Aurelio De Rosa -Senya Pugach -Dan Hart -Nazar Mokrynskyi -Benjamin Tan -Amit Merchant -Jason Bedard -Veaceslav Grimalschi -Richard McDaniel -Arthur Verschaeve -Shivaji Varma -Ben Toews -Bin Xin -Neftaly Hernandez -T.J. Crowder -Nicolas HENRY -Frederic Hemberger -Victor Homyakov -Aditya Raghavan -Anne-Gaelle Colom -Leonardo Braga -George Mauer -Stephen Edgar -Thomas Tortorini -Jรถrn Wagner -Jon Hester -Colin Frick -Winston Howes -Alexander O'Mara -Chris Rebert -Bastian Buchholz -Mu Haibao -Calvin Metcalf -Arthur Stolyar -Gabriel Schulhof -Gilad Peleg -Julian Alexander Murillo -Kevin Kirsche -Martin Naumann -Yongwoo Jeon -John-David Dalton -Marek Lewandowski -Bruno Peฬrel -Daniel Nill -Reed Loden -Sean Henderson -Gary Ye -Richard Kraaijenhagen -Connor Atherton -Christian Grete -Tom von Clef -Liza Ramo -Joelle Fleurantin -Steve Mao -Jon Dufresne -Jae Sung Park -Josh Soref -Saptak Sengupta -Henry Wong -Jun Sun -Martijn W. van der Lee -Devin Wilson -Damian Senn -Zack Hall -Vitaliy Terziev -Todor Prikumov -Bernhard M. Wiedemann -Jha Naman -Alexander Lisianoi -William Robinet -Joe Trumbull -Alexander K -Ralin Chimev -Felipe Sateler -Christophe Tafani-Dereeper -Manoj Kumar -David Broder-Rodgers -Alex Louden -Alex Padilla -karan-96 -ๅ—ๆผ‚ไธ€ๅ’ -Erik Lax -Boom Lee -Andreas Solleder -Pierre Spring -Shashanka Nataraj -CDAGaming -Matan Kotler-Berkowitz <205matan@gmail.com> -Jordan Beland -Henry Zhu -Nilton Cesar -basil.belokon -Andrey Meshkov -tmybr11 -Luis Emilio Velasco Sanchez -Ed S -Bert Zhang -Sรฉbastien Rรจgne -wartmanm <3869625+wartmanm@users.noreply.github.com> -Siddharth Dungarwal -abnud1 -Andrei Fangli -Marja Hรถlttรค -buddh4 -Hoang -Wonseop Kim -Pat O'Callaghan -JuanMa Ruiz -Ahmed.S.ElAfifi -Sean Robinson -Christian Oliff diff --git a/docs/articles/xportr_files/jquery-3.5.1/jquery.js b/docs/articles/xportr_files/jquery-3.5.1/jquery.js deleted file mode 100644 index 50937333..00000000 --- a/docs/articles/xportr_files/jquery-3.5.1/jquery.js +++ /dev/null @@ -1,10872 +0,0 @@ -/*! - * jQuery JavaScript Library v3.5.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2020-05-04T22:49Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var flat = arr.flat ? function( array ) { - return arr.flat.call( array ); -} : function( array ) { - return arr.concat.apply( [], array ); -}; - - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - -var isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - return typeof obj === "function" && typeof obj.nodeType !== "number"; - }; - - -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - -var document = window.document; - - - - var preservedScriptAttributes = { - type: true, - src: true, - nonce: true, - noModule: true - }; - - function DOMEval( code, node, doc ) { - doc = doc || document; - - var i, val, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - - // Support: Firefox 64+, Edge 18+ - // Some browsers don't support the "nonce" property on scripts. - // On the other hand, just using `getAttribute` is not enough as - // the `nonce` attribute is reset to an empty string whenever it - // becomes browsing-context connected. - // See https://github.com/whatwg/html/issues/2369 - // See https://html.spec.whatwg.org/#nonce-attributes - // The `node.getAttribute` check was added for the sake of - // `jQuery.globalEval` so that it can fake a nonce-containing node - // via an object. - val = node[ i ] || node.getAttribute && node.getAttribute( i ); - if ( val ) { - script.setAttribute( i, val ); - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.5.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - even: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return ( i + 1 ) % 2; - } ) ); - }, - - odd: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return i % 2; - } ) ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - copy = options[ name ]; - - // Prevent Object.prototype pollution - // Prevent never-ending loop - if ( name === "__proto__" || target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - src = target[ name ]; - - // Ensure proper type for the source value - if ( copyIsArray && !Array.isArray( src ) ) { - clone = []; - } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { - clone = {}; - } else { - clone = src; - } - copyIsArray = false; - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a provided context; falls back to the global one - // if not specified. - globalEval: function( code, options, doc ) { - DOMEval( code, { nonce: options && options.nonce }, doc ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return flat( ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( _i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.5 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2020-03-14 - */ -( function( window ) { -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram - identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + - whitespace + "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), - rdescend = new RegExp( whitespace + "|>" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + - "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rhtml = /HTML$/i, - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), - funescape = function( escape, nonHex ) { - var high = "0x" + escape.slice( 1 ) - 0x10000; - - return nonHex ? - - // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - ( arr = slice.call( preferredDoc.childNodes ) ), - preferredDoc.childNodes - ); - - // Support: Android<4.0 - // Detect silently failing push.apply - // eslint-disable-next-line no-unused-expressions - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - setDocument( context ); - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { - - // ID selector - if ( ( m = match[ 1 ] ) ) { - - // Document context - if ( nodeType === 9 ) { - if ( ( elem = context.getElementById( m ) ) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[ 2 ] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - - newSelector = selector; - newContext = context; - - // qSA considers elements outside a scoping root when evaluating child or - // descendant combinators, which is not what we want. - // In such cases, we work around the behavior by prefixing every selector in the - // list with an ID selector referencing the scope context. - // The technique has to be used as well when a leading combinator is used - // as such selectors are not recognized by querySelectorAll. - // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && - ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - - // We can use :scope instead of the ID hack if the browser - // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { - - // Capture the context ID, setting it first if necessary - if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", ( nid = expando ) ); - } - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + - toSelector( groups[ i ] ); - } - newSelector = groups.join( "," ); - } - - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return ( cache[ key + " " ] = value ); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement( "fieldset" ); - - try { - return !!fn( el ); - } catch ( e ) { - return false; - } finally { - - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction( function( argument ) { - argument = +argument; - return markFunction( function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ ( j = matchIndexes[ i ] ) ] ) { - seed[ j ] = !( matches[ j ] = seed[ j ] ); - } - } - } ); - } ); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - var namespace = elem.namespaceURI, - docElem = ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; - } ); - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); - } ); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; - } ); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - } ); - - // ID filter and find - if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode( "id" ); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( ( elem = elems[ i++ ] ) ) { - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { - - var input; - - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); - - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } - - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { - - assert( function( el ) { - - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); - } : - function( a, b ) { - if ( b ) { - while ( ( b = b.parentNode ) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { - - // Choose the first element that is related to our preferred document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( a == document || a.ownerDocument == preferredDoc && - contains( preferredDoc, a ) ) { - return -1; - } - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( b == document || b.ownerDocument == preferredDoc && - contains( preferredDoc, b ) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - return a == document ? -1 : - b == document ? 1 : - /* eslint-enable eqeqeq */ - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( ( cur = cur.parentNode ) ) { - ap.unshift( cur ); - } - cur = b; - while ( ( cur = cur.parentNode ) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[ i ] === bp[ i ] ) { - i++; - } - - return i ? - - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[ i ], bp[ i ] ) : - - // Otherwise nodes in our document sort first - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - ap[ i ] == preferredDoc ? -1 : - bp[ i ] == preferredDoc ? 1 : - /* eslint-enable eqeqeq */ - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - setDocument( elem ); - - if ( support.matchesSelector && documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch ( e ) { - nonnativeSelectorCache( expr, true ); - } - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( context.ownerDocument || context ) != document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( elem.ownerDocument || elem ) != document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - - // If no nodeType, this is expected to be an array - while ( ( node = elem[ i++ ] ) ) { - - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[ 1 ] = match[ 1 ].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || - match[ 5 ] || "" ).replace( runescape, funescape ); - - if ( match[ 2 ] === "~=" ) { - match[ 3 ] = " " + match[ 3 ] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[ 1 ] = match[ 1 ].toLowerCase(); - - if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - - // nth-* requires argument - if ( !match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[ 4 ] = +( match[ 4 ] ? - match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); - match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - - // other types prohibit arguments - } else if ( match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[ 6 ] && match[ 2 ]; - - if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[ 3 ] ) { - match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - - // Get excess from tokenize (recursively) - ( excess = tokenize( unquoted, true ) ) && - - // advance to the next closing parenthesis - ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { - - // excess is a negative index - match[ 0 ] = match[ 0 ].slice( 0, excess ); - match[ 2 ] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { - return true; - } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - ( pattern = new RegExp( "(^|" + whitespace + - ")" + className + "(" + whitespace + "|$)" ) ) && classCache( - className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); - } ); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - /* eslint-disable max-len */ - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - /* eslint-enable max-len */ - - }; - }, - - "CHILD": function( type, what, _argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, _context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( ( node = node[ dir ] ) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( ( node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - - // Use previously-cached element index if available - if ( useCache ) { - - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - - // Use the same loop as above to seek `elem` from the start - while ( ( node = ++nodeIndex && node && node[ dir ] || - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || - ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction( function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[ i ] ); - seed[ idx ] = !( matches[ idx ] = matched[ i ] ); - } - } ) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - - // Potentially complex pseudos - "not": markFunction( function( selector ) { - - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction( function( seed, matches, _context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( ( elem = unmatched[ i ] ) ) { - seed[ i ] = !( matches[ i ] = elem ); - } - } - } ) : - function( elem, _context, xml ) { - input[ 0 ] = elem; - matcher( input, null, xml, results ); - - // Don't keep the element (issue #299) - input[ 0 ] = null; - return !results.pop(); - }; - } ), - - "has": markFunction( function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - } ), - - "contains": markFunction( function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; - }; - } ), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - - // lang value must be a valid identifier - if ( !ridentifier.test( lang || "" ) ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( ( elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); - return false; - }; - } ), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && - ( !document.hasFocus || document.hasFocus() ) && - !!( elem.type || elem.href || ~elem.tabIndex ); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return ( nodeName === "input" && !!elem.checked ) || - ( nodeName === "option" && !!elem.selected ); - }, - - "selected": function( elem ) { - - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - // eslint-disable-next-line no-unused-expressions - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos[ "empty" ]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( ( attr = elem.getAttribute( "type" ) ) == null || - attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo( function() { - return [ 0 ]; - } ), - - "last": createPositionalPseudo( function( _matchIndexes, length ) { - return [ length - 1 ]; - } ), - - "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - } ), - - "even": createPositionalPseudo( function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "odd": createPositionalPseudo( function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? - argument + length : - argument > length ? - length : - argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ) - } -}; - -Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || ( match = rcomma.exec( soFar ) ) ) { - if ( match ) { - - // Don't consume trailing commas as valid - soFar = soFar.slice( match[ 0 ].length ) || soFar; - } - groups.push( ( tokens = [] ) ); - } - - matched = false; - - // Combinators - if ( ( match = rcombinators.exec( soFar ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - - // Cast descendant combinators to space - type: match[ 0 ].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || - ( match = preFilters[ type ]( match ) ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[ i ].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || - ( outerCache[ elem.uniqueID ] = {} ); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( ( oldCache = uniqueCache[ key ] ) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return ( newCache[ 2 ] = oldCache[ 2 ] ); - } else { - - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[ i ]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[ 0 ]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[ i ], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( ( elem = unmatched[ i ] ) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction( function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( - selector || "*", - context.nodeType ? [ context ] : context, - [] - ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( ( elem = temp[ i ] ) ) { - matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) ) { - - // Restore matcherIn since elem is not yet a final match - temp.push( ( matcherIn[ i ] = elem ) ); - } - } - postFinder( null, ( matcherOut = [] ), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - - seed[ temp ] = !( results[ temp ] = elem ); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - } ); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[ 0 ].type ], - implicitRelative = leadingRelative || Expr.relative[ " " ], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - ( checkContext = context ).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[ j ].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens - .slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), - - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), - len = elems.length; - - if ( outermost ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - outermostContext = context == document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( !context && elem.ownerDocument != document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( ( matcher = elementMatchers[ j++ ] ) ) { - if ( matcher( elem, context || document, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - - // They will have gone through all possible matchers - if ( ( elem = !matcher && elem ) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( ( matcher = setMatchers[ j++ ] ) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !( unmatched[ i ] || setMatched[ i ] ) ) { - setMatched[ i ] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[ i ] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( - selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) - ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[ 0 ] = match[ 0 ].slice( 0 ); - if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - - context = ( Expr.find[ "ID" ]( token.matches[ 0 ] - .replace( runescape, funescape ), context ) || [] )[ 0 ]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[ i ]; - - // Abort if we hit a combinator - if ( Expr.relative[ ( type = token.type ) ] ) { - break; - } - if ( ( find = Expr.find[ type ] ) ) { - - // Search, expanding context for leading sibling combinators - if ( ( seed = find( - token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || - context - ) ) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert( function( el ) { - - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; -} ); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert( function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute( "href" ) === "#"; -} ) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - } ); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert( function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -} ) ) { - addHandle( "value", function( elem, _name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - } ); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert( function( el ) { - return el.getAttribute( "disabled" ) == null; -} ) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; - } - } ); -} - -return Sizzle; - -} )( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, _i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, _i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, _i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( elem.contentDocument != null && - - // Support: IE 11+ - // elements with no `data` attribute has an object - // `contentDocument` with a `null` prototype. - getProto( elem.contentDocument ) ) { - - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( _i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, _key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( _all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var documentElement = document.documentElement; - - - - var isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ); - }, - composed = { composed: true }; - - // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only - // Check attachment across shadow DOM boundaries when possible (gh-3504) - // Support: iOS 10.0-10.2 only - // Early iOS 10 versions support `attachShadow` but not `getRootNode`, - // leading to errors. We need to check for `getRootNode`. - if ( documentElement.getRootNode ) { - isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ) || - elem.getRootNode( composed ) === elem.ownerDocument; - }; - } -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - isAttached( elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = elem.nodeType && - ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); - -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // Support: IE <=9 only - // IE <=9 replaces "; - support.option = !!div.lastChild; -} )(); - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: IE <=9 only -if ( !support.option ) { - wrapMap.optgroup = wrapMap.option = [ 1, "" ]; -} - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, attached, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - attached = isAttached( elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( attached ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 - 11+ -// focus() and blur() are asynchronous, except when they are no-op. -// So expect focus to be synchronous when the element is already active, -// and blur to be synchronous when the element is not already active. -// (focus and blur are always synchronous in other supported browsers, -// this just defines when we can count on it). -function expectSync( elem, type ) { - return ( elem === safeActiveElement() ) === ( type === "focus" ); -} - -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Only attach events to objects that accept data - if ( !acceptData( elem ) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = Object.create( null ); - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( nativeEvent ), - - handlers = ( - dataPriv.get( this, "events" ) || Object.create( null ) - )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // If the event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( !event.rnamespace || handleObj.namespace === false || - event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - - // Utilize native event to ensure correct state for checkable inputs - setup: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Claim the first handler - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", returnTrue ); - } - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Force setup before triggering a click - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - leverageNative( el, "click" ); - } - - // Return non-false to allow normal event-path propagation - return true; - }, - - // For cross-browser consistency, suppress native .click() on links - // Also prevent it if we're currently inside a leveraged native-event stack - _default: function( event ) { - var target = event.target; - return rcheckableType.test( target.type ) && - target.click && nodeName( target, "input" ) && - dataPriv.get( target, "click" ) || - nodeName( target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -// Ensure the presence of an event listener that handles manually-triggered -// synthetic events by interrupting progress until reinvoked in response to -// *native* events that it fires directly, ensuring that state changes have -// already occurred before other listeners are invoked. -function leverageNative( el, type, expectSync ) { - - // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add - if ( !expectSync ) { - if ( dataPriv.get( el, type ) === undefined ) { - jQuery.event.add( el, type, returnTrue ); - } - return; - } - - // Register the controller as a special universal handler for all event namespaces - dataPriv.set( el, type, false ); - jQuery.event.add( el, type, { - namespace: false, - handler: function( event ) { - var notAsync, result, - saved = dataPriv.get( this, type ); - - if ( ( event.isTrigger & 1 ) && this[ type ] ) { - - // Interrupt processing of the outer synthetic .trigger()ed event - // Saved data should be false in such cases, but might be a leftover capture object - // from an async native handler (gh-4350) - if ( !saved.length ) { - - // Store arguments for use when handling the inner native event - // There will always be at least one argument (an event object), so this array - // will not be confused with a leftover capture object. - saved = slice.call( arguments ); - dataPriv.set( this, type, saved ); - - // Trigger the native event and capture its result - // Support: IE <=9 - 11+ - // focus() and blur() are asynchronous - notAsync = expectSync( this, type ); - this[ type ](); - result = dataPriv.get( this, type ); - if ( saved !== result || notAsync ) { - dataPriv.set( this, type, false ); - } else { - result = {}; - } - if ( saved !== result ) { - - // Cancel the outer synthetic event - event.stopImmediatePropagation(); - event.preventDefault(); - return result.value; - } - - // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering the - // native event and prevent that from happening again here. - // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the - // bubbling surrogate propagates *after* the non-bubbling base), but that seems - // less bad than duplication. - } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { - event.stopPropagation(); - } - - // If this is a native event triggered above, everything is now in order - // Fire an inner synthetic event with the original arguments - } else if ( saved.length ) { - - // ...and capture the result - dataPriv.set( this, type, { - value: jQuery.event.trigger( - - // Support: IE <=9 - 11+ - // Extend with the prototype to reset the above stopImmediatePropagation() - jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), - saved.slice( 1 ), - this - ) - } ); - - // Abort handling of the native event - event.stopImmediatePropagation(); - } - } - } ); -} - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - code: true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { - jQuery.event.special[ type ] = { - - // Utilize native event if possible so blur/focus sequence is correct - setup: function() { - - // Claim the first handler - // dataPriv.set( this, "focus", ... ) - // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, expectSync ); - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function() { - - // Force setup before trigger - leverageNative( this, type ); - - // Return non-false to allow normal event-path propagation - return true; - }, - - delegateType: delegateType - }; -} ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.get( src ); - events = pdataOld.events; - - if ( events ) { - dataPriv.remove( dest, "handle events" ); - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = flat( args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce || node.getAttribute( "nonce" ) - }, doc ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && isAttached( node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html; - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = isAttached( elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -var swap = function( elem, options, callback ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - // Support: Chrome <=64 - // Don't get tricked when zoom affects offsetWidth (gh-4029) - div.style.position = "absolute"; - scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableTrDimensionsVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - }, - - // Support: IE 9 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Behavior in IE 9 is more subtle than in newer versions & it passes - // some versions of this test; make sure not to make it pass there! - reliableTrDimensions: function() { - var table, tr, trChild, trStyle; - if ( reliableTrDimensionsVal == null ) { - table = document.createElement( "table" ); - tr = document.createElement( "tr" ); - trChild = document.createElement( "div" ); - - table.style.cssText = "position:absolute;left:-11111px"; - tr.style.height = "1px"; - trChild.style.height = "9px"; - - documentElement - .appendChild( table ) - .appendChild( tr ) - .appendChild( trChild ); - - trStyle = window.getComputedStyle( tr ); - reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; - - documentElement.removeChild( table ); - } - return reliableTrDimensionsVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !isAttached( elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style, - vendorProps = {}; - -// Return a vendor-prefixed property or undefined -function vendorPropName( name ) { - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a potentially-mapped jQuery.cssProps or vendor prefixed property -function finalPropName( name ) { - var final = jQuery.cssProps[ name ] || vendorProps[ name ]; - - if ( final ) { - return final; - } - if ( name in emptyStyle ) { - return name; - } - return vendorProps[ name ] = vendorPropName( name ) || name; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }; - -function setPositiveNumber( _elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - - // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter - // Use an explicit zero to avoid NaN (gh-3964) - ) ) || 0; - } - - return delta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - var styles = getStyles( elem ), - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). - // Fake content-box until we know it's needed to know the true value. - boxSizingNeeded = !support.boxSizingReliable() || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox, - - val = curCSS( elem, dimension, styles ), - offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - - // Support: IE 9 - 11 only - // Use offsetWidth/offsetHeight for when box sizing is unreliable. - // In those cases, the computed value can be trusted to be border-box. - if ( ( !support.boxSizingReliable() && isBorderBox || - - // Support: IE 10 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Interestingly, in some cases IE 9 doesn't suffer from this issue. - !support.reliableTrDimensions() && nodeName( elem, "tr" ) || - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - val === "auto" || - - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - - // Make sure the element is visible & connected - elem.getClientRects().length ) { - - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Where available, offsetWidth/offsetHeight approximate border box dimensions. - // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the - // retrieved value as a content box dimension. - valueIsBorderBox = offsetProp in elem; - if ( valueIsBorderBox ) { - val = elem[ offsetProp ]; - } - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "gridArea": true, - "gridColumn": true, - "gridColumnEnd": true, - "gridColumnStart": true, - "gridRow": true, - "gridRowEnd": true, - "gridRowStart": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append - // "px" to a few hardcoded values. - if ( type === "number" && !isCustomProp ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( _i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = getStyles( elem ), - - // Only read styles.position if the test has a chance to fail - // to avoid forcing a reflow. - scrollboxSizeBuggy = !support.scrollboxSize() && - styles.position === "absolute", - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) - boxSizingNeeded = scrollboxSizeBuggy || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra ? - boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ) : - 0; - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && scrollboxSizeBuggy ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && ( - jQuery.cssHooks[ tween.prop ] || - tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( isValidValue ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -support.focusin = "onfocusin" in window; - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( - dataPriv.get( cur, "events" ) || Object.create( null ) - )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = { guid: Date.now() }; - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - if ( a == null ) { - return ""; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( _i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() + " " ] = - ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) - .concat( match[ 2 ] ); - } - } - match = responseHeaders[ key.toLowerCase() + " " ]; - } - return match == null ? null : match.join( ", " ); - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + - uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Use a noop converter for missing script - if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { - s.converters[ "text script" ] = function() {}; - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( _i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - -jQuery.ajaxPrefilter( function( s ) { - var i; - for ( i in s.headers ) { - if ( i.toLowerCase() === "content-type" ) { - s.contentType = s.headers[ i ] || ""; - } - } -} ); - - -jQuery._evalUrl = function( url, options, doc ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - - // Only evaluate the response if it is successful (gh-4126) - // dataFilter is not invoked for failure responses, so using it instead - // of the default converter is kludgy but it works. - converters: { - "text script": function() {} - }, - dataFilter: function( response ) { - jQuery.globalEval( response, options, doc ); - } - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain or forced-by-attrs requests - if ( s.crossDomain || s.scriptAttrs ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - - -
-
- - - -
-
-
- - - -
  • -

    Eli Miller. Author, maintainer. -

    -
  • -
  • -

    Vignesh Thanikachalam. Author. -

    -
  • -
  • -

    Ben Straub. Author. -

    -
  • -
  • -

    Ross Didenko. Author. -

    -
  • -
  • -

    Atorus/GSK JPT. Copyright holder. -

    -
  • -
-
-
-

Citation

- Source: DESCRIPTION -
-
- - -

Miller E, Thanikachalam V, Straub B, Didenko R (2022). -xportr: Utilities to Output CDISC SDTM/ADaM XPT Files. -R package version 0.1.0, https://github.com/atorus-research/xportr. -

-
@Manual{,
-  title = {xportr: Utilities to Output CDISC SDTM/ADaM XPT Files},
-  author = {Eli Miller and Vignesh  Thanikachalam and Ben Straub and Ross Didenko},
-  year = {2022},
-  note = {R package version 0.1.0},
-  url = {https://github.com/atorus-research/xportr},
-}
- -
- -
- - - -
- -
-

Site built with pkgdown 2.0.3.

-
- -
- - - - - - - - diff --git a/docs/bootstrap-toc.css b/docs/bootstrap-toc.css deleted file mode 100644 index 5a859415..00000000 --- a/docs/bootstrap-toc.css +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) - * Copyright 2015 Aidan Feldman - * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ - -/* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ - -/* All levels of nav */ -nav[data-toggle='toc'] .nav > li > a { - display: block; - padding: 4px 20px; - font-size: 13px; - font-weight: 500; - color: #767676; -} -nav[data-toggle='toc'] .nav > li > a:hover, -nav[data-toggle='toc'] .nav > li > a:focus { - padding-left: 19px; - color: #563d7c; - text-decoration: none; - background-color: transparent; - border-left: 1px solid #563d7c; -} -nav[data-toggle='toc'] .nav > .active > a, -nav[data-toggle='toc'] .nav > .active:hover > a, -nav[data-toggle='toc'] .nav > .active:focus > a { - padding-left: 18px; - font-weight: bold; - color: #563d7c; - background-color: transparent; - border-left: 2px solid #563d7c; -} - -/* Nav: second level (shown on .active) */ -nav[data-toggle='toc'] .nav .nav { - display: none; /* Hide by default, but at >768px, show it */ - padding-bottom: 10px; -} -nav[data-toggle='toc'] .nav .nav > li > a { - padding-top: 1px; - padding-bottom: 1px; - padding-left: 30px; - font-size: 12px; - font-weight: normal; -} -nav[data-toggle='toc'] .nav .nav > li > a:hover, -nav[data-toggle='toc'] .nav .nav > li > a:focus { - padding-left: 29px; -} -nav[data-toggle='toc'] .nav .nav > .active > a, -nav[data-toggle='toc'] .nav .nav > .active:hover > a, -nav[data-toggle='toc'] .nav .nav > .active:focus > a { - padding-left: 28px; - font-weight: 500; -} - -/* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ -nav[data-toggle='toc'] .nav > .active > ul { - display: block; -} diff --git a/docs/bootstrap-toc.js b/docs/bootstrap-toc.js deleted file mode 100644 index 1cdd573b..00000000 --- a/docs/bootstrap-toc.js +++ /dev/null @@ -1,159 +0,0 @@ -/*! - * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) - * Copyright 2015 Aidan Feldman - * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ -(function() { - 'use strict'; - - window.Toc = { - helpers: { - // return all matching elements in the set, or their descendants - findOrFilter: function($el, selector) { - // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ - // http://stackoverflow.com/a/12731439/358804 - var $descendants = $el.find(selector); - return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])'); - }, - - generateUniqueIdBase: function(el) { - var text = $(el).text(); - var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-'); - return anchor || el.tagName.toLowerCase(); - }, - - generateUniqueId: function(el) { - var anchorBase = this.generateUniqueIdBase(el); - for (var i = 0; ; i++) { - var anchor = anchorBase; - if (i > 0) { - // add suffix - anchor += '-' + i; - } - // check if ID already exists - if (!document.getElementById(anchor)) { - return anchor; - } - } - }, - - generateAnchor: function(el) { - if (el.id) { - return el.id; - } else { - var anchor = this.generateUniqueId(el); - el.id = anchor; - return anchor; - } - }, - - createNavList: function() { - return $(''); - }, - - createChildNavList: function($parent) { - var $childList = this.createNavList(); - $parent.append($childList); - return $childList; - }, - - generateNavEl: function(anchor, text) { - var $a = $(''); - $a.attr('href', '#' + anchor); - $a.text(text); - var $li = $('
  • '); - $li.append($a); - return $li; - }, - - generateNavItem: function(headingEl) { - var anchor = this.generateAnchor(headingEl); - var $heading = $(headingEl); - var text = $heading.data('toc-text') || $heading.text(); - return this.generateNavEl(anchor, text); - }, - - // Find the first heading level (`

    `, then `

    `, etc.) that has more than one element. Defaults to 1 (for `

    `). - getTopLevel: function($scope) { - for (var i = 1; i <= 6; i++) { - var $headings = this.findOrFilter($scope, 'h' + i); - if ($headings.length > 1) { - return i; - } - } - - return 1; - }, - - // returns the elements for the top level, and the next below it - getHeadings: function($scope, topLevel) { - var topSelector = 'h' + topLevel; - - var secondaryLevel = topLevel + 1; - var secondarySelector = 'h' + secondaryLevel; - - return this.findOrFilter($scope, topSelector + ',' + secondarySelector); - }, - - getNavLevel: function(el) { - return parseInt(el.tagName.charAt(1), 10); - }, - - populateNav: function($topContext, topLevel, $headings) { - var $context = $topContext; - var $prevNav; - - var helpers = this; - $headings.each(function(i, el) { - var $newNav = helpers.generateNavItem(el); - var navLevel = helpers.getNavLevel(el); - - // determine the proper $context - if (navLevel === topLevel) { - // use top level - $context = $topContext; - } else if ($prevNav && $context === $topContext) { - // create a new level of the tree and switch to it - $context = helpers.createChildNavList($prevNav); - } // else use the current $context - - $context.append($newNav); - - $prevNav = $newNav; - }); - }, - - parseOps: function(arg) { - var opts; - if (arg.jquery) { - opts = { - $nav: arg - }; - } else { - opts = arg; - } - opts.$scope = opts.$scope || $(document.body); - return opts; - } - }, - - // accepts a jQuery object, or an options object - init: function(opts) { - opts = this.helpers.parseOps(opts); - - // ensure that the data attribute is in place for styling - opts.$nav.attr('data-toggle', 'toc'); - - var $topContext = this.helpers.createChildNavList(opts.$nav); - var topLevel = this.helpers.getTopLevel(opts.$scope); - var $headings = this.helpers.getHeadings(opts.$scope, topLevel); - this.helpers.populateNav($topContext, topLevel, $headings); - } - }; - - $(function() { - $('nav[data-toggle="toc"]').each(function(i, el) { - var $nav = $(el); - Toc.init($nav); - }); - }); -})(); diff --git a/docs/docsearch.css b/docs/docsearch.css deleted file mode 100644 index e5f1fe1d..00000000 --- a/docs/docsearch.css +++ /dev/null @@ -1,148 +0,0 @@ -/* Docsearch -------------------------------------------------------------- */ -/* - Source: https://github.com/algolia/docsearch/ - License: MIT -*/ - -.algolia-autocomplete { - display: block; - -webkit-box-flex: 1; - -ms-flex: 1; - flex: 1 -} - -.algolia-autocomplete .ds-dropdown-menu { - width: 100%; - min-width: none; - max-width: none; - padding: .75rem 0; - background-color: #fff; - background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, .1); - box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .175); -} - -@media (min-width:768px) { - .algolia-autocomplete .ds-dropdown-menu { - width: 175% - } -} - -.algolia-autocomplete .ds-dropdown-menu::before { - display: none -} - -.algolia-autocomplete .ds-dropdown-menu [class^=ds-dataset-] { - padding: 0; - background-color: rgb(255,255,255); - border: 0; - max-height: 80vh; -} - -.algolia-autocomplete .ds-dropdown-menu .ds-suggestions { - margin-top: 0 -} - -.algolia-autocomplete .algolia-docsearch-suggestion { - padding: 0; - overflow: visible -} - -.algolia-autocomplete .algolia-docsearch-suggestion--category-header { - padding: .125rem 1rem; - margin-top: 0; - font-size: 1.3em; - font-weight: 500; - color: #00008B; - border-bottom: 0 -} - -.algolia-autocomplete .algolia-docsearch-suggestion--wrapper { - float: none; - padding-top: 0 -} - -.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { - float: none; - width: auto; - padding: 0; - text-align: left -} - -.algolia-autocomplete .algolia-docsearch-suggestion--content { - float: none; - width: auto; - padding: 0 -} - -.algolia-autocomplete .algolia-docsearch-suggestion--content::before { - display: none -} - -.algolia-autocomplete .ds-suggestion:not(:first-child) .algolia-docsearch-suggestion--category-header { - padding-top: .75rem; - margin-top: .75rem; - border-top: 1px solid rgba(0, 0, 0, .1) -} - -.algolia-autocomplete .ds-suggestion .algolia-docsearch-suggestion--subcategory-column { - display: block; - padding: .1rem 1rem; - margin-bottom: 0.1; - font-size: 1.0em; - font-weight: 400 - /* display: none */ -} - -.algolia-autocomplete .algolia-docsearch-suggestion--title { - display: block; - padding: .25rem 1rem; - margin-bottom: 0; - font-size: 0.9em; - font-weight: 400 -} - -.algolia-autocomplete .algolia-docsearch-suggestion--text { - padding: 0 1rem .5rem; - margin-top: -.25rem; - font-size: 0.8em; - font-weight: 400; - line-height: 1.25 -} - -.algolia-autocomplete .algolia-docsearch-footer { - width: 110px; - height: 20px; - z-index: 3; - margin-top: 10.66667px; - float: right; - font-size: 0; - line-height: 0; -} - -.algolia-autocomplete .algolia-docsearch-footer--logo { - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: 50%; - background-size: 100%; - overflow: hidden; - text-indent: -9000px; - width: 100%; - height: 100%; - display: block; - transform: translate(-8px); -} - -.algolia-autocomplete .algolia-docsearch-suggestion--highlight { - color: #FF8C00; - background: rgba(232, 189, 54, 0.1) -} - - -.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight { - box-shadow: inset 0 -2px 0 0 rgba(105, 105, 105, .5) -} - -.algolia-autocomplete .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content { - background-color: rgba(192, 192, 192, .15) -} diff --git a/docs/docsearch.js b/docs/docsearch.js deleted file mode 100644 index b35504cd..00000000 --- a/docs/docsearch.js +++ /dev/null @@ -1,85 +0,0 @@ -$(function() { - - // register a handler to move the focus to the search bar - // upon pressing shift + "/" (i.e. "?") - $(document).on('keydown', function(e) { - if (e.shiftKey && e.keyCode == 191) { - e.preventDefault(); - $("#search-input").focus(); - } - }); - - $(document).ready(function() { - // do keyword highlighting - /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ - var mark = function() { - - var referrer = document.URL ; - var paramKey = "q" ; - - if (referrer.indexOf("?") !== -1) { - var qs = referrer.substr(referrer.indexOf('?') + 1); - var qs_noanchor = qs.split('#')[0]; - var qsa = qs_noanchor.split('&'); - var keyword = ""; - - for (var i = 0; i < qsa.length; i++) { - var currentParam = qsa[i].split('='); - - if (currentParam.length !== 2) { - continue; - } - - if (currentParam[0] == paramKey) { - keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); - } - } - - if (keyword !== "") { - $(".contents").unmark({ - done: function() { - $(".contents").mark(keyword); - } - }); - } - } - }; - - mark(); - }); -}); - -/* Search term highlighting ------------------------------*/ - -function matchedWords(hit) { - var words = []; - - var hierarchy = hit._highlightResult.hierarchy; - // loop to fetch from lvl0, lvl1, etc. - for (var idx in hierarchy) { - words = words.concat(hierarchy[idx].matchedWords); - } - - var content = hit._highlightResult.content; - if (content) { - words = words.concat(content.matchedWords); - } - - // return unique words - var words_uniq = [...new Set(words)]; - return words_uniq; -} - -function updateHitURL(hit) { - - var words = matchedWords(hit); - var url = ""; - - if (hit.anchor) { - url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; - } else { - url = hit.url + '?q=' + escape(words.join(" ")); - } - - return url; -} diff --git a/docs/favicon-16x16.png b/docs/favicon-16x16.png deleted file mode 100644 index 5c2cb7d5..00000000 Binary files a/docs/favicon-16x16.png and /dev/null differ diff --git a/docs/favicon-32x32.png b/docs/favicon-32x32.png deleted file mode 100644 index 91d483bf..00000000 Binary files a/docs/favicon-32x32.png and /dev/null differ diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100644 index c7837eb8..00000000 Binary files a/docs/favicon.ico and /dev/null differ diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 68476df5..00000000 --- a/docs/index.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - - -Utilities to Output CDISC SDTM/ADaM XPT Files โ€ข xportr - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - -
    -
    - -
    - - - -

    Welcome to xportr! We have designed xportr to help get your xpt files ready for transport either to a clinical data set validator application or to a regulatory agency. This package has the functionality to associate metadata information to a local R data frame, perform data set level validation checks and convert into a transport v5 file(xpt).

    -

    As always, we welcome your feedback. If you spot a bug, would like to see a new feature, or if any documentation is unclear - submit an issue on xportrโ€™s GitHub page.

    -
    -

    Installation -

    -
    -

    Development version: -

    -
    -devtools::install_github("https://github.com/atorus-research/xportr.git", ref = "main")
    -
    -
    -

    CRAN -

    -
      -
    • As this is an experimental package and under development we have not made it available on CRAN.
    • -
    -
    -
    -
    -
    -

    What is xportr? -

    -


    -

    xportr is designed for clinical programmers to create CDISC compliant xpt files- ADaM or SDTM. Essentially, this package has two big components to it

    -
      -
    1. Writing xpt files with well-defined metadata
    2. -
    3. Checking compliance of the data sets.
    4. -
    -

    The first set of tools are designed to allow a clinical programmer to build a CDISC compliant xpt file directly from R. The second set of tools are to perform checks on your data sets before you send them off to any validators or data reviewers.

    -


    -

    -


    -
    -
    -

    What are the checks? -

    -


    -
      -
    • Variable names must start with a letter (not an underscore), be comprised of only uppercase letters (A-Z), numerals (0-9) and be free of non-ASCII characters, symbols, and underscores.
    • -
    • Allotted length for each column containing character (text) data should be set to the maximum length of the variable used across all data sets (โ‰ค 200)
    • -
    • Coerces variables to only numeric or character types
    • -
    • Display format support for numeric float and date/time values
    • -
    • Variables names are โ‰ค 8 characters.
    • -
    • Variable labels are โ‰ค 200 characters.
    • -
    • Data set labels are โ‰ค 40 characters.
    • -
    • Presence of non-ASCII characters in Variable Names, Labels or data set labels.
    • -
    -

    NOTE: Each check has associated messages and warning.

    -
    -

    Simple Example -

    -

    Objective: Create a fully compliant v5 xpt ADSL dataset that was developed using R.

    -

    To do this we will need to do the following:

    -
      -
    • Apply types
    • -
    • Apply lengths
      -
    • -
    • Apply variable labels
    • -
    • Apply formats
    • -
    • Re-order the variables
    • -
    • Apply a dataset label
    • -
    • Write out a version 5 xpt file
    • -
    -

    All of which can be done using a well-defined specification file and the xportr package!

    -

    First we will start with our ADSL dataset created in R. This example ADSL dataset is taken from the {admiral} package. The script that generates this ADSL dataset can be created by using this command admiral::use_ad_template("adsl"). This ADSL dataset has 306 observations and 48 variables.

    - -

    We have created a dummy specification file called ADaM_admiral_spec.xlsx found in the specs folder of this package. You can use system.file(paste0("specs/", "ADaM_admiral_spec.xlsx"), package = "xportr") to access this file.

    -
    -spec_path <- system.file(paste0("specs/", "ADaM_admiral_spec.xlsx"), package = "xportr")
    -
    -var_spec <- readxl::read_xlsx(spec_path, sheet = "Variables") %>%
    -  dplyr::rename(type = "Data Type") %>%
    -  rlang::set_names(tolower)
    -

    Each xportr_ function has been written in a way to take in a part of the specification file and apply that piece to the dataset.

    -
    -adsl %>% 
    -  xportr_type(var_spec, "ADSL") %>%
    -  xportr_length(var_spec, "ADSL") %>%
    -  xportr_label(var_spec, "ADSL") %>%
    -  xportr_order(var_spec, "ADSL") %>% 
    -  xportr_format(var_spec, "ADSL") %>% 
    -  xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset")
    -

    Thatโ€™s it! We now have a xpt file created in R with all appropriate types, lengths, labels, ordering and formats. Please check out the Get Started for more information and detailed walk through of each xportr_ function.

    -

    We are in talks with other Pharma companies involved with the {pharmaverse} to enhance this package to play well with other downstream and upstream packages.

    -
    -
    -
    -

    References -

    -


    -

    This package was developed jointly by GSK and Atorus.

    -
    - -
    - - -
    - - -
    - -
    -

    -

    Site built with pkgdown 2.0.3.

    -
    - -
    -
    - - - - - - - - diff --git a/docs/link.svg b/docs/link.svg deleted file mode 100644 index 88ad8276..00000000 --- a/docs/link.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/docs/logo.png b/docs/logo.png deleted file mode 100644 index b439544c..00000000 Binary files a/docs/logo.png and /dev/null differ diff --git a/docs/news/index.html b/docs/news/index.html deleted file mode 100644 index 689f43ec..00000000 --- a/docs/news/index.html +++ /dev/null @@ -1,91 +0,0 @@ - -Changelog โ€ข xportr - - -
    -
    - - - -
    -
    - - -
    - -

    Beta release for xportr

    -
    • Added exported functions xportr_varnames and xportr_tidy_rename into dev folder found on GitHub Repostiory. Intention to move into packages after CRAN release.
    • -
    • Fixed xportr_format() bug
    • -
    • Using admiral ADSL dataset in examples
    • -
    -
    - -

    Initial alpha release of xportr

    -
    • Development of 5 core functions
    • -
    • Package down site and documentation created
    • -
    -
    - - - -
    - - -
    - -
    -

    Site built with pkgdown 2.0.3.

    -
    - -
    - - - - - - - - diff --git a/docs/pkgdown.css b/docs/pkgdown.css deleted file mode 100644 index 80ea5b83..00000000 --- a/docs/pkgdown.css +++ /dev/null @@ -1,384 +0,0 @@ -/* Sticky footer */ - -/** - * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ - * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css - * - * .Site -> body > .container - * .Site-content -> body > .container .row - * .footer -> footer - * - * Key idea seems to be to ensure that .container and __all its parents__ - * have height set to 100% - * - */ - -html, body { - height: 100%; -} - -body { - position: relative; -} - -body > .container { - display: flex; - height: 100%; - flex-direction: column; -} - -body > .container .row { - flex: 1 0 auto; -} - -footer { - margin-top: 45px; - padding: 35px 0 36px; - border-top: 1px solid #e5e5e5; - color: #666; - display: flex; - flex-shrink: 0; -} -footer p { - margin-bottom: 0; -} -footer div { - flex: 1; -} -footer .pkgdown { - text-align: right; -} -footer p { - margin-bottom: 0; -} - -img.icon { - float: right; -} - -/* Ensure in-page images don't run outside their container */ -.contents img { - max-width: 100%; - height: auto; -} - -/* Fix bug in bootstrap (only seen in firefox) */ -summary { - display: list-item; -} - -/* Typographic tweaking ---------------------------------*/ - -.contents .page-header { - margin-top: calc(-60px + 1em); -} - -dd { - margin-left: 3em; -} - -/* Section anchors ---------------------------------*/ - -a.anchor { - display: none; - margin-left: 5px; - width: 20px; - height: 20px; - - background-image: url(./link.svg); - background-repeat: no-repeat; - background-size: 20px 20px; - background-position: center center; -} - -h1:hover .anchor, -h2:hover .anchor, -h3:hover .anchor, -h4:hover .anchor, -h5:hover .anchor, -h6:hover .anchor { - display: inline-block; -} - -/* Fixes for fixed navbar --------------------------*/ - -.contents h1, .contents h2, .contents h3, .contents h4 { - padding-top: 60px; - margin-top: -40px; -} - -/* Navbar submenu --------------------------*/ - -.dropdown-submenu { - position: relative; -} - -.dropdown-submenu>.dropdown-menu { - top: 0; - left: 100%; - margin-top: -6px; - margin-left: -1px; - border-radius: 0 6px 6px 6px; -} - -.dropdown-submenu:hover>.dropdown-menu { - display: block; -} - -.dropdown-submenu>a:after { - display: block; - content: " "; - float: right; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - border-width: 5px 0 5px 5px; - border-left-color: #cccccc; - margin-top: 5px; - margin-right: -10px; -} - -.dropdown-submenu:hover>a:after { - border-left-color: #ffffff; -} - -.dropdown-submenu.pull-left { - float: none; -} - -.dropdown-submenu.pull-left>.dropdown-menu { - left: -100%; - margin-left: 10px; - border-radius: 6px 0 6px 6px; -} - -/* Sidebar --------------------------*/ - -#pkgdown-sidebar { - margin-top: 30px; - position: -webkit-sticky; - position: sticky; - top: 70px; -} - -#pkgdown-sidebar h2 { - font-size: 1.5em; - margin-top: 1em; -} - -#pkgdown-sidebar h2:first-child { - margin-top: 0; -} - -#pkgdown-sidebar .list-unstyled li { - margin-bottom: 0.5em; -} - -/* bootstrap-toc tweaks ------------------------------------------------------*/ - -/* All levels of nav */ - -nav[data-toggle='toc'] .nav > li > a { - padding: 4px 20px 4px 6px; - font-size: 1.5rem; - font-weight: 400; - color: inherit; -} - -nav[data-toggle='toc'] .nav > li > a:hover, -nav[data-toggle='toc'] .nav > li > a:focus { - padding-left: 5px; - color: inherit; - border-left: 1px solid #878787; -} - -nav[data-toggle='toc'] .nav > .active > a, -nav[data-toggle='toc'] .nav > .active:hover > a, -nav[data-toggle='toc'] .nav > .active:focus > a { - padding-left: 5px; - font-size: 1.5rem; - font-weight: 400; - color: inherit; - border-left: 2px solid #878787; -} - -/* Nav: second level (shown on .active) */ - -nav[data-toggle='toc'] .nav .nav { - display: none; /* Hide by default, but at >768px, show it */ - padding-bottom: 10px; -} - -nav[data-toggle='toc'] .nav .nav > li > a { - padding-left: 16px; - font-size: 1.35rem; -} - -nav[data-toggle='toc'] .nav .nav > li > a:hover, -nav[data-toggle='toc'] .nav .nav > li > a:focus { - padding-left: 15px; -} - -nav[data-toggle='toc'] .nav .nav > .active > a, -nav[data-toggle='toc'] .nav .nav > .active:hover > a, -nav[data-toggle='toc'] .nav .nav > .active:focus > a { - padding-left: 15px; - font-weight: 500; - font-size: 1.35rem; -} - -/* orcid ------------------------------------------------------------------- */ - -.orcid { - font-size: 16px; - color: #A6CE39; - /* margins are required by official ORCID trademark and display guidelines */ - margin-left:4px; - margin-right:4px; - vertical-align: middle; -} - -/* Reference index & topics ----------------------------------------------- */ - -.ref-index th {font-weight: normal;} - -.ref-index td {vertical-align: top; min-width: 100px} -.ref-index .icon {width: 40px;} -.ref-index .alias {width: 40%;} -.ref-index-icons .alias {width: calc(40% - 40px);} -.ref-index .title {width: 60%;} - -.ref-arguments th {text-align: right; padding-right: 10px;} -.ref-arguments th, .ref-arguments td {vertical-align: top; min-width: 100px} -.ref-arguments .name {width: 20%;} -.ref-arguments .desc {width: 80%;} - -/* Nice scrolling for wide elements --------------------------------------- */ - -table { - display: block; - overflow: auto; -} - -/* Syntax highlighting ---------------------------------------------------- */ - -pre, code, pre code { - background-color: #f8f8f8; - color: #333; -} -pre, pre code { - white-space: pre-wrap; - word-break: break-all; - overflow-wrap: break-word; -} - -pre { - border: 1px solid #eee; -} - -pre .img, pre .r-plt { - margin: 5px 0; -} - -pre .img img, pre .r-plt img { - background-color: #fff; -} - -code a, pre a { - color: #375f84; -} - -a.sourceLine:hover { - text-decoration: none; -} - -.fl {color: #1514b5;} -.fu {color: #000000;} /* function */ -.ch,.st {color: #036a07;} /* string */ -.kw {color: #264D66;} /* keyword */ -.co {color: #888888;} /* comment */ - -.error {font-weight: bolder;} -.warning {font-weight: bolder;} - -/* Clipboard --------------------------*/ - -.hasCopyButton { - position: relative; -} - -.btn-copy-ex { - position: absolute; - right: 0; - top: 0; - visibility: hidden; -} - -.hasCopyButton:hover button.btn-copy-ex { - visibility: visible; -} - -/* headroom.js ------------------------ */ - -.headroom { - will-change: transform; - transition: transform 200ms linear; -} -.headroom--pinned { - transform: translateY(0%); -} -.headroom--unpinned { - transform: translateY(-100%); -} - -/* mark.js ----------------------------*/ - -mark { - background-color: rgba(255, 255, 51, 0.5); - border-bottom: 2px solid rgba(255, 153, 51, 0.3); - padding: 1px; -} - -/* vertical spacing after htmlwidgets */ -.html-widget { - margin-bottom: 10px; -} - -/* fontawesome ------------------------ */ - -.fab { - font-family: "Font Awesome 5 Brands" !important; -} - -/* don't display links in code chunks when printing */ -/* source: https://stackoverflow.com/a/10781533 */ -@media print { - code a:link:after, code a:visited:after { - content: ""; - } -} - -/* Section anchors --------------------------------- - Added in pandoc 2.11: https://github.com/jgm/pandoc-templates/commit/9904bf71 -*/ - -div.csl-bib-body { } -div.csl-entry { - clear: both; -} -.hanging-indent div.csl-entry { - margin-left:2em; - text-indent:-2em; -} -div.csl-left-margin { - min-width:2em; - float:left; -} -div.csl-right-inline { - margin-left:2em; - padding-left:1em; -} -div.csl-indent { - margin-left: 2em; -} diff --git a/docs/pkgdown.js b/docs/pkgdown.js deleted file mode 100644 index 6f0eee40..00000000 --- a/docs/pkgdown.js +++ /dev/null @@ -1,108 +0,0 @@ -/* http://gregfranko.com/blog/jquery-best-practices/ */ -(function($) { - $(function() { - - $('.navbar-fixed-top').headroom(); - - $('body').css('padding-top', $('.navbar').height() + 10); - $(window).resize(function(){ - $('body').css('padding-top', $('.navbar').height() + 10); - }); - - $('[data-toggle="tooltip"]').tooltip(); - - var cur_path = paths(location.pathname); - var links = $("#navbar ul li a"); - var max_length = -1; - var pos = -1; - for (var i = 0; i < links.length; i++) { - if (links[i].getAttribute("href") === "#") - continue; - // Ignore external links - if (links[i].host !== location.host) - continue; - - var nav_path = paths(links[i].pathname); - - var length = prefix_length(nav_path, cur_path); - if (length > max_length) { - max_length = length; - pos = i; - } - } - - // Add class to parent
  • , and enclosing
  • if in dropdown - if (pos >= 0) { - var menu_anchor = $(links[pos]); - menu_anchor.parent().addClass("active"); - menu_anchor.closest("li.dropdown").addClass("active"); - } - }); - - function paths(pathname) { - var pieces = pathname.split("/"); - pieces.shift(); // always starts with / - - var end = pieces[pieces.length - 1]; - if (end === "index.html" || end === "") - pieces.pop(); - return(pieces); - } - - // Returns -1 if not found - function prefix_length(needle, haystack) { - if (needle.length > haystack.length) - return(-1); - - // Special case for length-0 haystack, since for loop won't run - if (haystack.length === 0) { - return(needle.length === 0 ? 0 : -1); - } - - for (var i = 0; i < haystack.length; i++) { - if (needle[i] != haystack[i]) - return(i); - } - - return(haystack.length); - } - - /* Clipboard --------------------------*/ - - function changeTooltipMessage(element, msg) { - var tooltipOriginalTitle=element.getAttribute('data-original-title'); - element.setAttribute('data-original-title', msg); - $(element).tooltip('show'); - element.setAttribute('data-original-title', tooltipOriginalTitle); - } - - if(ClipboardJS.isSupported()) { - $(document).ready(function() { - var copyButton = ""; - - $("div.sourceCode").addClass("hasCopyButton"); - - // Insert copy buttons: - $(copyButton).prependTo(".hasCopyButton"); - - // Initialize tooltips: - $('.btn-copy-ex').tooltip({container: 'body'}); - - // Initialize clipboard: - var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { - text: function(trigger) { - return trigger.parentNode.textContent.replace(/\n#>[^\n]*/g, ""); - } - }); - - clipboardBtnCopies.on('success', function(e) { - changeTooltipMessage(e.trigger, 'Copied!'); - e.clearSelection(); - }); - - clipboardBtnCopies.on('error', function() { - changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); - }); - }); - } -})(window.jQuery || window.$) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml deleted file mode 100644 index d38c0354..00000000 --- a/docs/pkgdown.yml +++ /dev/null @@ -1,7 +0,0 @@ -pandoc: 2.17.1.1 -pkgdown: 2.0.3 -pkgdown_sha: ~ -articles: - xportr: xportr.html -last_built: 2022-06-17T13:28Z - diff --git a/docs/reference/Rplot001.png b/docs/reference/Rplot001.png deleted file mode 100644 index 17a35806..00000000 Binary files a/docs/reference/Rplot001.png and /dev/null differ diff --git a/docs/reference/df_label_view.html b/docs/reference/df_label_view.html deleted file mode 100644 index 6fa010d4..00000000 --- a/docs/reference/df_label_view.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - -View Label on a dataframe โ€” df_label_view โ€ข xportr - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - -
    - -
    -
    - - -
    -

    View Label on a dataframe

    -
    - -
    df_label_view(.data)
    - -

    Arguments

    - - - - - - -
    .data
    - -

    Value

    - -

    Message with the Label or "No Label on dataframe"

    - -
    - -
    - - -
    - - -
    -

    Site built with pkgdown 1.6.1.

    -
    - -
    -
    - - - - - - - - diff --git a/docs/reference/figures/_logo.png b/docs/reference/figures/_logo.png deleted file mode 100644 index b439544c..00000000 Binary files a/docs/reference/figures/_logo.png and /dev/null differ diff --git a/docs/reference/figures/design_flow.png b/docs/reference/figures/design_flow.png deleted file mode 100644 index beef0e52..00000000 Binary files a/docs/reference/figures/design_flow.png and /dev/null differ diff --git a/docs/reference/figures/fa-icon-8fddca93e3a89fee152146540f9de78a.svg b/docs/reference/figures/fa-icon-8fddca93e3a89fee152146540f9de78a.svg deleted file mode 100644 index 291427c9..00000000 --- a/docs/reference/figures/fa-icon-8fddca93e3a89fee152146540f9de78a.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/docs/reference/figures/fa-icon-9b00320707d42527dde67262afb33ded.svg b/docs/reference/figures/fa-icon-9b00320707d42527dde67262afb33ded.svg deleted file mode 100644 index 4c9b3ff4..00000000 --- a/docs/reference/figures/fa-icon-9b00320707d42527dde67262afb33ded.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/docs/reference/figures/fa-icon-fb6dbd8383fef2a17a1d7c77a9b4b828.svg b/docs/reference/figures/fa-icon-fb6dbd8383fef2a17a1d7c77a9b4b828.svg deleted file mode 100644 index 906893ea..00000000 --- a/docs/reference/figures/fa-icon-fb6dbd8383fef2a17a1d7c77a9b4b828.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/docs/reference/figures/logo.png b/docs/reference/figures/logo.png deleted file mode 100644 index b439544c..00000000 Binary files a/docs/reference/figures/logo.png and /dev/null differ diff --git a/docs/reference/figures/pressure-1.png b/docs/reference/figures/pressure-1.png deleted file mode 100644 index 7507aa93..00000000 Binary files a/docs/reference/figures/pressure-1.png and /dev/null differ diff --git a/docs/reference/figures/xportr_rev.png b/docs/reference/figures/xportr_rev.png deleted file mode 100644 index abaf6ca1..00000000 Binary files a/docs/reference/figures/xportr_rev.png and /dev/null differ diff --git a/docs/reference/index.html b/docs/reference/index.html deleted file mode 100644 index e0ff536b..00000000 --- a/docs/reference/index.html +++ /dev/null @@ -1,144 +0,0 @@ - -Function reference โ€ข xportr - - -
    -
    - - - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -

    The six core xportr functions

    -

    -
    -

    xportr_type()

    -

    Coerce variable type

    -

    xportr_length()

    -

    Assign SAS Length

    -

    xportr_label()

    -

    Assign Variable Label

    -

    xportr_write()

    -

    Write xpt v5 transport file

    -

    xportr_format()

    -

    Assign SAS Format

    -

    xportr_order()

    -

    Order variables of a dataset according to Spec

    -

    xportr helper functions

    -

    -
    -

    label_log()

    -

    Utility for Variable Labels

    -

    length_log()

    -

    Utility for Lengths

    -

    type_log()

    -

    Utility for Types

    -

    var_names_log()

    -

    Utility for Renaming Variables

    -

    var_ord_msg()

    -

    Utility for Ordering

    -

    xportr_logger()

    -

    Utility Logging Function

    -

    xportr_df_label()

    -

    Assign Dataset Label

    -

    xportr

    -

    -
    -

    xportr-package

    -

    The xportr package

    - - -
    - - -
    - -
    -

    Site built with pkgdown 2.0.3.

    -
    - -
    - - - - - - - - diff --git a/docs/reference/pipe.html b/docs/reference/pipe.html deleted file mode 100644 index 2aa9c355..00000000 --- a/docs/reference/pipe.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - -Pipe operator โ€” %>% โ€ข xportr - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - -
    - -
    -
    - - -
    -

    See magrittr::%>% for details.

    -
    - -
    lhs %>% rhs
    - - - -
    - -
    - - -
    - - -
    -

    Site built with pkgdown 1.6.1.

    -
    - -
    -
    - - - - - - - - diff --git a/docs/reference/xportr-package.html b/docs/reference/xportr-package.html deleted file mode 100644 index 171d8460..00000000 --- a/docs/reference/xportr-package.html +++ /dev/null @@ -1,97 +0,0 @@ - -The xportr package โ€” xportr-package โ€ข xportr - - -
    -
    - - - -
    -
    - - -
    -

    Package Info here

    -
    - - - -
    -

    Author

    -

    Maintainer: Eli Miller Eli.Miller@AtorusResearch.com (ORCID)

    -

    Authors:

    Other contributors:

    • Atorus/GSK JPT [copyright holder]

    • -
    - -
    - -
    - - -
    - -
    -

    Site built with pkgdown 2.0.3.

    -
    - -
    - - - - - - - - diff --git a/docs/reference/xportr_core.html b/docs/reference/xportr_core.html deleted file mode 100644 index bd37c738..00000000 --- a/docs/reference/xportr_core.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - - - -Check variables according to their 'Core' category value. โ€” xportr_core โ€ข xportr - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - -
    - -
    -
    - - -
    -

    Do checks on CDISC compliance for each variable of the dataset, depending on its 'Core' category value. Values of -'Core' category includes: Required, Expected, Permissible and Conditionally Required. Refer to https://www.cdisc.org/ -for more details. If checks passed - will run silent, otherwise - throw errors or warnings.

    -
    - -
    xportr_core(
    -  .df,
    -  datadef,
    -  ds_name. = "",
    -  var_categ. = c("req", "exp", "perm", "cond")
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - -
    .df

    Path do dataset, when var check is required.

    datadef

    A table-like object, containing actual SDTM or ADaM specification information.

    ds_name.

    Optional; by default takes name of the dataset without extension; useful when dataset filename -differs from actual name of dataset (i.e. if ADAE file is named 'adae_final.xpt' etc.)

    var_categ.

    Vector with names of 'Core' vategories to check; default: c("req", "exp", "perm", "cond").

    - -

    Value

    - -

    Nothing

    - -

    Examples

    -
    # Consider having specifications or spec metadata in place ("ADaM_datadefxlsx"). -# Let ADAE be dataset we want to check. - -#d <- load_spec("ADaM_datadefxlsx") %>% - #xportr_core("adae.xpt") - -# If filename is different for any reason (like dataset had to be split to -# meet size expectations). - -#d <- load_spec("analysis_metadata.xlsx") %>% - #xportr_core("mo1.xpt", ds_name = "MO") - -#d <- load_spec("tests/testthat/files/ADaM_datadefxlsx") %>% - #xportr_core("adae.xpt", ds_name = "ADAE") - -# Using 'datadef' tools: -#dd <- define_to_DataDef(path_to_xml_file) -#xportr_core(dd$ds_spec, "adae.xpt") - -
    -
    - -
    - - -
    - - -
    -

    Site built with pkgdown 1.6.1.

    -
    - -
    -
    - - - - - - - - diff --git a/docs/reference/xportr_df_label.html b/docs/reference/xportr_df_label.html deleted file mode 100644 index 452540a9..00000000 --- a/docs/reference/xportr_df_label.html +++ /dev/null @@ -1,125 +0,0 @@ - -Assign Dataset Label โ€” xportr_df_label โ€ข xportr - - -
    -
    - - - -
    -
    - - -
    -

    Assigns dataset label from a dataset level metadata to a given data frame.

    -
    - -
    -
    xportr_df_label(.df, metacore, domain = NULL)
    -
    - -
    -

    Arguments

    -
    .df
    -

    A data frame of CDISC standard.

    -
    metacore
    -

    A data frame containing dataset level metadata.

    -
    domain
    -

    A character value to subset the .df. If NULL(default), uses -.df value as a subset condition.

    -
    -
    -

    Value

    -

    Data frame with label attributes.

    -
    -
    -

    See also

    - -
    - -
    -

    Examples

    -
    adsl <- data.frame(
    -  USUBJID = c(1001, 1002, 1003),
    -  SITEID = c(001, 002, 003),
    -  AGE = c(63, 35, 27),
    -  SEX = c("M", "F", "M")
    -)
    -
    -metacore <- data.frame(
    -  dataset = c("adsl", "adae"),
    -  label = c("Subject-Level Analysis", "Adverse Events Analysis")
    -)
    -
    -adsl <- xportr_df_label(adsl, metacore)
    -
    -
    -
    - -
    - - -
    - -
    -

    Site built with pkgdown 2.0.3.

    -
    - -
    - - - - - - - - diff --git a/docs/reference/xportr_df_varnames.html b/docs/reference/xportr_df_varnames.html deleted file mode 100644 index 46eb9c4f..00000000 --- a/docs/reference/xportr_df_varnames.html +++ /dev/null @@ -1,392 +0,0 @@ - - - - - - - - -Rename terms for submission compliance from data โ€” xportr_df_varnames โ€ข xportr - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - -
    - -
    -
    - - -
    -

    Change the variable names in the .df (only) and return that object to the -user. This function simply calls xportr_tidy_names on -colnames(.df)

    -
    - -
    xportr_df_varnames(
    -  .df,
    -  verbose = getOption("xportr.type_verbose", "none"),
    -  relo_2_end = TRUE,
    -  letter_for_num_prefix = "x",
    -  sep = "",
    -  replace_vec = c(`'` = "", `"` = "", `%` = "_pct_", `#` = "_nmbr_"),
    -  dict_dat = data.frame(original_varname = character(), dict_varname = character())
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    .df

    An R object with columns that can be coerced

    verbose

    The action the function takes when a variable isn't typed -properly. Options are 'stop', 'warn', 'message', and 'none'

    relo_2_end

    logical, if TRUE: numerical prefix bundles will be -relocated to the end of the string. A prefix bundle is determined as a -number or "grouping of numbers separated by special characters and other -punctuation". See details section for more info on prefix bundles.

    letter_for_num_prefix

    character. Will be ignored if relo_2_end = TRUE. Per CDISC & regulatory body requirements, variable names cannot -start with a number, so if you want to leave the number of the left-hand -side, use this argument to insert a starting letter before the numeric -prefix.

    sep

    string of only one character long, intended to separate words from -one another. In general, only "" and "" are possible, but the user can -insert a letter if desired. Note that "" is discouraged because it is only -permissible for legacy studies started on or before Dec 17, 2016.

    replace_vec

    A named character vector where the name is replaced by the -value.

    dict_dat

    a data frame containing two variables: the original_varname -and the dict_varname to find and replace

    - -

    Value

    - -

    Returns the modified table.

    -

    Details

    - -

    Abbreviating variable names in the xportr pkg uses a step-by-step -process detailed below. Each original variable name will be renamed based -on the method performing the least amount of work to achieve the min -character length requirement (8 chars), thus maintaining maximum -originality. Therefore, the function only moves on to the next method on an -as-needed basis; namely, when the term is still greater than 8 characters, -even after lite modification.

    -

    (1) Blanks: Any columns that are missing a variable name (i.e., the header -was blank in the source file) will be renamed to 'V' + the column position. -So if the 2nd column is blank, it will receive a 'V2' rename.

    -

    (2) Use dictionary of controlled terminology: For example: 'Subject ID' may -better be suited as 'SUBJID' within your organization. Note, that -dictionary terms are expected to be submission compliant and will not be -further abbreviated. They will, however, undergo a check for non-compliance.

    -

    (3) Do nothing! Or at the very least, mimic what SAS does automatically -when cleaning up variable names during a PROC IMPORT. Namely, replace any -special characters with underscores (''), capitalize everything, and if -the value starts with a digit, add the '' prefix. If the 'SASified' name -is <= 8 chars, then the function will use that rename. However, if its -still too long, the function will try removing any extra special characters -or spaces to help reduce to 8 chars.

    -

    (4) Find the STEM or ROOT word of each original variable name. For example, -if the original contains the word 'resting', the 'ing' will be dropped and -only the root word 'rest' will be considered. If less than 8 chars, the -algorithm suggests that result. If its still too long, the function will, -again, remove any special characters or spaces from the stemmed word(s).

    -

    (5) Apply an abbreviation algorithm who's primary goal is readability, such -that the results remain unique. The methods described below are a bit more -'involved', but the results are very robust. First, you should know that -characters are always stripped from the end of the strings first (i.e. from -right to left). If an element of the variable name contains more than one -word (words are separated by spaces) then at least one letter from each -word will be retained.

    -

    Method: First spaces at the ends of the string are stripped. Then (if -necessary) any other spaces are stripped. Next, lower case vowels are -removed followed by lower case consonants. Finally if the abbreviation is -still longer than 8 chars, upper case letters and symbols are stripped.

    -

    When identifying 'words', the app performs some pre-processing steps in the -following order:

    * Certain symbols are replaced. Like the '%' symbol is replaced with 'PCT'. Ex: 'I_Ate_%' becomes 'I_Ate_PCT'
    -* Replace any symbols with a blank space. Ex: 'I_Ate' becomes 'I Ate'
    -* Find when there are two capital letters next to each other, followed by a lower case letter then adds a space between the two capital letters to separate the assumed 'words'. Ex: 'IAte' becomes 'I Ate'
    -* Insert a space between a number followed by a character. Ex: 'iAte1meal' becomes 'iAte1 meal'
    -* Insert a space between a character followed by a number. Ex: 'iAte1meal' becomes 'iAte 1meal'
    -
    - -

    What do we abbreviate when? If a stemmed word exists, the app will apply -the abbreviation algorithm described above on the stemmed version of the -variable name, else the original variable name.

    -

    Since submission guidelines indicate variables may not start with a number, -when found, the algorithm will either add and maintain a '_' "prefix -bundle" through any transformations detailed above, or it will relocate the -"prefix bundle" to the end of the term (the default behavior). What if a term -starts with non-standard numerical prefix? Currently, the function accounts -for the following types of prefix bundles, where 3 is used as an example -starting digit:

    * x = "3a. hey" will return "3a"
    -* x = "3_17a_hey" will return "3_17a"
    -* x = "3_17_hey" will return "3_17"
    -* x = "3_17hey" will return "3_17"
    -* x = "3hey" will return "3"
    -
    - -

    See also

    - -

    Other var_name functions: -xportr_tidy_names()

    - -

    Examples

    -
    vars <- c("", "STUDYID", "studyid", "subject id", "1c. ENT", "1b. Eyes", - "1d. Lungs", "1e. Heart", "year number", "1a. Skin_Desc") -adxx <- data.frame(matrix(0, ncol = 10, nrow = 3)) -colnames(adxx) <- vars - -my_dictionary <- data.frame(original_varname = "subject id", dict_varname = "subjid") - -metacore <- data.frame( - dataset = "adxx", # fix both here and in metacore? - variable = c(vars, "NotUsed") -) %>% -dplyr::union( - data.frame( dataset = "advs", variable = c("carrot", "cake")) -) - -xportr_df_varnames(adxx) # default -
    #> -#> The following variable name validation checks failed: -#> Must be 8 characters or less: Variables `subject id`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Must start with a letter: Variables `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, and `1a. Skin_Desc`. -#> Cannot contain any non-ASCII, symbol or underscore characters: Variables `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Cannot contain any lowercase characters Variables ``, `studyid`, `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`.
    #>
    #>
    #> -- 9 of 10 (90%) variables were renamed --
    #>
    #> Var 1: '' was renamed to 'V1' -#> Var 3: 'studyid' was renamed to 'STUDYID2' -#> Var 4: 'subject id' was renamed to 'SUBJECTD' -#> Var 5: '1c. ENT' was renamed to 'ENT1C' -#> Var 6: '1b. Eyes' was renamed to 'EYES1B' -#> Var 7: '1d. Lungs' was renamed to 'LUNGS1D' -#> Var 8: '1e. Heart' was renamed to 'HEART1E' -#> Var 9: 'year number' was renamed to 'YEARNUMB' -#> Var 10: '1a. Skin_Desc' was renamed to 'SKNDSC1A'
    #> -#> All renamed variables passed validation.
    #> V1 STUDYID STUDYID2 SUBJECTD ENT1C EYES1B LUNGS1D HEART1E YEARNUMB SKNDSC1A -#> 1 0 0 0 0 0 0 0 0 0 0 -#> 2 0 0 0 0 0 0 0 0 0 0 -#> 3 0 0 0 0 0 0 0 0 0 0
    xportr_df_varnames(adxx, relo_2_end = FALSE) # prefix numbers on left-hand side -
    #> -#> The following variable name validation checks failed: -#> Must be 8 characters or less: Variables `subject id`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Must start with a letter: Variables `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, and `1a. Skin_Desc`. -#> Cannot contain any non-ASCII, symbol or underscore characters: Variables `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Cannot contain any lowercase characters Variables ``, `studyid`, `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`.
    #>
    #> -- 9 of 10 (90%) variables were renamed --
    #>
    #> Var 1: '' was renamed to 'V1' -#> Var 3: 'studyid' was renamed to 'STUDYID2' -#> Var 4: 'subject id' was renamed to 'SUBJECTD' -#> Var 5: '1c. ENT' was renamed to 'X1CENT' -#> Var 6: '1b. Eyes' was renamed to 'X1BEYES' -#> Var 7: '1d. Lungs' was renamed to 'X1DLUNGS' -#> Var 8: '1e. Heart' was renamed to 'X1EHEART' -#> Var 9: 'year number' was renamed to 'YEARNUMB' -#> Var 10: '1a. Skin_Desc' was renamed to 'X1SKNDSC'
    #> -#> All renamed variables passed validation.
    #> V1 STUDYID STUDYID2 SUBJECTD X1CENT X1BEYES X1DLUNGS X1EHEART YEARNUMB -#> 1 0 0 0 0 0 0 0 0 0 -#> 2 0 0 0 0 0 0 0 0 0 -#> 3 0 0 0 0 0 0 0 0 0 -#> X1SKNDSC -#> 1 0 -#> 2 0 -#> 3 0
    xportr_df_varnames(adxx, dict_dat = my_dictionary) # 'SUBJID' used -
    #> -#> The following variable name validation checks failed: -#> Must be 8 characters or less: Variables `subject id`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Must start with a letter: Variables `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, and `1a. Skin_Desc`. -#> Cannot contain any non-ASCII, symbol or underscore characters: Variables `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Cannot contain any lowercase characters Variables ``, `studyid`, `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`.
    #>
    #> -- 9 of 10 (90%) variables were renamed --
    #>
    #> Var 1: '' was renamed to 'V1' -#> Var 3: 'studyid' was renamed to 'STUDYID2' -#> Var 4: 'subject id' was renamed to 'SUBJID' -#> Var 5: '1c. ENT' was renamed to 'ENT1C' -#> Var 6: '1b. Eyes' was renamed to 'EYES1B' -#> Var 7: '1d. Lungs' was renamed to 'LUNGS1D' -#> Var 8: '1e. Heart' was renamed to 'HEART1E' -#> Var 9: 'year number' was renamed to 'YEARNUMB' -#> Var 10: '1a. Skin_Desc' was renamed to 'SKNDSC1A'
    #> -#> All renamed variables passed validation.
    #> V1 STUDYID STUDYID2 SUBJID ENT1C EYES1B LUNGS1D HEART1E YEARNUMB SKNDSC1A -#> 1 0 0 0 0 0 0 0 0 0 0 -#> 2 0 0 0 0 0 0 0 0 0 0 -#> 3 0 0 0 0 0 0 0 0 0 0
    xportr_df_varnames(adxx, sep = "_") # permissible for legacy studies -
    #> -#> The following variable name validation checks failed: -#> Must be 8 characters or less: Variables `subject id`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Must start with a letter: Variables `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, and `1a. Skin_Desc`. -#> Cannot contain any non-ASCII, symbol or underscore characters: Variables `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Cannot contain any lowercase characters Variables ``, `studyid`, `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`.
    #>
    #> -- 9 of 10 (90%) variables were renamed --
    #>
    #> Var 1: '' was renamed to 'V1' -#> Var 3: 'studyid' was renamed to 'STUDYID2' -#> Var 4: 'subject id' was renamed to 'SUBJCTID' -#> Var 5: '1c. ENT' was renamed to 'ENT_1C' -#> Var 6: '1b. Eyes' was renamed to 'EYES_1B' -#> Var 7: '1d. Lungs' was renamed to 'LUNGS_1D' -#> Var 8: '1e. Heart' was renamed to 'HEART_1E' -#> Var 9: 'year number' was renamed to 'YEARNMBR' -#> Var 10: '1a. Skin_Desc' was renamed to 'SKNDSC1A'
    #> -#> The following variable name validation checks still failed: -#> Cannot contain any non-ASCII, symbol or underscore characters: Variables `ENT_1C`, `EYES_1B`, `LUNGS_1D`, and `HEART_1E`. -#> Cannot contain any lowercase characters Variables `ENT_1C`, `EYES_1B`, `LUNGS_1D`, and `HEART_1E`.
    #> V1 STUDYID STUDYID2 SUBJCTID ENT_1C EYES_1B LUNGS_1D HEART_1E YEARNMBR -#> 1 0 0 0 0 0 0 0 0 0 -#> 2 0 0 0 0 0 0 0 0 0 -#> 3 0 0 0 0 0 0 0 0 0 -#> SKNDSC1A -#> 1 0 -#> 2 0 -#> 3 0
    -
    -
    - -
    - - -
    - - -
    -

    Site built with pkgdown 1.6.1.

    -
    - -
    -
    - - - - - - - - diff --git a/docs/reference/xportr_format.html b/docs/reference/xportr_format.html deleted file mode 100644 index 9b582f78..00000000 --- a/docs/reference/xportr_format.html +++ /dev/null @@ -1,132 +0,0 @@ - -Assign SAS Format โ€” xportr_format โ€ข xportr - - -
    -
    - - - -
    -
    - - -
    -

    Assigns a SAS format from a variable level metadata to a given data frame.

    -
    - -
    -
    xportr_format(
    -  .df,
    -  metacore,
    -  domain = NULL,
    -  verbose = getOption("xportr.format_verbose", "none")
    -)
    -
    - -
    -

    Arguments

    -
    .df
    -

    A data frame of CDISC standard.

    -
    metacore
    -

    A data frame containing variable level metadata.

    -
    domain
    -

    A character value to subset the .df. If NULL(default), uses -.df value as a subset condition.

    -
    verbose
    -

    The action the function takes when a variable label isn't. -found. Options are 'stop', 'warn', 'message', and 'none'

    -
    -
    -

    Value

    -

    Data frame with SASformat attributes for each variable.

    -
    -
    -

    See also

    - -
    - -
    -

    Examples

    -
    adsl <- data.frame(
    -  USUBJID = c(1001, 1002, 1003),
    -  BRTHDT = c(1, 1, 2)
    -)
    -
    -metacore <- data.frame(
    -  dataset = c("adsl", "adsl"),
    -  variable = c("USUBJID", "BRTHDT"),
    -  format = c(NA, "DATE9.")
    -)
    -
    -adsl <- xportr_format(adsl, metacore)
    -
    -
    -
    - -
    - - -
    - -
    -

    Site built with pkgdown 2.0.3.

    -
    - -
    - - - - - - - - diff --git a/docs/reference/xportr_label.html b/docs/reference/xportr_label.html deleted file mode 100644 index 9d3787e2..00000000 --- a/docs/reference/xportr_label.html +++ /dev/null @@ -1,134 +0,0 @@ - -Assign Variable Label โ€” xportr_label โ€ข xportr - - -
    -
    - - - -
    -
    - - -
    -

    Assigns variable label from a variable level metadata to a given data frame.

    -
    - -
    -
    xportr_label(
    -  .df,
    -  metacore,
    -  domain = NULL,
    -  verbose = getOption("xportr.label_verbose", "none")
    -)
    -
    - -
    -

    Arguments

    -
    .df
    -

    A data frame of CDISC standard.

    -
    metacore
    -

    A data frame containing variable level metadata.

    -
    domain
    -

    A character value to subset the .df. If NULL(default), uses -.df value as a subset condition.

    -
    verbose
    -

    The action the function takes when a variable length isn't -Found. Options are 'stop', 'warn', 'message', and 'none'

    -
    -
    -

    Value

    -

    Data frame with label attributes for each variable.

    -
    -
    -

    See also

    - -
    - -
    -

    Examples

    -
    adsl <- data.frame(
    -  USUBJID = c(1001, 1002, 1003),
    -  SITEID = c(001, 002, 003),
    -  AGE = c(63, 35, 27),
    -  SEX = c("M", "F", "M")
    -)
    -
    -metacore <- data.frame(
    -  dataset = "adsl",
    -  variable = c("USUBJID", "SITEID", "AGE", "SEX"),
    -  label = c("Unique Subject Identifier", "Study Site Identifier", "Age", "Sex")
    -)
    -
    -adsl <- xportr_label(adsl, metacore)
    -
    -
    -
    - -
    - - -
    - -
    -

    Site built with pkgdown 2.0.3.

    -
    - -
    - - - - - - - - diff --git a/docs/reference/xportr_length.html b/docs/reference/xportr_length.html deleted file mode 100644 index 51a2aba4..00000000 --- a/docs/reference/xportr_length.html +++ /dev/null @@ -1,132 +0,0 @@ - -Assign SAS Length โ€” xportr_length โ€ข xportr - - -
    -
    - - - -
    -
    - - -
    -

    Assigns SAS length from a variable level metadata to a given data frame.

    -
    - -
    -
    xportr_length(
    -  .df,
    -  metacore,
    -  domain = NULL,
    -  verbose = getOption("xportr.length_verbose", "none")
    -)
    -
    - -
    -

    Arguments

    -
    .df
    -

    A data frame of CDISC standard.

    -
    metacore
    -

    A data frame containing variable level metadata.

    -
    domain
    -

    A character value to subset the .df. If NULL(default), uses -.df value as a subset condition.

    -
    verbose
    -

    The action the function takes when a length isn't found in -metadata. Options are 'stop', 'warn', 'message', and 'none'

    -
    -
    -

    Value

    -

    Data frame with SASlength attributes for each variable.

    -
    -
    -

    See also

    - -
    - -
    -

    Examples

    -
    adsl <- data.frame(
    -  USUBJID = c(1001, 1002, 1003),
    -  BRTHDT = c(1, 1, 2)
    -)
    -
    -metacore <- data.frame(
    -  dataset = c("adsl", "adsl"),
    -  variable = c("USUBJID", "BRTHDT"),
    -  length = c(10, 8)
    -)
    -
    -adsl <- xportr_length(adsl, metacore)
    -
    -
    -
    - -
    - - -
    - -
    -

    Site built with pkgdown 2.0.3.

    -
    - -
    - - - - - - - - diff --git a/docs/reference/xportr_order.html b/docs/reference/xportr_order.html deleted file mode 100644 index 5a75a97a..00000000 --- a/docs/reference/xportr_order.html +++ /dev/null @@ -1,107 +0,0 @@ - -Order variables of a dataset according to Spec โ€” xportr_order โ€ข xportr - - -
    -
    - - - -
    -
    - - -
    -

    Order variables of a dataset according to Spec

    -
    - -
    -
    xportr_order(
    -  .df,
    -  metacore,
    -  domain = NULL,
    -  verbose = getOption("xportr.order_verbose", "none")
    -)
    -
    - -
    -

    Arguments

    -
    .df
    -

    A data frame of CDISC standard.

    -
    metacore
    -

    A data frame containing variable level metadata.

    -
    domain
    -

    A character value to subset the .df. If NULL(default), uses -.df value as a subset condition.

    -
    verbose
    -

    Option for messaging order results

    -
    -
    -

    Value

    -

    Dataframe that has been re-ordered according to spec

    -
    - -
    - -
    - - -
    - -
    -

    Site built with pkgdown 2.0.3.

    -
    - -
    - - - - - - - - diff --git a/docs/reference/xportr_tidy_names.html b/docs/reference/xportr_tidy_names.html deleted file mode 100644 index ab5db080..00000000 --- a/docs/reference/xportr_tidy_names.html +++ /dev/null @@ -1,415 +0,0 @@ - - - - - - - - -Rename terms for submission compliance from vector โ€” xportr_tidy_names โ€ข xportr - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - -
    - -
    -
    - - -
    -

    The function takes a single character vector as input and converts each -string to be submission compliant. This means the variable must be a maximum -of 8 characters, ASCII only, and should contain only uppercase letters, -numbers and must start with a letter. No other symbols or special characters -should be included in these names. However, legacy studies started on or -before December 17, 2016, may use the underscore character "_". This function -is slightly more flexible than the submission criteria would allow, so use -the arguments wisely. xportr_var_names performs the same logic, -but directly renames the columns of a data.frame plus enforces more strict -adherence to the regulatory guidelines mentioned above.

    -
    - -
    xportr_tidy_names(
    -  original_varname,
    -  char_len = 8,
    -  relo_2_end = TRUE,
    -  letter_for_num_prefix = "x",
    -  sep = "",
    -  replace_vec = c(`'` = "", `"` = "", `%` = "_pct_", `#` = "_nmbr_"),
    -  dict_dat = data.frame(original_varname = character(), dict_varname = character()),
    -  letter_case = "upper",
    -  case = "parsed",
    -  return_df = FALSE,
    -  verbose = getOption("xportr.type_verbose", "none")
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    original_varname

    a character vector needing renaming

    char_len

    integer, the max number of characters allowed in the renamed -strings

    relo_2_end

    logical, if TRUE: numerical prefix bundles will be -relocated to the end of the string. A prefix bundle is determined as a -number or "grouping of numbers separated by special characters and other -punctuation". See details section for more info on prefix bundles.

    letter_for_num_prefix

    character. Will be ignored if relo_2_end = TRUE. Per CDISC & regulatory body requirements, variable names cannot -start with a number, so if you want to leave the number of the left-hand -side, use this argument to insert a starting letter before the numeric -prefix.

    sep

    string of only one character long, intended to separate words from -one another. In general, only "" and "" are possible, but the user can -insert a letter if desired. Note that "" is discouraged because it is only -permissible for legacy studies started on or before Dec 17, 2016.

    replace_vec

    A named character vector where the name is replaced by the -value.

    dict_dat

    a data frame containing two variables: the original_varname -and the dict_varname to find and replace

    letter_case

    character, with choices c("upper", "lower", "asis") -allowing user to make the final renamed terms uppercase (the default), -lowercase, or leave them as-is, respectively. Note, lowercase is discourage -as it is not submission compliant.

    case

    character, see to_any_case for more info -on alternate cases but some popular choices are "snake", "lower_camel", or -"parsed" (the default). From the documentation, the "parsed" case parses -out substrings and surrounds them with an underscore. Underscores at the -start and end are trimmed. No lower or upper case pattern from the input -string are changed.

    return_df

    logical, defaults to TRUE where entire dataset is returned -from suggestion process, else just the suggestion column itself

    - -

    Details

    - -

    Abbreviating variable names in the xportr pkg uses a step-by-step -process detailed below. Each original variable name will be renamed based -on the method performing the least amount of work to achieve the min -character length requirement (8 chars), thus maintaining maximum -originality. Therefore, the function only moves on to the next method on an -as-needed basis; namely, when the term is still greater than 8 characters, -even after lite modification.

    -

    (1) Blanks: Any columns that are missing a variable name (i.e., the header -was blank in the source file) will be renamed to 'V' + the column position. -So if the 2nd column is blank, it will receive a 'V2' rename.

    -

    (2) Use dictionary of controlled terminology: For example: 'Subject ID' may -better be suited as 'SUBJID' within your organization. Note, that -dictionary terms are expected to be submission compliant and will not be -further abbreviated. They will, however, undergo a check for -non-compliance.

    -

    (3) Do nothing! Or at the very least, mimic what SAS does automatically -when cleaning up variable names during a PROC IMPORT. Namely, replace any -special characters with underscores (''), capitalize everything, and if -the value starts with a digit, add the '' prefix. If the 'SASified' name -is <= 8 chars, then the function will use that rename. However, if its -still too long, the function will try removing any extra special characters -or spaces to help reduce to 8 chars.

    -

    (4) Find the STEM or ROOT word of each original variable name. For example, -if the original contains the word 'resting', the 'ing' will be dropped and -only the root word 'rest' will be considered. If less than 8 chars, the -algorithm suggests that result. If its still too long, the function will, -again, remove any special characters or spaces from the stemmed word(s).

    -

    (5) Apply an abbreviation algorithm who's primary goal is readability, such -that the results remain unique. The methods described below are a bit more -'involved', but the results are very robust. First, you should know that -characters are always stripped from the end of the strings first (i.e. from -right to left). If an element of the variable name contains more than one -word (words are separated by spaces) then at least one letter from each -word will be retained.

    -

    Method: First spaces at the ends of the string are stripped. Then (if -necessary) any other spaces are stripped. Next, lower case vowels are -removed followed by lower case consonants. Finally if the abbreviation is -still longer than 8 chars, upper case letters and symbols are stripped.

    -

    When identifying 'words', the app performs some pre-processing steps in the -following order:

      -
    • Certain symbols are replaced. Like the '%' symbol is replaced with 'PCT'. -Ex: 'I_Ate_%' becomes 'I_Ate_PCT'

    • -
    • Replace any symbols with a blank space. Ex: 'I_Ate' becomes 'I Ate'

    • -
    • Find when there are two capital letters next to each other, followed by a -lower case letter then adds a space between the two capital letters to -separate the assumed 'words'. Ex: 'IAte' becomes 'I Ate'

    • -
    • Insert a space between a number followed by a character. Ex: iAte1meal' -becomes 'iAte1 meal'

    • -
    • Insert a space between a character followed by a number. Ex: 'iAte1meal' -becomes 'iAte 1meal'

    • -
    - -

    What do we abbreviate when? If a stemmed word exists, the app will apply -the abbreviation algorithm described above on the stemmed version of the -variable name, else the original variable name.

    -

    Since submission guidelines indicate variables may not start with a number, -when found, the algorithm will either add and maintain a '_' "prefix -bundle" through any transformations detailed above, or it will relocate the -"prefix bundle" to the end of the term (the default behavior). What if a -term starts with non-standard numerical prefix? Currently, the function -accounts for the following types of prefix bundles, where 3 is used as an -example starting digit:

      -
    • x = "3a. hey" will return "3a"

    • -
    • x = "3_17a_hey" will return "3_17a"

    • -
    • x = "3_17_hey" will return "3_17"

    • -
    • x = "3_17hey" will return "3_17"

    • -
    • x = "3hey" will return "3"

    • -
    - -

    See also

    - -

    Other var_name functions: -xportr_df_varnames()

    - -

    Examples

    -
    vars <- c("", "studyid", "STUDYID", "subject id", "1c. ENT", "1b. Eyes", - "1d. Lungs", "1e. Heart", "year number", "1a. Skin_Desc") - -# Default behavior -xportr_tidy_names(vars) -
    #> -#> The following variable name validation checks failed: -#> Must be 8 characters or less: Variables `subject id`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Must start with a letter: Variables `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, and `1a. Skin_Desc`. -#> Cannot contain any non-ASCII, symbol or underscore characters: Variables `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Cannot contain any lowercase characters Variables ``, `studyid`, `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`.
    #>
    #> -- 10 of 10 (100%) variables were renamed --
    #>
    #> Var 1: '' was renamed to 'V1' -#> Var 2: 'studyid' was renamed to 'STUDYID' -#> Var 3: 'STUDYID' was renamed to 'STUDYID2' -#> Var 4: 'subject id' was renamed to 'SUBJECTD' -#> Var 5: '1c. ENT' was renamed to 'ENT1C' -#> Var 6: '1b. Eyes' was renamed to 'EYES1B' -#> Var 7: '1d. Lungs' was renamed to 'LUNGS1D' -#> Var 8: '1e. Heart' was renamed to 'HEART1E' -#> Var 9: 'year number' was renamed to 'YEARNUMB' -#> Var 10: '1a. Skin_Desc' was renamed to 'SKNDSC1A'
    #> -#> All renamed variables passed validation.
    #> [1] "V1" "STUDYID" "STUDYID2" "SUBJECTD" "ENT1C" "EYES1B" -#> [7] "LUNGS1D" "HEART1E" "YEARNUMB" "SKNDSC1A"
    xportr_tidy_names(vars, letter_case = "asis") -
    #> -#> The following variable name validation checks failed: -#> Must be 8 characters or less: Variables `subject id`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Must start with a letter: Variables `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, and `1a. Skin_Desc`. -#> Cannot contain any non-ASCII, symbol or underscore characters: Variables `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Cannot contain any lowercase characters Variables ``, `studyid`, `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`.
    #>
    #> -- 8 of 10 (80%) variables were renamed --
    #>
    #> Var 1: '' was renamed to 'V1' -#> Var 4: 'subject id' was renamed to 'subjectd' -#> Var 5: '1c. ENT' was renamed to 'ENT1c' -#> Var 6: '1b. Eyes' was renamed to 'Eyes1b' -#> Var 7: '1d. Lungs' was renamed to 'Lungs1d' -#> Var 8: '1e. Heart' was renamed to 'Heart1e' -#> Var 9: 'year number' was renamed to 'yearnumb' -#> Var 10: '1a. Skin_Desc' was renamed to 'SknDsc1a'
    #> -#> The following variable name validation checks still failed: -#> Cannot contain any lowercase characters Variables `studyid`, `subjectd`, `ENT1c`, `Eyes1b`, `Lungs1d`, `Heart1e`, `yearnumb`, and `SknDsc1a`.
    #> [1] "V1" "studyid" "STUDYID" "subjectd" "ENT1c" "Eyes1b" -#> [7] "Lungs1d" "Heart1e" "yearnumb" "SknDsc1a"
    -# Leave numerical prefix on left-hand side, but add a starting letter -xportr_tidy_names(vars, relo_2_end = FALSE, letter_for_num_prefix = "x") -
    #> -#> The following variable name validation checks failed: -#> Must be 8 characters or less: Variables `subject id`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Must start with a letter: Variables `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, and `1a. Skin_Desc`. -#> Cannot contain any non-ASCII, symbol or underscore characters: Variables `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Cannot contain any lowercase characters Variables ``, `studyid`, `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`.
    #>
    #> -- 10 of 10 (100%) variables were renamed --
    #>
    #> Var 1: '' was renamed to 'V1' -#> Var 2: 'studyid' was renamed to 'STUDYID' -#> Var 3: 'STUDYID' was renamed to 'STUDYID2' -#> Var 4: 'subject id' was renamed to 'SUBJECTD' -#> Var 5: '1c. ENT' was renamed to 'X1CENT' -#> Var 6: '1b. Eyes' was renamed to 'X1BEYES' -#> Var 7: '1d. Lungs' was renamed to 'X1DLUNGS' -#> Var 8: '1e. Heart' was renamed to 'X1EHEART' -#> Var 9: 'year number' was renamed to 'YEARNUMB' -#> Var 10: '1a. Skin_Desc' was renamed to 'X1SKNDSC'
    #> -#> All renamed variables passed validation.
    #> [1] "V1" "STUDYID" "STUDYID2" "SUBJECTD" "X1CENT" "X1BEYES" -#> [7] "X1DLUNGS" "X1EHEART" "YEARNUMB" "X1SKNDSC"
    -# Add a dictionary and remove underscores -xportr_tidy_names(vars, sep = "", - dict_dat = data.frame(original_varname = "subject id", dict_varname = "subjid")) -
    #> -#> The following variable name validation checks failed: -#> Must be 8 characters or less: Variables `subject id`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Must start with a letter: Variables `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, and `1a. Skin_Desc`. -#> Cannot contain any non-ASCII, symbol or underscore characters: Variables `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`. -#> Cannot contain any lowercase characters Variables ``, `studyid`, `subject id`, `1c. ENT`, `1b. Eyes`, `1d. Lungs`, `1e. Heart`, `year number`, and `1a. Skin_Desc`.
    #>
    #> -- 10 of 10 (100%) variables were renamed --
    #>
    #> Var 1: '' was renamed to 'V1' -#> Var 2: 'studyid' was renamed to 'STUDYID' -#> Var 3: 'STUDYID' was renamed to 'STUDYID2' -#> Var 4: 'subject id' was renamed to 'SUBJID' -#> Var 5: '1c. ENT' was renamed to 'ENT1C' -#> Var 6: '1b. Eyes' was renamed to 'EYES1B' -#> Var 7: '1d. Lungs' was renamed to 'LUNGS1D' -#> Var 8: '1e. Heart' was renamed to 'HEART1E' -#> Var 9: 'year number' was renamed to 'YEARNUMB' -#> Var 10: '1a. Skin_Desc' was renamed to 'SKNDSC1A'
    #> -#> All renamed variables passed validation.
    #> [1] "V1" "STUDYID" "STUDYID2" "SUBJID" "ENT1C" "EYES1B" -#> [7] "LUNGS1D" "HEART1E" "YEARNUMB" "SKNDSC1A"
    -
    -
    - -
    - - -
    - - -
    -

    Site built with pkgdown 1.6.1.

    -
    - -
    -
    - - - - - - - - diff --git a/docs/reference/xportr_type.html b/docs/reference/xportr_type.html deleted file mode 100644 index 0166b8cb..00000000 --- a/docs/reference/xportr_type.html +++ /dev/null @@ -1,135 +0,0 @@ - -Coerce variable type โ€” xportr_type โ€ข xportr - - -
    -
    - - - -
    -
    - - -
    -

    Current assumptions: -columns_meta is a data.frame with names "Variables", "Type"

    -
    - -
    -
    xportr_type(
    -  .df,
    -  metacore,
    -  domain = NULL,
    -  verbose = getOption("xportr.type_verbose", "none")
    -)
    -
    - -
    -

    Arguments

    -
    .df
    -

    An R object with columns that can be coerced

    -
    metacore
    -

    Either a data.frame that has the names of all possible columns -and their types, or a Metacore object from the Metacore package. Required -column names are dataset, variables, type

    -
    domain
    -

    Name of the dataset. Ex ADAE/DM. This will be used to subset -the metacore object. If none is passed it is assumed to be the name of the -dataset passed in .df.

    -
    verbose
    -

    The action the function takes when a variable isn't typed -properly. Options are 'stop', 'warn', 'message', and 'none'

    -
    -
    -

    Value

    -

    Returns the modified table.

    -
    - -
    -

    Examples

    -
    metacore <- data.frame(
    -  dataset = "test",
    -  variable = c("Subj", "Param", "Val", "NotUsed"),
    -  type = c("numeric", "character", "numeric", "character")
    -)
    -
    -.df <- data.frame(
    - Subj = as.character(123, 456, 789),
    - Different = c("a", "b", "c"),
    - Val = c("1", "2", "3"),
    - Param = c("param1", "param2", "param3")
    -)
    -
    -df2 <- xportr_type(.df, metacore, "test")
    -#> 
    -#> โ”€โ”€ Variable type mismatches found. โ”€โ”€
    -#> 
    -#> โœ” 2 variables coerced
    -
    -
    -
    - -
    - - -
    - -
    -

    Site built with pkgdown 2.0.3.

    -
    - -
    - - - - - - - - diff --git a/docs/reference/xportr_write.html b/docs/reference/xportr_write.html deleted file mode 100644 index 28348a6f..00000000 --- a/docs/reference/xportr_write.html +++ /dev/null @@ -1,112 +0,0 @@ - -Write xpt v5 transport file โ€” xportr_write โ€ข xportr - - -
    -
    - - - -
    -
    - - -
    -

    Writes a local data frame into SAS transport file of version 5. The SAS -transport format is an open format, as is required for submission of the data -to the FDA.

    -
    - -
    -
    xportr_write(.df, path, label = NULL)
    -
    - -
    -

    Arguments

    -
    .df
    -

    A data frame to write.

    -
    path
    -

    Path where transport file will be written. File name sans will be -used as xpt name.

    -
    label
    -

    Dataset label. It must be<=40 characters.

    -
    -
    -

    Value

    -

    A data frame. xportr_write() returns the input data invisibly.

    -
    -
    -

    Details

    - -
    • Variable and dataset labels are stored in the "label" attribute.

    • -
    • SAS length are stored in the "SASlength" attribute.

    • -
    • SAS format are stored in the "SASformat" attribute.

    • -
    • SAS type are stored in the "SAStype" attribute.

    • -
    - -
    - -
    - - -
    - -
    -

    Site built with pkgdown 2.0.3.

    -
    - -
    - - - - - - - - diff --git a/docs/reference/xpt_check_ascii_lbls.html b/docs/reference/xpt_check_ascii_lbls.html deleted file mode 100644 index ce04efc4..00000000 --- a/docs/reference/xpt_check_ascii_lbls.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - -ASCII Check on Variable Labels โ€” xpt_check_ascii_lbls โ€ข xportr - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - -
    - -
    -
    - - -
    -

    Check that only ASCII characters are being used in variable labels

    -
    - -
    xpt_check_ascii_lbls(.df)
    - -

    Arguments

    - - - - - - -
    .df

    Data frame

    - - -
    - -
    - - -
    - - -
    -

    Site built with pkgdown 1.6.1.

    -
    - -
    -
    - - - - - - - - diff --git a/docs/reference/xpt_check_ascii_vars.html b/docs/reference/xpt_check_ascii_vars.html deleted file mode 100644 index 2e172ff1..00000000 --- a/docs/reference/xpt_check_ascii_vars.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - -ASCII Check on Variable Names โ€” xpt_check_ascii_vars โ€ข xportr - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - -
    - -
    -
    - - -
    -

    Check that only ASCII characters are being used in variable names

    -
    - -
    xpt_check_ascii_vars(.df)
    - -

    Arguments

    - - - - - - -
    .df

    Data frame

    - - -
    - -
    - - -
    - - -
    -

    Site built with pkgdown 1.6.1.

    -
    - -
    -
    - - - - - - - - diff --git a/docs/reference/xpt_check_label_length.html b/docs/reference/xpt_check_label_length.html deleted file mode 100644 index 753bede2..00000000 --- a/docs/reference/xpt_check_label_length.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - -Variable Label Check โ€” xpt_check_label_length โ€ข xportr - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - -
    - -
    -
    - - -
    -

    Check for variable labels greater than 40 characters in length

    -
    - -
    xpt_check_label_length(.df)
    - -

    Arguments

    - - - - - - -
    .df

    Data set with labels to check

    - - -
    - -
    - - -
    - - -
    -

    Site built with pkgdown 1.6.1.

    -
    - -
    -
    - - - - - - - - diff --git a/example_data_specs/TDF_ADaM - Pilot 3 Team updated.xlsx b/example_data_specs/TDF_ADaM - Pilot 3 Team updated.xlsx new file mode 100644 index 00000000..52be4910 Binary files /dev/null and b/example_data_specs/TDF_ADaM - Pilot 3 Team updated.xlsx differ diff --git a/example_data_specs/TDF_ADaM_Pilot3.xlsx b/example_data_specs/TDF_ADaM_Pilot3.xlsx new file mode 100644 index 00000000..52be4910 Binary files /dev/null and b/example_data_specs/TDF_ADaM_Pilot3.xlsx differ diff --git a/example_data_specs/adadas.xpt b/example_data_specs/adadas.xpt new file mode 100644 index 00000000..de7b820a Binary files /dev/null and b/example_data_specs/adadas.xpt differ diff --git a/example_data_specs/adae.xpt b/example_data_specs/adae.xpt new file mode 100644 index 00000000..fc9cc95b Binary files /dev/null and b/example_data_specs/adae.xpt differ diff --git a/example_data_specs/adlbc.xpt b/example_data_specs/adlbc.xpt new file mode 100644 index 00000000..17740f1c Binary files /dev/null and b/example_data_specs/adlbc.xpt differ diff --git a/example_data_specs/adtte.xpt b/example_data_specs/adtte.xpt new file mode 100644 index 00000000..039e2e1e Binary files /dev/null and b/example_data_specs/adtte.xpt differ diff --git a/example_data_specs/readme.md b/example_data_specs/readme.md new file mode 100644 index 00000000..f4b3e9ba --- /dev/null +++ b/example_data_specs/readme.md @@ -0,0 +1 @@ +Data taken from Pilot 3 Submission Study: https://github.com/RConsortium/submissions-pilot3-adam diff --git a/inst/WORDLIST b/inst/WORDLIST index 03ab611d..494ba288 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,92 +1,48 @@ -ADaM ADAE -ADCM -ADDV -ADEC -ADEG -ADEX -ADLB -ADMH ADSL -ADVS +ADaM AE -analytics -ATC Atorus -Bazett -Bazett's -BDS -Biologics BMI -Biologics CDISC -Changelog -censorings -codebase -CRF -CQ -cyclomatic -datepart -datetime -developersโ€™ -dtc -DTC -DuBois -durations -EMA -FACM -Fridericia -Fridericia's -Fujimoto -functionsโ€™ -funder -Gehan -GitHub -GlaxoSmithKline -groupwise +CDSIC +Codelist +Completers +DCREASCD +DM GSK -Guillain -GUILLAIN -GxP -Hoffmann -https -IG -knitr +JPT Lifecycle -linter -LLC -MedDRA -metacore -metatools -mmHg -modularized -Mosteller -msec -OCCDS -optionality -Pharma -pharmaverse +MMSE +ORCID PHUSE -quosure -quosures -README -RStudio -Sagie -Sagie's +Pharma +Repostiory +SASformat +SASlength +SAStype +SDSP SDTM -SDQ -SMQ -SMQs -stylesheet -summarization -Takahira -tidyverse -timepart -timepoint -ungrouped -unmerged +Standardisation +TRTDUR +Trt +Vignesh +Vis +XPT +acrf +adrg +bootswatch +chr +cli +deliverables +df +iso +magrittr +metacore +pre +repo +sdrg validator -validator's -xpt +validators xportr's -YAML +xpt diff --git a/man/adsl.Rd b/man/adsl.Rd new file mode 100644 index 00000000..cd943674 --- /dev/null +++ b/man/adsl.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{adsl} +\alias{adsl} +\title{Analysis Dataset Subject Level} +\format{ +\subsection{\code{adsl}}{ + +A data frame with 254 rows and 48 columns: +\describe{ +\item{STUDYID}{Study Identifier} +\item{USUBJID}{Unique Subject Identifier} +\item{SUBJID}{Subject Identifier for the Study} +\item{SITEID}{Study Site Identifier} +\item{SITEGR1}{Pooled Site Group 1} +\item{ARM}{Description of Planned Arm} +\item{TRT01P}{Planned Treatment for Period 01} +\item{TRT01PN}{Planned Treatment for Period 01 (N)} +\item{TRT01A}{Actual Treatment for Period 01} +\item{TRT01AN}{Actual Treatment for Period 01 (N)} +\item{TRTSDT}{Date of First Exposure to Treatment} +\item{TRTEDT}{Date of Last Exposure to Treatment} +\item{TRTDUR}{Duration of Treatment (days)} +\item{AVGDD}{Avg Daily Dose (as planned)} +\item{CUMDOSE}{Cumulative Dose (as planned)} +\item{AGE}{Age} +\item{AGEGR1}{Pooled Age Group 1} +\item{AGEGR1N}{Pooled Age Group 1 (N)} +\item{AGEU}{Age Units} +\item{RACE}{Race} +\item{RACEN}{Race (N)} +\item{SEX}{Sex} +\item{ETHNIC}{Ethnicity} +\item{SAFFL}{Safety Population Flag} +\item{ITTFL}{Intent-To-Treat Population Flag} +\item{EFFFL}{Efficacy Population Flag} +\item{COMP8FL}{Completers of Week 8 Population Flag} +\item{COMP16FL}{Completers of Week 16 Population Flag} +\item{COMP24FL}{Completers of Week 24 Population Flag} +\item{DISCONFL}{Did the Subject Discontinue the Study} +\item{DSRAEFL}{Discontinued due to AE} +\item{DTHFL}{Subject Died} +\item{BMIBL}{Baseline BMI (kg/m^2)} +\item{BMIBLGR1}{Pooled Baseline BMI Group 1} +\item{HEIGHTBL}{Baseline Height (cm)} +\item{WEIGHTBL}{Baseline Weight (kg)} +\item{EDUCLVL}{Years of Education} +\item{DISONSDT}{Date of Onset of Disease} +\item{DURDIS}{Duration of Disease (Months)} +\item{DURDSGR1}{Pooled Disease Duration Group 1} +\item{VISIT1DT}{Date of Visit 1} +\item{RFSTDTC}{Subject Reference Start Date/Time} +\item{RFENDTC}{Subject Reference End Date/Time} +\item{VISNUMEN}{End of Trt Visit (Vis 12 or Early Term.)} +\item{RFENDT}{Date of Discontinuation/Completion} +\item{DCDECOD}{Standardized Disposition Term} +\item{DCREASCD}{Reason for Discontinuation} +\item{MMSETOT}{MMSE Total} +} +} +} +\usage{ +adsl +} +\description{ +An example dataset containing subject level data +} +\keyword{datasets} diff --git a/man/expect_attr_width.Rd b/man/expect_attr_width.Rd new file mode 100644 index 00000000..7070291a --- /dev/null +++ b/man/expect_attr_width.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/support-test.R +\name{expect_attr_width} +\alias{expect_attr_width} +\title{Custom expect function to test result of xportr_length} +\usage{ +expect_attr_width(result, metadata_length) +} +\arguments{ +\item{result}{data.frame with \code{width} attribute on its columns.} + +\item{metadata_length}{vector of numeric with expected lengths for each +column width.} +} +\value{ +The first argument, invisibly. +} +\description{ +Custom expect function to test result of xportr_length +} +\keyword{internal} diff --git a/man/figures/design_flow.png b/man/figures/design_flow.png index beef0e52..543a8db3 100644 Binary files a/man/figures/design_flow.png and b/man/figures/design_flow.png differ diff --git a/man/figures/lifecycle-archived.svg b/man/figures/lifecycle-archived.svg new file mode 100644 index 00000000..48f72a6f --- /dev/null +++ b/man/figures/lifecycle-archived.svg @@ -0,0 +1 @@ + lifecyclelifecyclearchivedarchived \ No newline at end of file diff --git a/man/figures/lifecycle-defunct.svg b/man/figures/lifecycle-defunct.svg new file mode 100644 index 00000000..01452e5f --- /dev/null +++ b/man/figures/lifecycle-defunct.svg @@ -0,0 +1 @@ +lifecyclelifecycledefunctdefunct \ No newline at end of file diff --git a/man/figures/lifecycle-deprecated.svg b/man/figures/lifecycle-deprecated.svg new file mode 100644 index 00000000..4baaee01 --- /dev/null +++ b/man/figures/lifecycle-deprecated.svg @@ -0,0 +1 @@ +lifecyclelifecycledeprecateddeprecated \ No newline at end of file diff --git a/man/figures/lifecycle-experimental.svg b/man/figures/lifecycle-experimental.svg new file mode 100644 index 00000000..d1d060e9 --- /dev/null +++ b/man/figures/lifecycle-experimental.svg @@ -0,0 +1 @@ +lifecyclelifecycleexperimentalexperimental \ No newline at end of file diff --git a/man/figures/lifecycle-maturing.svg b/man/figures/lifecycle-maturing.svg new file mode 100644 index 00000000..df713101 --- /dev/null +++ b/man/figures/lifecycle-maturing.svg @@ -0,0 +1 @@ +lifecyclelifecyclematuringmaturing \ No newline at end of file diff --git a/man/figures/lifecycle-questioning.svg b/man/figures/lifecycle-questioning.svg new file mode 100644 index 00000000..08ee0c90 --- /dev/null +++ b/man/figures/lifecycle-questioning.svg @@ -0,0 +1 @@ +lifecyclelifecyclequestioningquestioning \ No newline at end of file diff --git a/man/figures/lifecycle-stable.svg b/man/figures/lifecycle-stable.svg new file mode 100644 index 00000000..e015dc81 --- /dev/null +++ b/man/figures/lifecycle-stable.svg @@ -0,0 +1 @@ +lifecyclelifecyclestablestable \ No newline at end of file diff --git a/man/figures/lifecycle-superseded.svg b/man/figures/lifecycle-superseded.svg new file mode 100644 index 00000000..75f24f55 --- /dev/null +++ b/man/figures/lifecycle-superseded.svg @@ -0,0 +1 @@ + lifecyclelifecyclesupersededsuperseded \ No newline at end of file diff --git a/man/figures/xpt_validate.png b/man/figures/xpt_validate.png new file mode 100644 index 00000000..8db728f6 Binary files /dev/null and b/man/figures/xpt_validate.png differ diff --git a/man/length_log.Rd b/man/length_log.Rd index 8bbc1d63..8b550292 100644 --- a/man/length_log.Rd +++ b/man/length_log.Rd @@ -7,7 +7,7 @@ length_log(miss_vars, verbose) } \arguments{ -\item{miss_vars}{Variables missing from metatdata} +\item{miss_vars}{Variables missing from metadata} \item{verbose}{Provides additional messaging for user} } diff --git a/man/local_cli_theme.Rd b/man/local_cli_theme.Rd new file mode 100644 index 00000000..959204d1 --- /dev/null +++ b/man/local_cli_theme.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/support-test.R +\name{local_cli_theme} +\alias{local_cli_theme} +\title{Local function to remove extra spaces and format by cli} +\usage{ +local_cli_theme(.local_envir = parent.frame()) +} +\arguments{ +\item{`[environment(1)]`\cr}{Attach exit handlers to this environment. Typically, this should +be either the current environment or a parent frame +(accessed through parent.frame()).} +} +\description{ +Groups together multiple calls instead of being spread out in code +} +\keyword{internal} diff --git a/man/minimal_metadata.Rd b/man/minimal_metadata.Rd new file mode 100644 index 00000000..0fc416d4 --- /dev/null +++ b/man/minimal_metadata.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/support-test.R +\name{minimal_metadata} +\alias{minimal_metadata} +\title{Minimal metadata data frame mock for a ADaM dataset} +\usage{ +minimal_metadata( + dataset = FALSE, + length = FALSE, + label = FALSE, + type = FALSE, + format = FALSE, + order = FALSE, + dataset_name = "adsl", + var_names = NULL +) +} +\arguments{ +\item{dataset}{Flag that indicates that the \code{dataset} column should +be included.} + +\item{length}{Flag that indicates that the \code{length} column should +be included.} + +\item{label}{Flag that indicates that the \code{label} column should +be included.} + +\item{type}{Flag that indicates that the \code{type} column should +be included.} + +\item{format}{Flag that indicates that the \code{format} column should +be included.} + +\item{order}{Flag that indicates that the \code{order} column should +be included.} + +\item{dataset_name}{String with name of domain.} + +\item{var_names}{Character vector that defines which variables (rows) +to keep} +} +\value{ +A metadata data.frame +} +\description{ +Minimal metadata data frame mock for a ADaM dataset +} +\keyword{internal} diff --git a/man/minimal_table.Rd b/man/minimal_table.Rd new file mode 100644 index 00000000..35bb62d5 --- /dev/null +++ b/man/minimal_table.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/support-test.R +\name{minimal_table} +\alias{minimal_table} +\title{Minimal data frame mock of a valid ADaM dataset} +\usage{ +minimal_table(n_rows = 3, cols = c("x", "y")) +} +\arguments{ +\item{n_rows}{Numeric value that indicates the number of rows of the data +frame} + +\item{cols}{Vector of characters that indicates which columns to return. +By default only \code{x} and \code{y} are returned with numeric contents.} +} +\value{ +A data.frame mimicking a valid ADaM dataset. +} +\description{ +This function is only used in tests. +} +\keyword{internal} diff --git a/man/multiple_vars_in_spec_helper.Rd b/man/multiple_vars_in_spec_helper.Rd new file mode 100644 index 00000000..d3cefce6 --- /dev/null +++ b/man/multiple_vars_in_spec_helper.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/support-test.R +\name{multiple_vars_in_spec_helper} +\alias{multiple_vars_in_spec_helper} +\title{Test if multiple vars in spec will result in warning message} +\usage{ +multiple_vars_in_spec_helper(FUN) +} +\description{ +Test if multiple vars in spec will result in warning message +} +\keyword{internal} diff --git a/man/multiple_vars_in_spec_helper2.Rd b/man/multiple_vars_in_spec_helper2.Rd new file mode 100644 index 00000000..f3e09957 --- /dev/null +++ b/man/multiple_vars_in_spec_helper2.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/support-test.R +\name{multiple_vars_in_spec_helper2} +\alias{multiple_vars_in_spec_helper2} +\title{Test if multiple vars in spec with appropriate} +\usage{ +multiple_vars_in_spec_helper2(FUN) +} +\description{ +Test if multiple vars in spec with appropriate +} +\keyword{internal} diff --git a/man/var_ord_msg.Rd b/man/var_ord_msg.Rd index 1b92ab4e..be7f79dd 100644 --- a/man/var_ord_msg.Rd +++ b/man/var_ord_msg.Rd @@ -4,10 +4,12 @@ \alias{var_ord_msg} \title{Utility for Ordering} \usage{ -var_ord_msg(moved_vars, verbose) +var_ord_msg(reordered_vars, moved_vars, verbose) } \arguments{ -\item{moved_vars}{Variables moved in the dataset} +\item{reordered_vars}{Number of variables reordered} + +\item{moved_vars}{Number of variables moved in the dataset} \item{verbose}{Provides additional messaging for user} } diff --git a/man/var_spec.Rd b/man/var_spec.Rd new file mode 100644 index 00000000..1b688c9c --- /dev/null +++ b/man/var_spec.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{var_spec} +\alias{var_spec} +\title{Example Dataset Specification} +\format{ +\subsection{\code{var_spec}}{ + +A data frame with 216 rows and 19 columns: +\describe{ +\item{Order}{Order of variable} +\item{Dataset}{Dataset} +\item{Variable}{Variable} +\item{Label}{Variable Label} +\item{Data Type}{Data Type} +\item{Length}{Variable Length} +\item{Significant Digits}{Significant Digits} +\item{Format}{Variable Format} +\item{Mandatory}{Mandatory Variable Flag} +\item{Assigned Value}{Variable Assigned Value} +\item{Codelist}{Variable Codelist} +\item{Common}{Common Variable Flag} +\item{Origin}{Variable Origin} +\item{Pages}{Pages} +\item{Method}{Variable Method} +\item{Predecessor}{Variable Predecessor} +\item{Role}{Variable Role} +\item{Comment}{Comment} +\item{Developer Notes}{Developer Notes} +} +} +} +\usage{ +var_spec +} +\description{ +Example Dataset Specification +} +\keyword{datasets} diff --git a/man/xportr-package.Rd b/man/xportr-package.Rd index 157e2099..cf9ff450 100644 --- a/man/xportr-package.Rd +++ b/man/xportr-package.Rd @@ -6,8 +6,101 @@ \alias{xportr-package} \title{The \code{xportr} package} \description{ -Package Info here +\code{xportr} is designed to be a clinical workflow friendly method for outputting +CDISC complaint data sets in R, to XPT version 5 files. It was designed with +options in mind to allow for flexible setting of options while allowing +projects and system administrators to set sensible defaults for their +orginziations workflows. Below are a list of options that can be set to +customize how \code{xportr} works in your environment. } +\section{xportr options}{ + + +\itemize{ +\item{ +xportr.df_domain_name - The name of the domain "name" column in dataset +metadata. Default: "dataset" +} +\item { +xportr.df_label - The column noting the dataset label in dataset metadata. +Default: "label" +} +\item{ +xportr.domain_name - The name of the domain "name" column in variable +metadata. Default: "dataset" +} +\item{ +xportr.variable_name - The name of the variable "name" in variable +metadata. Default: "variable" +} +\item{ +xportr.type_name - The name of the variable type column in variable +metadata. Default: "type" +} +\item{ +xportr.label - The name of the variable label column in variable metadata. +Default: "label" +} +\item{ +xportr.length - The name of the variable length column in variable +metadata. Default: "length" +} +\item{ +xportr.format_name - The name of the variable format column in variable +metadata. Default: "format" +} +\item{ +xportr.order_name - The name of the variable order column in variable +metadata. Default: "order" +} +\item{ +xportr.format_verbose - The default argument for the 'verbose' argument for +\code{xportr_format}. Default: "none" +} +\item{ +xportr.label_verbose - The default argument for the 'verbose' argument for +\code{xportr_label}. Default: "none" +} +\item{ +xportr.length_verbose - The default argument for the 'verbose' argument for +\code{xportr_length}. Default: "none" +} +\item{ +xportr.type_verbose - The default argument for the 'verbose' argument for +\code{xportr_type}. Default: "none" +} +\item{ +xportr.character_types - The default character vector used to explicitly +coerce R classes to character XPT types. Default: c("character", "char", +"text", "date", "posixct", "posixt", "datetime", "time", "partialdate", +"partialtime", "partialdatetime", "incompletedatetime", "durationdatetime", +"intervaldatetime") +} +\item{ +xportr.numeric_types - The default character vector used to explicitly +coerce R classes to numeric XPT types. Default: c("integer", "numeric", +"num", "float") +} +} +} + +\section{Updating Options}{ + +\itemize{ +\item{For a single session, an option can be changed by +\verb{option( = )}} +\item{To change an option for a single projects across sessions in that +projects, place the options update in the \code{.Rprofile} in that project +directory.} +\item{To change an option for a user across all sessions, place the options +update in the \code{.Rprofile} file in the users home directory.} +\item{To change an option for all users in an R environment, place the +options update in the \code{.Rprofile.site} file in the R home directory.} +} + +See \href{https://support.posit.co/hc/en-us/articles/360047157094-Managing-R-with-Rprofile-Renviron-Rprofile-site-Renviron-site-rsession-conf-and-repos-conf}{Managing R with .Rprofile, .Renviron, Rprofile.site, Renviron.site, rsession.conf, and repos.conf} # nolint +} + \seealso{ Useful links: \itemize{ @@ -21,10 +114,17 @@ Useful links: Authors: \itemize{ - \item Vignesh Thanikachalam \email{vignesh.x.thanikachalam@gsk.com} - \item Ben Straub \email{ben.x.straub@gsk.com} - \item Ross Didenko \email{Ross.Didenko@AtorusResearch.com} - \item Zelos Zhu \email{Zelos.Zhu@AtorusResearch.com} + \item Vignesh Thanikachalam + \item Ben Straub + \item Ross Didenko + \item Zelos Zhu + \item Ethan Brockmann + \item Vedha Viyash + \item Andre Verissimo + \item Sophie Shapcott + \item Celine Piraux + \item Adrian Chan + \item Sadchla Mascary } Other contributors: diff --git a/man/xportr_df_label.Rd b/man/xportr_df_label.Rd index 86316b64..e0a461fd 100644 --- a/man/xportr_df_label.Rd +++ b/man/xportr_df_label.Rd @@ -4,22 +4,44 @@ \alias{xportr_df_label} \title{Assign Dataset Label} \usage{ -xportr_df_label(.df, metacore, domain = NULL) +xportr_df_label(.df, metadata = NULL, domain = NULL, metacore = deprecated()) } \arguments{ \item{.df}{A data frame of CDISC standard.} -\item{metacore}{A data frame containing dataset level metadata.} +\item{metadata}{A data frame containing dataset. See 'Metadata' section for +details.} -\item{domain}{A character value to subset the \code{.df}. If \code{NULL}(default), uses -\code{.df} value as a subset condition.} +\item{domain}{Appropriate CDSIC dataset name, e.g. ADAE, DM. Used to subset +the metadata object. If none is passed, then name of the dataset passed as +.df will be used.} + +\item{metacore}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Previously used to pass +metadata now renamed with \code{metadata}} } \value{ Data frame with label attributes. } \description{ Assigns dataset label from a dataset level metadata to a given data frame. +This is stored in the 'label' attribute of the dataframe. +} +\section{Metadata}{ + The argument passed in the 'metadata' argument can either +be a metacore object, or a data.frame containing the data listed below. If +metacore is used, no changes to options are required. + +For data.frame 'metadata' arguments two columns must be present: +\enumerate{ +\item Domain Name - passed as the 'xportr.df_domain_name' option. Default: +"dataset". This is the column subset by the 'domain' argument in the +function. +\item Label Name - passed as the 'xportr.df_label' option. Default: +"format". Character values to update the 'format.sas' attribute of the +dataframe This is passed to \code{haven::write_xpt} to note the label. +} } + \examples{ adsl <- data.frame( USUBJID = c(1001, 1002, 1003), @@ -28,19 +50,10 @@ adsl <- data.frame( SEX = c("M", "F", "M") ) -metacore <- data.frame( +metadata <- data.frame( dataset = c("adsl", "adae"), label = c("Subject-Level Analysis", "Adverse Events Analysis") ) -adsl <- xportr_df_label(adsl, metacore) -} -\seealso{ -\code{\link[=xportr_label]{xportr_label()}}, \code{\link[=xportr_format]{xportr_format()}} and \code{\link[=xportr_length]{xportr_length()}} - -Other metadata functions: -\code{\link{xportr_format}()}, -\code{\link{xportr_label}()}, -\code{\link{xportr_length}()} +adsl <- xportr_df_label(adsl, metadata) } -\concept{metadata functions} diff --git a/man/xportr_format.Rd b/man/xportr_format.Rd index d62d1781..a4f06222 100644 --- a/man/xportr_format.Rd +++ b/man/xportr_format.Rd @@ -4,50 +4,59 @@ \alias{xportr_format} \title{Assign SAS Format} \usage{ -xportr_format( - .df, - metacore, - domain = NULL, - verbose = getOption("xportr.format_verbose", "none") -) +xportr_format(.df, metadata = NULL, domain = NULL, metacore = deprecated()) } \arguments{ \item{.df}{A data frame of CDISC standard.} -\item{metacore}{A data frame containing variable level metadata.} +\item{metadata}{A data frame containing variable level metadata. See +'Metadata' section for details.} -\item{domain}{A character value to subset the \code{.df}. If \code{NULL}(default), uses -\code{.df} value as a subset condition.} +\item{domain}{Appropriate CDSIC dataset name, e.g. ADAE, DM. Used to subset +the metadata object. If none is passed, then name of the dataset passed as +.df will be used.} -\item{verbose}{The action the function takes when a variable label isn't. -found. Options are 'stop', 'warn', 'message', and 'none'} +\item{metacore}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Previously used to pass +metadata now renamed with \code{metadata}} } \value{ Data frame with \code{SASformat} attributes for each variable. } \description{ -Assigns a SAS format from a variable level metadata to a given data frame. +Assigns a SAS format from a variable level metadata to a given data frame. If +no format is found for a given variable, it is set as an empty character +vector. This is stored in the format.sas attribute. +} +\section{Metadata}{ + The argument passed in the 'metadata' argument can either +be a metacore object, or a data.frame containing the data listed below. If +metacore is used, no changes to options are required. + +For data.frame 'metadata' arguments three columns must be present: +\enumerate{ +\item Domain Name - passed as the 'xportr.domain_name' option. Default: +"dataset". This is the column subset by the 'domain' argument in the +function. +\item Format Name - passed as the 'xportr.format_name' option. +Default: "format". Character values to update the 'format.sas' attribute of +the column. This is passed to \code{haven::write} to note the format. +\item Variable Name - passed as the 'xportr.variable_name' option. Default: +"variable". This is used to match columns in '.df' argument and the +metadata. +} } + \examples{ adsl <- data.frame( USUBJID = c(1001, 1002, 1003), BRTHDT = c(1, 1, 2) ) -metacore <- data.frame( +metadata <- data.frame( dataset = c("adsl", "adsl"), variable = c("USUBJID", "BRTHDT"), format = c(NA, "DATE9.") ) -adsl <- xportr_format(adsl, metacore) -} -\seealso{ -\code{\link[=xportr_label]{xportr_label()}}, \code{\link[=xportr_df_label]{xportr_df_label()}} and \code{\link[=xportr_length]{xportr_length()}} - -Other metadata functions: -\code{\link{xportr_df_label}()}, -\code{\link{xportr_label}()}, -\code{\link{xportr_length}()} +adsl <- xportr_format(adsl, metadata) } -\concept{metadata functions} diff --git a/man/xportr_label.Rd b/man/xportr_label.Rd index de39a76d..a74137ed 100644 --- a/man/xportr_label.Rd +++ b/man/xportr_label.Rd @@ -6,28 +6,70 @@ \usage{ xportr_label( .df, - metacore, + metadata = NULL, domain = NULL, - verbose = getOption("xportr.label_verbose", "none") + verbose = getOption("xportr.label_verbose", "none"), + metacore = deprecated() ) } \arguments{ \item{.df}{A data frame of CDISC standard.} -\item{metacore}{A data frame containing variable level metadata.} +\item{metadata}{A data frame containing variable level metadata. See +'Metadata' section for details.} -\item{domain}{A character value to subset the \code{.df}. If \code{NULL}(default), uses -\code{.df} value as a subset condition.} +\item{domain}{Appropriate CDSIC dataset name, e.g. ADAE, DM. Used to subset +the metadata object. If none is passed, then name of the dataset passed as +.df will be used.} -\item{verbose}{The action the function takes when a variable length isn't -Found. Options are 'stop', 'warn', 'message', and 'none'} +\item{verbose}{The action this function takes when an action is taken on the +dataset or function validation finds an issue. See 'Messaging' section for +details. Options are 'stop', 'warn', 'message', and 'none'} + +\item{metacore}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Previously used to pass +metadata now renamed with \code{metadata}} } \value{ Data frame with label attributes for each variable. } \description{ Assigns variable label from a variable level metadata to a given data frame. +This function will give detect if a label is greater than +40 characters which isn't allowed in XPT v5. If labels aren't present for the +variable it will be assigned an empty character value. Labels are stored in +the 'label' attribute of the column. +} +\section{Messaging}{ + \code{label_log()} is the primary messaging tool for +\code{xportr_label()}. If there are any columns present in the '.df' that are not +noted in the metadata, they cannot be assigned a label and a message will +be generated noting the number or variables that have not been assigned a +label. + +If variables were not found in the metadata and the value passed to the +'verbose' argument is 'stop', 'warn', or 'message', a message will be +generated detailing the variables that were missing in metadata. +} + +\section{Metadata}{ + The argument passed in the 'metadata' argument can either +be a metacore object, or a data.frame containing the data listed below. If +metacore is used, no changes to options are required. + +For data.frame 'metadata' arguments three columns must be present: +\enumerate{ +\item Domain Name - passed as the 'xportr.domain_name' option. Default: +"dataset". This is the column subset by the 'domain' argument in the +function. +\item Variable Name - passed as the 'xportr.variable_name' option. +Default: "variable". This is used to match columns in '.df' argument and +the metadata. +\item Variable Label - passed as the 'xportr.label' option. +Default: "label". These character values to update the 'label' attribute of +the column. This is passed to \code{haven::write} to note the label. +} } + \examples{ adsl <- data.frame( USUBJID = c(1001, 1002, 1003), @@ -36,20 +78,11 @@ adsl <- data.frame( SEX = c("M", "F", "M") ) -metacore <- data.frame( +metadata <- data.frame( dataset = "adsl", variable = c("USUBJID", "SITEID", "AGE", "SEX"), label = c("Unique Subject Identifier", "Study Site Identifier", "Age", "Sex") ) -adsl <- xportr_label(adsl, metacore) -} -\seealso{ -\code{\link[=xportr_df_label]{xportr_df_label()}}, \code{\link[=xportr_format]{xportr_format()}} and \code{\link[=xportr_length]{xportr_length()}} - -Other metadata functions: -\code{\link{xportr_df_label}()}, -\code{\link{xportr_format}()}, -\code{\link{xportr_length}()} +adsl <- xportr_label(adsl, metadata) } -\concept{metadata functions} diff --git a/man/xportr_length.Rd b/man/xportr_length.Rd index 765c31d3..89fb5703 100644 --- a/man/xportr_length.Rd +++ b/man/xportr_length.Rd @@ -6,48 +6,80 @@ \usage{ xportr_length( .df, - metacore, + metadata = NULL, domain = NULL, - verbose = getOption("xportr.length_verbose", "none") + verbose = getOption("xportr.length_verbose", "none"), + metacore = deprecated() ) } \arguments{ \item{.df}{A data frame of CDISC standard.} -\item{metacore}{A data frame containing variable level metadata.} +\item{metadata}{A data frame containing variable level metadata. See +'Metadata' section for details.} -\item{domain}{A character value to subset the \code{.df}. If \code{NULL}(default), uses -\code{.df} value as a subset condition.} +\item{domain}{Appropriate CDSIC dataset name, e.g. ADAE, DM. Used to subset +the metadata object. If none is passed, then name of the dataset passed as +.df will be used.} -\item{verbose}{The action the function takes when a length isn't found in -metadata. Options are 'stop', 'warn', 'message', and 'none'} +\item{verbose}{The action this function takes when an action is taken on the +dataset or function validation finds an issue. See 'Messaging' section for +details. Options are 'stop', 'warn', 'message', and 'none'} + +\item{metacore}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Previously used to pass +metadata now renamed with \code{metadata}} } \value{ Data frame with \code{SASlength} attributes for each variable. } \description{ -Assigns SAS length from a variable level metadata to a given data frame. +Assigns SAS length from a metadata object to a given data frame. If a +length isn't present for a variable the length value is set to 200 for +character columns, and 8 for non-character columns. This value is stored in +the 'width' attribute of the column. +} +\section{Messaging}{ + \code{length_log} is the primary messaging tool for +\code{xportr_length}. If there are any columns present in the '.df' that are not +noted in the metadata, they cannot be assigned a length and a message will +be generated noting the number or variables that have not been assigned a +length. + +If variables were not found in the metadata and the value passed to the +'verbose' argument is 'stop', 'warn', or 'message', a message will be +generated detailing the variables that were missing in the metadata. +} + +\section{Metadata}{ + The argument passed in the 'metadata' argument can either +be a \code{{metacore}} object, or a data.frame containing the data listed below. If +metacore is used, no changes to options are required. + +For data.frame 'metadata' arguments three columns must be present: +\enumerate{ +\item Domain Name - passed as the 'xportr.domain_name' option. Default: +"dataset". This is the column subset by the 'domain' argument in the +function. +\item Variable Name - passed as the 'xportr.variable_name' option. +Default: "variable". This is used to match columns in '.df' argument and +the metadata. +\item Variable Label - passed as the 'xportr.length' option. +Default: "length". These numeric values to update the 'width' attribute of +the column. This is passed to \code{haven::write} to note the variable length. +} } + \examples{ adsl <- data.frame( USUBJID = c(1001, 1002, 1003), BRTHDT = c(1, 1, 2) ) -metacore <- data.frame( +metadata <- data.frame( dataset = c("adsl", "adsl"), variable = c("USUBJID", "BRTHDT"), length = c(10, 8) ) -adsl <- xportr_length(adsl, metacore) -} -\seealso{ -\code{\link[=xportr_label]{xportr_label()}}, \code{\link[=xportr_df_label]{xportr_df_label()}} and \code{\link[=xportr_format]{xportr_format()}} - -Other metadata functions: -\code{\link{xportr_df_label}()}, -\code{\link{xportr_format}()}, -\code{\link{xportr_label}()} +adsl <- xportr_length(adsl, metadata) } -\concept{metadata functions} diff --git a/man/xportr_metadata.Rd b/man/xportr_metadata.Rd new file mode 100644 index 00000000..592c6f45 --- /dev/null +++ b/man/xportr_metadata.Rd @@ -0,0 +1,55 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/metadata.R +\name{xportr_metadata} +\alias{xportr_metadata} +\title{Set variable specifications and domain} +\usage{ +xportr_metadata(.df, metadata, domain = NULL) +} +\arguments{ +\item{.df}{A data frame of CDISC standard.} + +\item{metadata}{A data frame containing variable level metadata. See +'Metadata' section for details.} + +\item{domain}{Appropriate CDSIC dataset name, e.g. ADAE, DM. Used to subset +the metadata object. If none is passed, then name of the dataset passed as +.df will be used.} +} +\value{ +\code{.df} dataset with metadata and domain attributes set +} +\description{ +Sets metadata for a dataset in a way that can be accessed by other xportr +functions. If used at the start of an xportr pipeline, it removes the need to +set metadata and domain at each step individually. For details on the format +of the metadata, see the 'Metadata' section for each function in question. +} +\examples{ + +metadata <- data.frame( + dataset = "test", + variable = c("Subj", "Param", "Val", "NotUsed"), + type = c("numeric", "character", "numeric", "character"), + format = NA, + order = c(1, 3, 4, 2) +) + +adlb <- data.frame( + Subj = as.character(123, 456, 789), + Different = c("a", "b", "c"), + Val = c("1", "2", "3"), + Param = c("param1", "param2", "param3") +) + +xportr_metadata(adlb, metadata, "test") + +if (rlang::is_installed("magrittr")) { + library(magrittr) + + adlb \%>\% + xportr_metadata(metadata, "test") \%>\% + xportr_type() \%>\% + xportr_order() +} +} diff --git a/man/xportr_order.Rd b/man/xportr_order.Rd index e0e9ceec..e8ea269c 100644 --- a/man/xportr_order.Rd +++ b/man/xportr_order.Rd @@ -6,24 +6,87 @@ \usage{ xportr_order( .df, - metacore, + metadata = NULL, domain = NULL, - verbose = getOption("xportr.order_verbose", "none") + verbose = getOption("xportr.order_verbose", "none"), + metacore = deprecated() ) } \arguments{ \item{.df}{A data frame of CDISC standard.} -\item{metacore}{A data frame containing variable level metadata.} +\item{metadata}{A data frame containing variable level metadata. See +'Metadata' section for details.} -\item{domain}{A character value to subset the \code{.df}. If \code{NULL}(default), uses -\code{.df} value as a subset condition.} +\item{domain}{Appropriate CDSIC dataset name, e.g. ADAE, DM. Used to subset +the metadata object. If none is passed, then name of the dataset passed as +.df will be used.} -\item{verbose}{Option for messaging order results} +\item{verbose}{The action this function takes when an action is taken on the +dataset or function validation finds an issue. See 'Messaging' section for +details. Options are 'stop', 'warn', 'message', and 'none'} + +\item{metacore}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Previously used to pass +metadata now renamed with \code{metadata}} } \value{ Dataframe that has been re-ordered according to spec } \description{ -Order variables of a dataset according to Spec +The \code{dplyr::arrange()} function is used to order the columns of the dataframe. +Any variables that are missing an order value are appended to the end of the dataframe +after all of the variables that have an order. +} +\section{Messaging}{ + \code{var_ord_msg()} is the primary messaging tool for +\code{xportr_order()}. There are two primary messages that are output from +\code{var_ord_msg()}. The first is the "moved" variables. These are the variables +that were not found in the metadata file and moved to the end of the +dataset. A message will be generated noting the number, if any, of +variables that were moved to the end of the dataset. If any variables were +moved, and the 'verbose' argument is 'stop', 'warn', or 'message', a +message will be generated detailing the variables that were moved. + +The second primary message is the number of variables that were in the +dataset, but not in the correct order. A message will be generated noting +the number, if any, of variables that have been reordered. If any variables +were reordered, and the 'verbose' argument is 'stop', 'warn', or 'message', +a message will be generated detailing the variables that were reordered. +} + +\section{Metadata}{ + The argument passed in the 'metadata' argument can either +be a metacore object, or a data.frame containing the data listed below. If +metacore is used, no changes to options are required. + +For data.frame 'metadata' arguments three columns must be present: +\enumerate{ +\item Domain Name - passed as the 'xportr.domain_name' option. Default: +"dataset". This is the column subset by the 'domain' argument in the +function. +\item Variable Name - passed as the 'xportr.variable_name' option. +Default: "variable". This is used to match columns in '.df' argument and +the metadata. +\item Variable Order - passed as the 'xportr.order_name' option. +Default: "order". These values used to arrange the order of the variables. +If the values of order metadata are not numeric, they will be corsersed to +prevent alphabetical sorting of numberic values. +} +} + +\examples{ +adsl <- data.frame( + BRTHDT = c(1, 1, 2), + STUDYID = c("mid987650", "mid987650", "mid987650"), + TRT01A = c("Active", "Active", "Placebo"), + USUBJID = c(1001, 1002, 1003) +) + +metadata <- data.frame( + dataset = c("adsl", "adsl", "adsl", "adsl"), + variable = c("STUDYID", "USUBJID", "TRT01A", "BRTHDT"), + order = 1:4 +) + +adsl <- xportr_order(adsl, metadata) } diff --git a/man/xportr_type.Rd b/man/xportr_type.Rd index 979028e3..abfa41d8 100644 --- a/man/xportr_type.Rd +++ b/man/xportr_type.Rd @@ -6,45 +6,103 @@ \usage{ xportr_type( .df, - metacore, + metadata = NULL, domain = NULL, - verbose = getOption("xportr.type_verbose", "none") + verbose = getOption("xportr.type_verbose", "none"), + metacore = deprecated() ) } \arguments{ -\item{.df}{An R object with columns that can be coerced} +\item{.df}{A data frame of CDISC standard.} -\item{metacore}{Either a data.frame that has the names of all possible columns -and their types, or a \code{Metacore} object from the \code{Metacore} package. Required -column names are dataset, variables, type} +\item{metadata}{A data frame containing variable level metadata. See +'Metadata' section for details.} -\item{domain}{Name of the dataset. Ex ADAE/DM. This will be used to subset -the metacore object. If none is passed it is assumed to be the name of the -dataset passed in \code{.df}.} +\item{domain}{Appropriate CDSIC dataset name, e.g. ADAE, DM. Used to subset +the metadata object. If none is passed, then name of the dataset passed as +.df will be used.} -\item{verbose}{The action the function takes when a variable isn't typed -properly. Options are 'stop', 'warn', 'message', and 'none'} +\item{verbose}{The action this function takes when an action is taken on the +dataset or function validation finds an issue. See 'Messaging' section for +details. Options are 'stop', 'warn', 'message', and 'none'} + +\item{metacore}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Previously used to pass +metadata now renamed with \code{metadata}} } \value{ Returns the modified table. } \description{ -Current assumptions: -columns_meta is a data.frame with names "Variables", "Type" +XPT v5 datasets only have data types of character and numeric. \code{xportr_type} +attempts to collapse R classes to those two XPT types. The +'xportr.character_types' option is used to explicitly collapse the class of a +column to character using \code{as.character}. Similarly, 'xportr.numeric_types' +will collapse a column to a numeric type. If no type is passed for a +variable and it isn't identifed as a timing variable, it is assumed to be numeric and coerced with \code{as.numeric}. +} +\details{ +Certain care should be taken when using timing variables. R serializes dates +based on a reference date of 01/01/1970 where XPT uses 01/01/1960. This can +result in dates being 10 years off when outputting from R to XPT if you're +using a date class. For this reason, \code{xportr} will try to determine what +should happen with variables that appear to be used to denote time. + +For variables that end in DT, DTM, or, TM, if they are not explicitly noted +in 'xportr.numeric_types' or 'xportr.character_types', they are coerced to +numeric results. } +\section{Messaging}{ + \code{type_log()} is the primary messaging tool for +\code{xportr_type()}. The number of column types that mismatch the reported type +in the metadata, if any, is reported by by \code{xportr_type()}. If there are any +type mismatches, and the 'verbose' argument is 'stop', 'warn', or +'message', each mismatch will be detailed with the actual type in the data +and the type noted in the metadata. +} + +\section{Metadata}{ + The argument passed in the 'metadata' argument can either +be a metacore object, or a data.frame containing the data listed below. If +metacore is used, no changes to options are required. + +For data.frame 'metadata' arguments four columns must be present: +\enumerate{ +\item Domain Name - passed as the 'xportr.domain_name' option. Default: +"dataset". This is the column subset by the 'domain' argument in the +function. +\item Format Name - passed as the 'xportr.format_name' option. Default: +"format". Character values to update the 'format.sas' attribute of the +column. This is passed to \code{haven::write} to note the format. +\item Variable Name - passed as the 'xportr.variable_name' option. Default: +"variable". This is used to match columns in '.df' argument and the +metadata. +\item Variable Type - passed as the 'xportr.type_name'. Default: "type". This +is used to note the XPT variable "type" options are numeric or character. +\item (Option only) Character Types - The list of classes that should be +explicitly coerced to a XPT Character type. Default: c( "character", +"char", "text", "date", "posixct", "posixt", "datetime", "time", +"partialdate", "partialtime", "partialdatetime", "incompletedatetime", +"durationdatetime", "intervaldatetime") +\item (Option only) Numeric Types - The list of classes that should be +explicitly coerced to a XPT numeric type. Default: c("integer", "numeric", +"num", "float") +} +} + \examples{ -metacore <- data.frame( +metadata <- data.frame( dataset = "test", variable = c("Subj", "Param", "Val", "NotUsed"), - type = c("numeric", "character", "numeric", "character") + type = c("numeric", "character", "numeric", "character"), + format = NA ) .df <- data.frame( - Subj = as.character(123, 456, 789), - Different = c("a", "b", "c"), - Val = c("1", "2", "3"), - Param = c("param1", "param2", "param3") + Subj = as.character(123, 456, 789), + Different = c("a", "b", "c"), + Val = c("1", "2", "3"), + Param = c("param1", "param2", "param3") ) -df2 <- xportr_type(.df, metacore, "test") +df2 <- xportr_type(.df, metadata, "test") } diff --git a/man/xportr_write.Rd b/man/xportr_write.Rd index ee82ac64..f1b89fc9 100644 --- a/man/xportr_write.Rd +++ b/man/xportr_write.Rd @@ -4,7 +4,7 @@ \alias{xportr_write} \title{Write xpt v5 transport file} \usage{ -xportr_write(.df, path, label = NULL) +xportr_write(.df, path, label = NULL, strict_checks = FALSE) } \arguments{ \item{.df}{A data frame to write.} @@ -12,7 +12,11 @@ xportr_write(.df, path, label = NULL) \item{path}{Path where transport file will be written. File name sans will be used as \code{xpt} name.} -\item{label}{Dataset label. It must be<=40 characters.} +\item{label}{Dataset label. It must be <=40 characters.} + +\item{strict_checks}{If TRUE, xpt validation will report errors and not write +out the dataset. If FALSE, xpt validation will report warnings and continue +with writing out the dataset. Defaults to FALSE} } \value{ A data frame. \code{xportr_write()} returns the input data invisibly. @@ -30,3 +34,18 @@ to the FDA. \item SAS type are stored in the "SAStype" attribute. } } +\examples{ +adsl <- data.frame( + Subj = as.character(123, 456, 789), + Different = c("a", "b", "c"), + Val = c("1", "2", "3"), + Param = c("param1", "param2", "param3") +) + +xportr_write(adsl, + path = paste0(tempdir(), "/adsl.xpt"), + label = "Subject-Level Analysis", + strict_checks = FALSE +) + +} diff --git a/renv.lock b/renv.lock deleted file mode 100644 index 486a5508..00000000 --- a/renv.lock +++ /dev/null @@ -1,1171 +0,0 @@ -{ - "R": { - "Version": "4.1.3", - "Repositories": [ - { - "Name": "CRAN", - "URL": "https://cran.rstudio.com" - } - ] - }, - "Packages": { - "BH": { - "Package": "BH", - "Version": "1.78.0-0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "4e348572ffcaa2fb1e610e7a941f6f3a", - "Requirements": [] - }, - "DT": { - "Package": "DT", - "Version": "0.22", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "fc53164de9ee32692eef6fdd14115381", - "Requirements": [ - "crosstalk", - "htmltools", - "htmlwidgets", - "jquerylib", - "jsonlite", - "magrittr", - "promises" - ] - }, - "NLP": { - "Package": "NLP", - "Version": "0.2-1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "94b0a21c13933f61cf6272a192964624", - "Requirements": [] - }, - "R6": { - "Package": "R6", - "Version": "2.5.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "470851b6d5d0ac559e9d01bb352b4021", - "Requirements": [] - }, - "Rcpp": { - "Package": "Rcpp", - "Version": "1.0.8.3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "32e79b908fda56ee57fe518a8d37b864", - "Requirements": [] - }, - "admiral": { - "Package": "admiral", - "Version": "0.7.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "677963ed23698fabf4b9577c89c55266", - "Requirements": [ - "assertthat", - "dplyr", - "hms", - "lifecycle", - "lubridate", - "magrittr", - "purrr", - "rlang", - "stringr", - "tidyr", - "tidyselect" - ] - }, - "askpass": { - "Package": "askpass", - "Version": "1.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "e8a22846fff485f0be3770c2da758713", - "Requirements": [ - "sys" - ] - }, - "assertthat": { - "Package": "assertthat", - "Version": "0.2.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "50c838a310445e954bc13f26f26a6ecf", - "Requirements": [] - }, - "base64enc": { - "Package": "base64enc", - "Version": "0.1-3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "543776ae6848fde2f48ff3816d0628bc", - "Requirements": [] - }, - "bit": { - "Package": "bit", - "Version": "4.0.4", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "f36715f14d94678eea9933af927bc15d", - "Requirements": [] - }, - "bit64": { - "Package": "bit64", - "Version": "4.0.5", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "9fe98599ca456d6552421db0d6772d8f", - "Requirements": [ - "bit" - ] - }, - "brio": { - "Package": "brio", - "Version": "1.1.3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "976cf154dfb043c012d87cddd8bca363", - "Requirements": [] - }, - "bslib": { - "Package": "bslib", - "Version": "0.3.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "56ae7e1987b340186a8a5a157c2ec358", - "Requirements": [ - "htmltools", - "jquerylib", - "jsonlite", - "rlang", - "sass" - ] - }, - "cachem": { - "Package": "cachem", - "Version": "1.0.6", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "648c5b3d71e6a37e3043617489a0a0e9", - "Requirements": [ - "fastmap", - "rlang" - ] - }, - "callr": { - "Package": "callr", - "Version": "3.7.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "461aa75a11ce2400245190ef5d3995df", - "Requirements": [ - "R6", - "processx" - ] - }, - "cellranger": { - "Package": "cellranger", - "Version": "1.1.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "f61dbaec772ccd2e17705c1e872e9e7c", - "Requirements": [ - "rematch", - "tibble" - ] - }, - "cli": { - "Package": "cli", - "Version": "3.2.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "1bdb126893e9ce6aae50ad1d6fc32faf", - "Requirements": [ - "glue" - ] - }, - "clipr": { - "Package": "clipr", - "Version": "0.8.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "3f038e5ac7f41d4ac41ce658c85e3042", - "Requirements": [] - }, - "cpp11": { - "Package": "cpp11", - "Version": "0.4.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "fa53ce256cd280f468c080a58ea5ba8c", - "Requirements": [] - }, - "crayon": { - "Package": "crayon", - "Version": "1.5.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "8dc45fd8a1ee067a92b85ef274e66d6a", - "Requirements": [] - }, - "crosstalk": { - "Package": "crosstalk", - "Version": "1.2.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "6aa54f69598c32177e920eb3402e8293", - "Requirements": [ - "R6", - "htmltools", - "jsonlite", - "lazyeval" - ] - }, - "curl": { - "Package": "curl", - "Version": "4.3.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "022c42d49c28e95d69ca60446dbabf88", - "Requirements": [] - }, - "desc": { - "Package": "desc", - "Version": "1.4.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "eebd27ee58fcc58714eedb7aa07d8ad1", - "Requirements": [ - "R6", - "cli", - "rprojroot" - ] - }, - "diffobj": { - "Package": "diffobj", - "Version": "0.3.5", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "bcaa8b95f8d7d01a5dedfd959ce88ab8", - "Requirements": [ - "crayon" - ] - }, - "digest": { - "Package": "digest", - "Version": "0.6.29", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "cf6b206a045a684728c3267ef7596190", - "Requirements": [] - }, - "downlit": { - "Package": "downlit", - "Version": "0.4.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "ba63dc9ab5a31f3209892437e40c5f60", - "Requirements": [ - "brio", - "desc", - "digest", - "evaluate", - "fansi", - "memoise", - "rlang", - "vctrs", - "yaml" - ] - }, - "dplyr": { - "Package": "dplyr", - "Version": "1.0.8", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "ef47665e64228a17609d6df877bf86f2", - "Requirements": [ - "R6", - "generics", - "glue", - "lifecycle", - "magrittr", - "pillar", - "rlang", - "tibble", - "tidyselect", - "vctrs" - ] - }, - "ellipsis": { - "Package": "ellipsis", - "Version": "0.3.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077", - "Requirements": [ - "rlang" - ] - }, - "evaluate": { - "Package": "evaluate", - "Version": "0.15", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "699a7a93d08c962d9f8950b2d7a227f1", - "Requirements": [] - }, - "fansi": { - "Package": "fansi", - "Version": "1.0.3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "83a8afdbe71839506baa9f90eebad7ec", - "Requirements": [] - }, - "fastmap": { - "Package": "fastmap", - "Version": "1.1.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "77bd60a6157420d4ffa93b27cf6a58b8", - "Requirements": [] - }, - "fontawesome": { - "Package": "fontawesome", - "Version": "0.2.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "55624ed409e46c5f358b2c060be87f67", - "Requirements": [ - "htmltools", - "rlang" - ] - }, - "forcats": { - "Package": "forcats", - "Version": "0.5.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "81c3244cab67468aac4c60550832655d", - "Requirements": [ - "ellipsis", - "magrittr", - "rlang", - "tibble" - ] - }, - "fs": { - "Package": "fs", - "Version": "1.5.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "7c89603d81793f0d5486d91ab1fc6f1d", - "Requirements": [] - }, - "generics": { - "Package": "generics", - "Version": "0.1.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "177475892cf4a55865868527654a7741", - "Requirements": [] - }, - "glue": { - "Package": "glue", - "Version": "1.6.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "4f2596dfb05dac67b9dc558e5c6fba2e", - "Requirements": [] - }, - "haven": { - "Package": "haven", - "Version": "2.5.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "e3058e4ac77f4fa686f68a1838d5b715", - "Requirements": [ - "cli", - "cpp11", - "forcats", - "hms", - "lifecycle", - "readr", - "rlang", - "tibble", - "tidyselect", - "vctrs" - ] - }, - "highr": { - "Package": "highr", - "Version": "0.9", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "8eb36c8125038e648e5d111c0d7b2ed4", - "Requirements": [ - "xfun" - ] - }, - "hms": { - "Package": "hms", - "Version": "1.1.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "5b8a2dd0fdbe2ab4f6081e6c7be6dfca", - "Requirements": [ - "ellipsis", - "lifecycle", - "pkgconfig", - "rlang", - "vctrs" - ] - }, - "htmltools": { - "Package": "htmltools", - "Version": "0.5.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "526c484233f42522278ab06fb185cb26", - "Requirements": [ - "base64enc", - "digest", - "fastmap", - "rlang" - ] - }, - "htmlwidgets": { - "Package": "htmlwidgets", - "Version": "1.5.4", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "76147821cd3fcd8c4b04e1ef0498e7fb", - "Requirements": [ - "htmltools", - "jsonlite", - "yaml" - ] - }, - "httr": { - "Package": "httr", - "Version": "1.4.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "a525aba14184fec243f9eaec62fbed43", - "Requirements": [ - "R6", - "curl", - "jsonlite", - "mime", - "openssl" - ] - }, - "janitor": { - "Package": "janitor", - "Version": "2.1.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "6de84a8c67fb247e721166049c84695f", - "Requirements": [ - "dplyr", - "lifecycle", - "lubridate", - "magrittr", - "purrr", - "rlang", - "snakecase", - "stringi", - "stringr", - "tidyr", - "tidyselect" - ] - }, - "jquerylib": { - "Package": "jquerylib", - "Version": "0.1.4", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "5aab57a3bd297eee1c1d862735972182", - "Requirements": [ - "htmltools" - ] - }, - "jsonlite": { - "Package": "jsonlite", - "Version": "1.8.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "d07e729b27b372429d42d24d503613a0", - "Requirements": [] - }, - "knitr": { - "Package": "knitr", - "Version": "1.38", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "10b3dc3c6acb925910edda5d0543b3a2", - "Requirements": [ - "evaluate", - "highr", - "stringr", - "xfun", - "yaml" - ] - }, - "labelled": { - "Package": "labelled", - "Version": "2.9.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "d3eb15eccdf131238f2184eed2a7fa9c", - "Requirements": [ - "dplyr", - "haven", - "lifecycle", - "rlang", - "stringr", - "tidyr", - "vctrs" - ] - }, - "later": { - "Package": "later", - "Version": "1.3.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "7e7b457d7766bc47f2a5f21cc2984f8e", - "Requirements": [ - "Rcpp", - "rlang" - ] - }, - "lazyeval": { - "Package": "lazyeval", - "Version": "0.2.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "d908914ae53b04d4c0c0fd72ecc35370", - "Requirements": [] - }, - "lifecycle": { - "Package": "lifecycle", - "Version": "1.0.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "a6b6d352e3ed897373ab19d8395c98d0", - "Requirements": [ - "glue", - "rlang" - ] - }, - "lubridate": { - "Package": "lubridate", - "Version": "1.8.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "2ff5eedb6ee38fb1b81205c73be1be5a", - "Requirements": [ - "cpp11", - "generics" - ] - }, - "magrittr": { - "Package": "magrittr", - "Version": "2.0.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "cdc87ecd81934679d1557633d8e1fe51", - "Requirements": [] - }, - "memoise": { - "Package": "memoise", - "Version": "2.0.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "e2817ccf4a065c5d9d7f2cfbe7c1d78c", - "Requirements": [ - "cachem", - "rlang" - ] - }, - "mime": { - "Package": "mime", - "Version": "0.12", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "18e9c28c1d3ca1560ce30658b22ce104", - "Requirements": [] - }, - "openssl": { - "Package": "openssl", - "Version": "2.0.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "cf4329aac12c2c44089974559c18e446", - "Requirements": [ - "askpass" - ] - }, - "pillar": { - "Package": "pillar", - "Version": "1.7.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "51dfc97e1b7069e9f7e6f83f3589c22e", - "Requirements": [ - "cli", - "crayon", - "ellipsis", - "fansi", - "glue", - "lifecycle", - "rlang", - "utf8", - "vctrs" - ] - }, - "pkgconfig": { - "Package": "pkgconfig", - "Version": "2.0.3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "01f28d4278f15c76cddbea05899c5d6f", - "Requirements": [] - }, - "pkgdown": { - "Package": "pkgdown", - "Version": "2.0.3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "ec3139021900fa27faae7a821b732bf8", - "Requirements": [ - "bslib", - "callr", - "crayon", - "desc", - "digest", - "downlit", - "fs", - "httr", - "jsonlite", - "magrittr", - "memoise", - "purrr", - "ragg", - "rlang", - "rmarkdown", - "tibble", - "whisker", - "withr", - "xml2", - "yaml" - ] - }, - "pkgload": { - "Package": "pkgload", - "Version": "1.2.4", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "7533cd805940821bf23eaf3c8d4c1735", - "Requirements": [ - "cli", - "crayon", - "desc", - "rlang", - "rprojroot", - "rstudioapi", - "withr" - ] - }, - "praise": { - "Package": "praise", - "Version": "1.0.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "a555924add98c99d2f411e37e7d25e9f", - "Requirements": [] - }, - "prettyunits": { - "Package": "prettyunits", - "Version": "1.1.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "95ef9167b75dde9d2ccc3c7528393e7e", - "Requirements": [] - }, - "processx": { - "Package": "processx", - "Version": "3.5.3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "8bbae1a548d0d3fdf6647bdd9d35bf6d", - "Requirements": [ - "R6", - "ps" - ] - }, - "progress": { - "Package": "progress", - "Version": "1.2.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "14dc9f7a3c91ebb14ec5bb9208a07061", - "Requirements": [ - "R6", - "crayon", - "hms", - "prettyunits" - ] - }, - "promises": { - "Package": "promises", - "Version": "1.2.0.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "4ab2c43adb4d4699cf3690acd378d75d", - "Requirements": [ - "R6", - "Rcpp", - "later", - "magrittr", - "rlang" - ] - }, - "ps": { - "Package": "ps", - "Version": "1.6.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "32620e2001c1dce1af49c49dccbb9420", - "Requirements": [] - }, - "purrr": { - "Package": "purrr", - "Version": "0.3.4", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "97def703420c8ab10d8f0e6c72101e02", - "Requirements": [ - "magrittr", - "rlang" - ] - }, - "ragg": { - "Package": "ragg", - "Version": "1.2.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "14932bb6f2739c771ca4ceaba6b4248e", - "Requirements": [ - "systemfonts", - "textshaping" - ] - }, - "rappdirs": { - "Package": "rappdirs", - "Version": "0.3.3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "5e3c5dc0b071b21fa128676560dbe94d", - "Requirements": [] - }, - "readr": { - "Package": "readr", - "Version": "2.1.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "9c59de1357dc209868b5feb5c9f0fe2f", - "Requirements": [ - "R6", - "cli", - "clipr", - "cpp11", - "crayon", - "hms", - "lifecycle", - "rlang", - "tibble", - "tzdb", - "vroom" - ] - }, - "readxl": { - "Package": "readxl", - "Version": "1.4.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "170c35f745563bb307e963bde0197e4f", - "Requirements": [ - "cellranger", - "cpp11", - "progress", - "tibble" - ] - }, - "rematch": { - "Package": "rematch", - "Version": "1.0.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "c66b930d20bb6d858cd18e1cebcfae5c", - "Requirements": [] - }, - "rematch2": { - "Package": "rematch2", - "Version": "2.1.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "76c9e04c712a05848ae7a23d2f170a40", - "Requirements": [ - "tibble" - ] - }, - "renv": { - "Package": "renv", - "Version": "0.15.5", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "6a38294e7d12f5d8e656b08c5bd8ae34", - "Requirements": [] - }, - "rlang": { - "Package": "rlang", - "Version": "1.0.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "04884d9a75d778aca22c7154b8333ec9", - "Requirements": [] - }, - "rmarkdown": { - "Package": "rmarkdown", - "Version": "2.13", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "ac78f4d2e0289d4cba73b88af567b8b1", - "Requirements": [ - "bslib", - "evaluate", - "htmltools", - "jquerylib", - "jsonlite", - "knitr", - "stringr", - "tinytex", - "xfun", - "yaml" - ] - }, - "rprojroot": { - "Package": "rprojroot", - "Version": "2.0.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "249d8cd1e74a8f6a26194a91b47f21d1", - "Requirements": [] - }, - "rstudioapi": { - "Package": "rstudioapi", - "Version": "0.13", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "06c85365a03fdaf699966cc1d3cf53ea", - "Requirements": [] - }, - "sass": { - "Package": "sass", - "Version": "0.4.1", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "f37c0028d720bab3c513fd65d28c7234", - "Requirements": [ - "R6", - "fs", - "htmltools", - "rappdirs", - "rlang" - ] - }, - "slam": { - "Package": "slam", - "Version": "0.1-50", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "e25793551cbdb843154152e5ee88cbd6", - "Requirements": [] - }, - "snakecase": { - "Package": "snakecase", - "Version": "0.11.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "4079070fc210c7901c0832a3aeab894f", - "Requirements": [ - "stringi", - "stringr" - ] - }, - "stringi": { - "Package": "stringi", - "Version": "1.7.6", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "bba431031d30789535745a9627ac9271", - "Requirements": [] - }, - "stringr": { - "Package": "stringr", - "Version": "1.4.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "0759e6b6c0957edb1311028a49a35e76", - "Requirements": [ - "glue", - "magrittr", - "stringi" - ] - }, - "sys": { - "Package": "sys", - "Version": "3.4", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "b227d13e29222b4574486cfcbde077fa", - "Requirements": [] - }, - "systemfonts": { - "Package": "systemfonts", - "Version": "1.0.4", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "90b28393209827327de889f49935140a", - "Requirements": [ - "cpp11" - ] - }, - "testthat": { - "Package": "testthat", - "Version": "3.1.3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "affcf9db2c99dd2c7e6459ef55ed3385", - "Requirements": [ - "R6", - "brio", - "callr", - "cli", - "crayon", - "desc", - "digest", - "ellipsis", - "evaluate", - "jsonlite", - "lifecycle", - "magrittr", - "pkgload", - "praise", - "processx", - "ps", - "rlang", - "waldo", - "withr" - ] - }, - "textshaping": { - "Package": "textshaping", - "Version": "0.3.6", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "1ab6223d3670fac7143202cb6a2d43d5", - "Requirements": [ - "cpp11", - "systemfonts" - ] - }, - "tibble": { - "Package": "tibble", - "Version": "3.1.6", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "8a8f02d1934dfd6431c671361510dd0b", - "Requirements": [ - "ellipsis", - "fansi", - "lifecycle", - "magrittr", - "pillar", - "pkgconfig", - "rlang", - "vctrs" - ] - }, - "tidyr": { - "Package": "tidyr", - "Version": "1.2.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "d8b95b7fee945d7da6888cf7eb71a49c", - "Requirements": [ - "cpp11", - "dplyr", - "ellipsis", - "glue", - "lifecycle", - "magrittr", - "purrr", - "rlang", - "tibble", - "tidyselect", - "vctrs" - ] - }, - "tidyselect": { - "Package": "tidyselect", - "Version": "1.1.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "17f6da8cfd7002760a859915ce7eef8f", - "Requirements": [ - "ellipsis", - "glue", - "purrr", - "rlang", - "vctrs" - ] - }, - "tinytex": { - "Package": "tinytex", - "Version": "0.38", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "759d047596ac173433985deddf313450", - "Requirements": [ - "xfun" - ] - }, - "tm": { - "Package": "tm", - "Version": "0.7-8", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "608413cbdf64d418b6a33e6c3d4d3b25", - "Requirements": [ - "BH", - "NLP", - "Rcpp", - "slam", - "xml2" - ] - }, - "tzdb": { - "Package": "tzdb", - "Version": "0.3.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "b2e1cbce7c903eaf23ec05c58e59fb5e", - "Requirements": [ - "cpp11" - ] - }, - "utf8": { - "Package": "utf8", - "Version": "1.2.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "c9c462b759a5cc844ae25b5942654d13", - "Requirements": [] - }, - "vctrs": { - "Package": "vctrs", - "Version": "0.3.8", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "ecf749a1b39ea72bd9b51b76292261f1", - "Requirements": [ - "ellipsis", - "glue", - "rlang" - ] - }, - "vroom": { - "Package": "vroom", - "Version": "1.5.7", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "976507b5a105bc3bdf6a5a5f29e0684f", - "Requirements": [ - "bit64", - "cli", - "cpp11", - "crayon", - "glue", - "hms", - "lifecycle", - "progress", - "rlang", - "tibble", - "tidyselect", - "tzdb", - "vctrs", - "withr" - ] - }, - "waldo": { - "Package": "waldo", - "Version": "0.4.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "035fba89d0c86e2113120f93301b98ad", - "Requirements": [ - "cli", - "diffobj", - "fansi", - "glue", - "rematch2", - "rlang", - "tibble" - ] - }, - "whisker": { - "Package": "whisker", - "Version": "0.4", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "ca970b96d894e90397ed20637a0c1bbe", - "Requirements": [] - }, - "withr": { - "Package": "withr", - "Version": "2.5.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "c0e49a9760983e81e55cdd9be92e7182", - "Requirements": [] - }, - "xfun": { - "Package": "xfun", - "Version": "0.30", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "e83f48136b041845e50a6658feffb197", - "Requirements": [] - }, - "xml2": { - "Package": "xml2", - "Version": "1.3.3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "40682ed6a969ea5abfd351eb67833adc", - "Requirements": [] - }, - "yaml": { - "Package": "yaml", - "Version": "2.3.5", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "458bb38374d73bf83b1bb85e353da200", - "Requirements": [] - } - } -} diff --git a/renv/.gitignore b/renv/.gitignore deleted file mode 100644 index 275e4ca3..00000000 --- a/renv/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -library/ -local/ -cellar/ -lock/ -python/ -staging/ diff --git a/renv/activate.R b/renv/activate.R deleted file mode 100644 index 72c0818a..00000000 --- a/renv/activate.R +++ /dev/null @@ -1,942 +0,0 @@ - -local({ - - # the requested version of renv - version <- "0.15.5" - - # the project directory - project <- getwd() - - # figure out whether the autoloader is enabled - enabled <- local({ - - # first, check config option - override <- getOption("renv.config.autoloader.enabled") - if (!is.null(override)) - return(override) - - # next, check environment variables - # TODO: prefer using the configuration one in the future - envvars <- c( - "RENV_CONFIG_AUTOLOADER_ENABLED", - "RENV_AUTOLOADER_ENABLED", - "RENV_ACTIVATE_PROJECT" - ) - - for (envvar in envvars) { - envval <- Sys.getenv(envvar, unset = NA) - if (!is.na(envval)) - return(tolower(envval) %in% c("true", "t", "1")) - } - - # enable by default - TRUE - - }) - - if (!enabled) - return(FALSE) - - # avoid recursion - if (identical(getOption("renv.autoloader.running"), TRUE)) { - warning("ignoring recursive attempt to run renv autoloader") - return(invisible(TRUE)) - } - - # signal that we're loading renv during R startup - options(renv.autoloader.running = TRUE) - on.exit(options(renv.autoloader.running = NULL), add = TRUE) - - # signal that we've consented to use renv - options(renv.consent = TRUE) - - # load the 'utils' package eagerly -- this ensures that renv shims, which - # mask 'utils' packages, will come first on the search path - library(utils, lib.loc = .Library) - - # unload renv if it's already been loaded - if ("renv" %in% loadedNamespaces()) - unloadNamespace("renv") - - # load bootstrap tools - `%||%` <- function(x, y) { - if (is.environment(x) || length(x)) x else y - } - - bootstrap <- function(version, library) { - - # attempt to download renv - tarball <- tryCatch(renv_bootstrap_download(version), error = identity) - if (inherits(tarball, "error")) - stop("failed to download renv ", version) - - # now attempt to install - status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) - if (inherits(status, "error")) - stop("failed to install renv ", version) - - } - - renv_bootstrap_tests_running <- function() { - getOption("renv.tests.running", default = FALSE) - } - - renv_bootstrap_repos <- function() { - - # check for repos override - repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) - if (!is.na(repos)) - return(repos) - - # check for lockfile repositories - repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) - if (!inherits(repos, "error") && length(repos)) - return(repos) - - # if we're testing, re-use the test repositories - if (renv_bootstrap_tests_running()) - return(getOption("renv.tests.repos")) - - # retrieve current repos - repos <- getOption("repos") - - # ensure @CRAN@ entries are resolved - repos[repos == "@CRAN@"] <- getOption( - "renv.repos.cran", - "https://cloud.r-project.org" - ) - - # add in renv.bootstrap.repos if set - default <- c(FALLBACK = "https://cloud.r-project.org") - extra <- getOption("renv.bootstrap.repos", default = default) - repos <- c(repos, extra) - - # remove duplicates that might've snuck in - dupes <- duplicated(repos) | duplicated(names(repos)) - repos[!dupes] - - } - - renv_bootstrap_repos_lockfile <- function() { - - lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") - if (!file.exists(lockpath)) - return(NULL) - - lockfile <- tryCatch(renv_json_read(lockpath), error = identity) - if (inherits(lockfile, "error")) { - warning(lockfile) - return(NULL) - } - - repos <- lockfile$R$Repositories - if (length(repos) == 0) - return(NULL) - - keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) - vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) - names(vals) <- keys - - return(vals) - - } - - renv_bootstrap_download <- function(version) { - - # if the renv version number has 4 components, assume it must - # be retrieved via github - nv <- numeric_version(version) - components <- unclass(nv)[[1]] - - # if this appears to be a development version of 'renv', we'll - # try to restore from github - dev <- length(components) == 4L - - # begin collecting different methods for finding renv - methods <- c( - renv_bootstrap_download_tarball, - if (dev) - renv_bootstrap_download_github - else c( - renv_bootstrap_download_cran_latest, - renv_bootstrap_download_cran_archive - ) - ) - - for (method in methods) { - path <- tryCatch(method(version), error = identity) - if (is.character(path) && file.exists(path)) - return(path) - } - - stop("failed to download renv ", version) - - } - - renv_bootstrap_download_impl <- function(url, destfile) { - - mode <- "wb" - - # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 - fixup <- - Sys.info()[["sysname"]] == "Windows" && - substring(url, 1L, 5L) == "file:" - - if (fixup) - mode <- "w+b" - - utils::download.file( - url = url, - destfile = destfile, - mode = mode, - quiet = TRUE - ) - - } - - renv_bootstrap_download_cran_latest <- function(version) { - - spec <- renv_bootstrap_download_cran_latest_find(version) - - message("* Downloading renv ", version, " ... ", appendLF = FALSE) - - type <- spec$type - repos <- spec$repos - - info <- tryCatch( - utils::download.packages( - pkgs = "renv", - destdir = tempdir(), - repos = repos, - type = type, - quiet = TRUE - ), - condition = identity - ) - - if (inherits(info, "condition")) { - message("FAILED") - return(FALSE) - } - - # report success and return - message("OK (downloaded ", type, ")") - info[1, 2] - - } - - renv_bootstrap_download_cran_latest_find <- function(version) { - - # check whether binaries are supported on this system - binary <- - getOption("renv.bootstrap.binary", default = TRUE) && - !identical(.Platform$pkgType, "source") && - !identical(getOption("pkgType"), "source") && - Sys.info()[["sysname"]] %in% c("Darwin", "Windows") - - types <- c(if (binary) "binary", "source") - - # iterate over types + repositories - for (type in types) { - for (repos in renv_bootstrap_repos()) { - - # retrieve package database - db <- tryCatch( - as.data.frame( - utils::available.packages(type = type, repos = repos), - stringsAsFactors = FALSE - ), - error = identity - ) - - if (inherits(db, "error")) - next - - # check for compatible entry - entry <- db[db$Package %in% "renv" & db$Version %in% version, ] - if (nrow(entry) == 0) - next - - # found it; return spec to caller - spec <- list(entry = entry, type = type, repos = repos) - return(spec) - - } - } - - # if we got here, we failed to find renv - fmt <- "renv %s is not available from your declared package repositories" - stop(sprintf(fmt, version)) - - } - - renv_bootstrap_download_cran_archive <- function(version) { - - name <- sprintf("renv_%s.tar.gz", version) - repos <- renv_bootstrap_repos() - urls <- file.path(repos, "src/contrib/Archive/renv", name) - destfile <- file.path(tempdir(), name) - - message("* Downloading renv ", version, " ... ", appendLF = FALSE) - - for (url in urls) { - - status <- tryCatch( - renv_bootstrap_download_impl(url, destfile), - condition = identity - ) - - if (identical(status, 0L)) { - message("OK") - return(destfile) - } - - } - - message("FAILED") - return(FALSE) - - } - - renv_bootstrap_download_tarball <- function(version) { - - # if the user has provided the path to a tarball via - # an environment variable, then use it - tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) - if (is.na(tarball)) - return() - - # allow directories - info <- file.info(tarball, extra_cols = FALSE) - if (identical(info$isdir, TRUE)) { - name <- sprintf("renv_%s.tar.gz", version) - tarball <- file.path(tarball, name) - } - - # bail if it doesn't exist - if (!file.exists(tarball)) { - - # let the user know we weren't able to honour their request - fmt <- "* RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." - msg <- sprintf(fmt, tarball) - warning(msg) - - # bail - return() - - } - - fmt <- "* Bootstrapping with tarball at path '%s'." - msg <- sprintf(fmt, tarball) - message(msg) - - tarball - - } - - renv_bootstrap_download_github <- function(version) { - - enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") - if (!identical(enabled, "TRUE")) - return(FALSE) - - # prepare download options - pat <- Sys.getenv("GITHUB_PAT") - if (nzchar(Sys.which("curl")) && nzchar(pat)) { - fmt <- "--location --fail --header \"Authorization: token %s\"" - extra <- sprintf(fmt, pat) - saved <- options("download.file.method", "download.file.extra") - options(download.file.method = "curl", download.file.extra = extra) - on.exit(do.call(base::options, saved), add = TRUE) - } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { - fmt <- "--header=\"Authorization: token %s\"" - extra <- sprintf(fmt, pat) - saved <- options("download.file.method", "download.file.extra") - options(download.file.method = "wget", download.file.extra = extra) - on.exit(do.call(base::options, saved), add = TRUE) - } - - message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) - - url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) - name <- sprintf("renv_%s.tar.gz", version) - destfile <- file.path(tempdir(), name) - - status <- tryCatch( - renv_bootstrap_download_impl(url, destfile), - condition = identity - ) - - if (!identical(status, 0L)) { - message("FAILED") - return(FALSE) - } - - message("OK") - return(destfile) - - } - - renv_bootstrap_install <- function(version, tarball, library) { - - # attempt to install it into project library - message("* Installing renv ", version, " ... ", appendLF = FALSE) - dir.create(library, showWarnings = FALSE, recursive = TRUE) - - # invoke using system2 so we can capture and report output - bin <- R.home("bin") - exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" - r <- file.path(bin, exe) - - args <- c( - "--vanilla", "CMD", "INSTALL", "--no-multiarch", - "-l", shQuote(path.expand(library)), - shQuote(path.expand(tarball)) - ) - - output <- system2(r, args, stdout = TRUE, stderr = TRUE) - message("Done!") - - # check for successful install - status <- attr(output, "status") - if (is.numeric(status) && !identical(status, 0L)) { - header <- "Error installing renv:" - lines <- paste(rep.int("=", nchar(header)), collapse = "") - text <- c(header, lines, output) - writeLines(text, con = stderr()) - } - - status - - } - - renv_bootstrap_platform_prefix <- function() { - - # construct version prefix - version <- paste(R.version$major, R.version$minor, sep = ".") - prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") - - # include SVN revision for development versions of R - # (to avoid sharing platform-specific artefacts with released versions of R) - devel <- - identical(R.version[["status"]], "Under development (unstable)") || - identical(R.version[["nickname"]], "Unsuffered Consequences") - - if (devel) - prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") - - # build list of path components - components <- c(prefix, R.version$platform) - - # include prefix if provided by user - prefix <- renv_bootstrap_platform_prefix_impl() - if (!is.na(prefix) && nzchar(prefix)) - components <- c(prefix, components) - - # build prefix - paste(components, collapse = "/") - - } - - renv_bootstrap_platform_prefix_impl <- function() { - - # if an explicit prefix has been supplied, use it - prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) - if (!is.na(prefix)) - return(prefix) - - # if the user has requested an automatic prefix, generate it - auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) - if (auto %in% c("TRUE", "True", "true", "1")) - return(renv_bootstrap_platform_prefix_auto()) - - # empty string on failure - "" - - } - - renv_bootstrap_platform_prefix_auto <- function() { - - prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) - if (inherits(prefix, "error") || prefix %in% "unknown") { - - msg <- paste( - "failed to infer current operating system", - "please file a bug report at https://github.com/rstudio/renv/issues", - sep = "; " - ) - - warning(msg) - - } - - prefix - - } - - renv_bootstrap_platform_os <- function() { - - sysinfo <- Sys.info() - sysname <- sysinfo[["sysname"]] - - # handle Windows + macOS up front - if (sysname == "Windows") - return("windows") - else if (sysname == "Darwin") - return("macos") - - # check for os-release files - for (file in c("/etc/os-release", "/usr/lib/os-release")) - if (file.exists(file)) - return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) - - # check for redhat-release files - if (file.exists("/etc/redhat-release")) - return(renv_bootstrap_platform_os_via_redhat_release()) - - "unknown" - - } - - renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { - - # read /etc/os-release - release <- utils::read.table( - file = file, - sep = "=", - quote = c("\"", "'"), - col.names = c("Key", "Value"), - comment.char = "#", - stringsAsFactors = FALSE - ) - - vars <- as.list(release$Value) - names(vars) <- release$Key - - # get os name - os <- tolower(sysinfo[["sysname"]]) - - # read id - id <- "unknown" - for (field in c("ID", "ID_LIKE")) { - if (field %in% names(vars) && nzchar(vars[[field]])) { - id <- vars[[field]] - break - } - } - - # read version - version <- "unknown" - for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { - if (field %in% names(vars) && nzchar(vars[[field]])) { - version <- vars[[field]] - break - } - } - - # join together - paste(c(os, id, version), collapse = "-") - - } - - renv_bootstrap_platform_os_via_redhat_release <- function() { - - # read /etc/redhat-release - contents <- readLines("/etc/redhat-release", warn = FALSE) - - # infer id - id <- if (grepl("centos", contents, ignore.case = TRUE)) - "centos" - else if (grepl("redhat", contents, ignore.case = TRUE)) - "redhat" - else - "unknown" - - # try to find a version component (very hacky) - version <- "unknown" - - parts <- strsplit(contents, "[[:space:]]")[[1L]] - for (part in parts) { - - nv <- tryCatch(numeric_version(part), error = identity) - if (inherits(nv, "error")) - next - - version <- nv[1, 1] - break - - } - - paste(c("linux", id, version), collapse = "-") - - } - - renv_bootstrap_library_root_name <- function(project) { - - # use project name as-is if requested - asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") - if (asis) - return(basename(project)) - - # otherwise, disambiguate based on project's path - id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) - paste(basename(project), id, sep = "-") - - } - - renv_bootstrap_library_root <- function(project) { - - prefix <- renv_bootstrap_profile_prefix() - - path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) - if (!is.na(path)) - return(paste(c(path, prefix), collapse = "/")) - - path <- renv_bootstrap_library_root_impl(project) - if (!is.null(path)) { - name <- renv_bootstrap_library_root_name(project) - return(paste(c(path, prefix, name), collapse = "/")) - } - - renv_bootstrap_paths_renv("library", project = project) - - } - - renv_bootstrap_library_root_impl <- function(project) { - - root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) - if (!is.na(root)) - return(root) - - type <- renv_bootstrap_project_type(project) - if (identical(type, "package")) { - userdir <- renv_bootstrap_user_dir() - return(file.path(userdir, "library")) - } - - } - - renv_bootstrap_validate_version <- function(version) { - - loadedversion <- utils::packageDescription("renv", fields = "Version") - if (version == loadedversion) - return(TRUE) - - # assume four-component versions are from GitHub; three-component - # versions are from CRAN - components <- strsplit(loadedversion, "[.-]")[[1]] - remote <- if (length(components) == 4L) - paste("rstudio/renv", loadedversion, sep = "@") - else - paste("renv", loadedversion, sep = "@") - - fmt <- paste( - "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", - "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", - "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", - sep = "\n" - ) - - msg <- sprintf(fmt, loadedversion, version, remote) - warning(msg, call. = FALSE) - - FALSE - - } - - renv_bootstrap_hash_text <- function(text) { - - hashfile <- tempfile("renv-hash-") - on.exit(unlink(hashfile), add = TRUE) - - writeLines(text, con = hashfile) - tools::md5sum(hashfile) - - } - - renv_bootstrap_load <- function(project, libpath, version) { - - # try to load renv from the project library - if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) - return(FALSE) - - # warn if the version of renv loaded does not match - renv_bootstrap_validate_version(version) - - # load the project - renv::load(project) - - TRUE - - } - - renv_bootstrap_profile_load <- function(project) { - - # if RENV_PROFILE is already set, just use that - profile <- Sys.getenv("RENV_PROFILE", unset = NA) - if (!is.na(profile) && nzchar(profile)) - return(profile) - - # check for a profile file (nothing to do if it doesn't exist) - path <- renv_bootstrap_paths_renv("profile", profile = FALSE) - if (!file.exists(path)) - return(NULL) - - # read the profile, and set it if it exists - contents <- readLines(path, warn = FALSE) - if (length(contents) == 0L) - return(NULL) - - # set RENV_PROFILE - profile <- contents[[1L]] - if (!profile %in% c("", "default")) - Sys.setenv(RENV_PROFILE = profile) - - profile - - } - - renv_bootstrap_profile_prefix <- function() { - profile <- renv_bootstrap_profile_get() - if (!is.null(profile)) - return(file.path("profiles", profile, "renv")) - } - - renv_bootstrap_profile_get <- function() { - profile <- Sys.getenv("RENV_PROFILE", unset = "") - renv_bootstrap_profile_normalize(profile) - } - - renv_bootstrap_profile_set <- function(profile) { - profile <- renv_bootstrap_profile_normalize(profile) - if (is.null(profile)) - Sys.unsetenv("RENV_PROFILE") - else - Sys.setenv(RENV_PROFILE = profile) - } - - renv_bootstrap_profile_normalize <- function(profile) { - - if (is.null(profile) || profile %in% c("", "default")) - return(NULL) - - profile - - } - - renv_bootstrap_path_absolute <- function(path) { - - substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( - substr(path, 1L, 1L) %in% c(letters, LETTERS) && - substr(path, 2L, 3L) %in% c(":/", ":\\") - ) - - } - - renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { - renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") - root <- if (renv_bootstrap_path_absolute(renv)) NULL else project - prefix <- if (profile) renv_bootstrap_profile_prefix() - components <- c(root, renv, prefix, ...) - paste(components, collapse = "/") - } - - renv_bootstrap_project_type <- function(path) { - - descpath <- file.path(path, "DESCRIPTION") - if (!file.exists(descpath)) - return("unknown") - - desc <- tryCatch( - read.dcf(descpath, all = TRUE), - error = identity - ) - - if (inherits(desc, "error")) - return("unknown") - - type <- desc$Type - if (!is.null(type)) - return(tolower(type)) - - package <- desc$Package - if (!is.null(package)) - return("package") - - "unknown" - - } - - renv_bootstrap_user_dir <- function() { - dir <- renv_bootstrap_user_dir_impl() - path.expand(chartr("\\", "/", dir)) - } - - renv_bootstrap_user_dir_impl <- function() { - - # use local override if set - override <- getOption("renv.userdir.override") - if (!is.null(override)) - return(override) - - # use R_user_dir if available - tools <- asNamespace("tools") - if (is.function(tools$R_user_dir)) - return(tools$R_user_dir("renv", "cache")) - - # try using our own backfill for older versions of R - envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") - for (envvar in envvars) { - root <- Sys.getenv(envvar, unset = NA) - if (!is.na(root)) - return(file.path(root, "R/renv")) - } - - # use platform-specific default fallbacks - if (Sys.info()[["sysname"]] == "Windows") - file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") - else if (Sys.info()[["sysname"]] == "Darwin") - "~/Library/Caches/org.R-project.R/R/renv" - else - "~/.cache/R/renv" - - } - - - renv_json_read <- function(file = NULL, text = NULL) { - - text <- paste(text %||% read(file), collapse = "\n") - - # find strings in the JSON - pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' - locs <- gregexpr(pattern, text, perl = TRUE)[[1]] - - # if any are found, replace them with placeholders - replaced <- text - strings <- character() - replacements <- character() - - if (!identical(c(locs), -1L)) { - - # get the string values - starts <- locs - ends <- locs + attr(locs, "match.length") - 1L - strings <- substring(text, starts, ends) - - # only keep those requiring escaping - strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) - - # compute replacements - replacements <- sprintf('"\032%i\032"', seq_along(strings)) - - # replace the strings - mapply(function(string, replacement) { - replaced <<- sub(string, replacement, replaced, fixed = TRUE) - }, strings, replacements) - - } - - # transform the JSON into something the R parser understands - transformed <- replaced - transformed <- gsub("[[{]", "list(", transformed) - transformed <- gsub("[]}]", ")", transformed) - transformed <- gsub(":", "=", transformed, fixed = TRUE) - text <- paste(transformed, collapse = "\n") - - # parse it - json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] - - # construct map between source strings, replaced strings - map <- as.character(parse(text = strings)) - names(map) <- as.character(parse(text = replacements)) - - # convert to list - map <- as.list(map) - - # remap strings in object - remapped <- renv_json_remap(json, map) - - # evaluate - eval(remapped, envir = baseenv()) - - } - - renv_json_remap <- function(json, map) { - - # fix names - if (!is.null(names(json))) { - lhs <- match(names(json), names(map), nomatch = 0L) - rhs <- match(names(map), names(json), nomatch = 0L) - names(json)[rhs] <- map[lhs] - } - - # fix values - if (is.character(json)) - return(map[[json]] %||% json) - - # handle true, false, null - if (is.name(json)) { - text <- as.character(json) - if (text == "true") - return(TRUE) - else if (text == "false") - return(FALSE) - else if (text == "null") - return(NULL) - } - - # recurse - if (is.recursive(json)) { - for (i in seq_along(json)) { - json[i] <- list(renv_json_remap(json[[i]], map)) - } - } - - json - - } - - # load the renv profile, if any - renv_bootstrap_profile_load(project) - - # construct path to library root - root <- renv_bootstrap_library_root(project) - - # construct library prefix for platform - prefix <- renv_bootstrap_platform_prefix() - - # construct full libpath - libpath <- file.path(root, prefix) - - # attempt to load - if (renv_bootstrap_load(project, libpath, version)) - return(TRUE) - - # load failed; inform user we're about to bootstrap - prefix <- paste("# Bootstrapping renv", version) - postfix <- paste(rep.int("-", 77L - nchar(prefix)), collapse = "") - header <- paste(prefix, postfix) - message(header) - - # perform bootstrap - bootstrap(version, libpath) - - # exit early if we're just testing bootstrap - if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) - return(TRUE) - - # try again to load - if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { - message("* Successfully installed and loaded renv ", version, ".") - return(renv::load()) - } - - # failed to download or load renv; warn the user - msg <- c( - "Failed to find an renv installation: the project will not be loaded.", - "Use `renv::activate()` to re-initialize the project." - ) - - warning(paste(msg, collapse = "\n"), call. = FALSE) - -}) diff --git a/renv/settings.dcf b/renv/settings.dcf deleted file mode 100644 index 169d82f1..00000000 --- a/renv/settings.dcf +++ /dev/null @@ -1,10 +0,0 @@ -bioconductor.version: -external.libraries: -ignored.packages: -package.dependency.fields: Imports, Depends, LinkingTo -r.version: -snapshot.type: implicit -use.cache: TRUE -vcs.ignore.cellar: TRUE -vcs.ignore.library: TRUE -vcs.ignore.local: TRUE diff --git a/tests/testthat/test-depreciation.R b/tests/testthat/test-depreciation.R new file mode 100644 index 00000000..157f59b1 --- /dev/null +++ b/tests/testthat/test-depreciation.R @@ -0,0 +1,113 @@ +test_that("xportr_df_label: deprecated metacore argument still works and gives warning", { + withr::local_options(lifecycle_verbosity = "quiet") + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame(dataset = "df", label = "Label") + + df_spec_labeled_df <- xportr_df_label(df, metacore = df_meta) + + expect_equal(attr(df_spec_labeled_df, "label"), "Label") + xportr_df_label(df, metacore = df_meta) %>% + lifecycle::expect_deprecated("Please use the `metadata` argument instead.") +}) + +test_that("xportr_format: deprecated metacore argument still works and gives warning", { + withr::local_options(lifecycle_verbosity = "quiet") + df <- data.frame(x = 1, y = 2) + df_meta <- data.frame( + dataset = "df", + variable = "x", + format = "date9." + ) + + formatted_df <- xportr_format(df, metacore = df_meta) + + expect_equal(attr(formatted_df$x, "format.sas"), "DATE9.") + xportr_format(df, metacore = df_meta) %>% + lifecycle::expect_deprecated("Please use the `metadata` argument instead.") +}) + +test_that("xportr_label: deprecated metacore argument still works and gives warning", { + withr::local_options(lifecycle_verbosity = "quiet") + + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame(dataset = "df", variable = "x", label = "foo") + + df_labeled_df <- suppressMessages( + xportr_label(df, metacore = df_meta) + ) + + expect_equal(attr(df_labeled_df$x, "label"), "foo") + + # Note that only the deprecated message should be caught (others are ignored) + suppressMessages( + xportr_label(df, metacore = df_meta) %>% + lifecycle::expect_deprecated("Please use the `metadata` argument instead.") + ) +}) + +test_that("xportr_length: deprecated metacore argument still works and gives warning", { + withr::local_options(lifecycle_verbosity = "quiet") + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame( + dataset = "df", + variable = c("x", "y"), + type = c("text", "text"), + length = c(1, 2) + ) + + df_with_width <- xportr_length(df, metacore = df_meta) + + expect_equal(c(x = 1, y = 2), map_dbl(df_with_width, attr, "width")) + + xportr_length(df, metacore = df_meta) %>% + lifecycle::expect_deprecated("Please use the `metadata` argument instead.") +}) + +test_that("xportr_order: deprecated metacore argument still works and gives warning", { + withr::local_options(lifecycle_verbosity = "quiet") + + df <- data.frame(c = 1:5, a = "a", d = 5:1, b = LETTERS[1:5]) + df_meta <- data.frame( + dataset = "DOMAIN", + variable = letters[1:4], + order = 1:4 + ) + + ordered_df <- suppressMessages( + xportr_order(df, metacore = df_meta, domain = "DOMAIN") + ) + + expect_equal(names(ordered_df), df_meta$variable) + + # Note that only the deprecated message should be caught (others are ignored) + suppressMessages( + xportr_order(df, metacore = df_meta) %>% + lifecycle::expect_deprecated("Please use the `metadata` argument instead.") + ) +}) + +test_that("xportr_type: deprecated metacore argument still works and gives warning", { + withr::local_options(lifecycle_verbosity = "quiet") + df <- data.frame( + Subj = as.character(c(123, 456, 789, "", NA, NA_integer_)), + Different = c("a", "b", "c", "", NA, NA_character_), + Val = c("1", "2", "3", "", NA, NA_character_), + Param = c("param1", "param2", "param3", "", NA, NA_character_) + ) + df_meta <- data.frame( + dataset = "df", + variable = c("Subj", "Param", "Val", "NotUsed"), + type = c("numeric", "character", "numeric", "character"), + format = NA + ) + + df2 <- suppressMessages( + xportr_type(df, metacore = df_meta) + ) + + # Note that only the deprecated message should be caught (others are ignored) + suppressMessages( + xportr_type(df, metacore = df_meta) %>% + lifecycle::expect_deprecated("Please use the `metadata` argument instead.") + ) +}) diff --git a/tests/testthat/test-df_label.R b/tests/testthat/test-df_label.R new file mode 100644 index 00000000..eae3969d --- /dev/null +++ b/tests/testthat/test-df_label.R @@ -0,0 +1,14 @@ +test_that("xportr_df_label: error when metadata is not set", { + adsl <- data.frame( + USUBJID = c(1001, 1002, 1003), + SITEID = c(001, 002, 003), + AGE = c(63, 35, 27), + SEX = c("M", "F", "M") + ) + + + expect_error( + xportr_df_label(adsl), + regexp = "Metadata must be set with `metadata` or `xportr_metadata\\(\\)`" + ) +}) diff --git a/tests/testthat/test-format.R b/tests/testthat/test-format.R new file mode 100644 index 00000000..76b65e1d --- /dev/null +++ b/tests/testthat/test-format.R @@ -0,0 +1,21 @@ +test_that("xportr_format: error when metadata is not set", { + adsl <- data.frame( + USUBJID = c(1001, 1002, 1003), + BRTHDT = c(1, 1, 2) + ) + + expect_error( + xportr_format(adsl), + regexp = "Metadata must be set with `metadata` or `xportr_metadata\\(\\)`" + ) +}) + +test_that("xportr_format: Gets warning when metadata has multiple rows with same variable", { + # This test uses the (2) functions below to reduce code duplication + # All `expect_*` are being called inside the functions + # + # Checks that message appears when xportr.domain_name is invalid + multiple_vars_in_spec_helper(xportr_format) + # Checks that message doesn't appear when xportr.domain_name is valid + multiple_vars_in_spec_helper2(xportr_format) +}) diff --git a/tests/testthat/test-label.R b/tests/testthat/test-label.R new file mode 100644 index 00000000..8030a826 --- /dev/null +++ b/tests/testthat/test-label.R @@ -0,0 +1,23 @@ +test_that("xportr_label: error when metadata is not set", { + df <- data.frame( + Subj = as.character(123, 456, 789), + Different = c("a", "b", "c"), + Val = c("1", "2", "3"), + Param = c("param1", "param2", "param3") + ) + + expect_error( + xportr_label(df), + regexp = "Metadata must be set with `metadata` or `xportr_metadata\\(\\)`" + ) +}) + +test_that("xportr_label: Gets warning when metadata has multiple rows with same variable", { + # This test uses the (2) functions below to reduce code duplication + # All `expect_*` are being called inside the functions + # + # Checks that message appears when xportr.domain_name is invalid + multiple_vars_in_spec_helper(xportr_label) + # Checks that message doesn't appear when xportr.domain_name is valid + multiple_vars_in_spec_helper2(xportr_label) +}) diff --git a/tests/testthat/test-length.R b/tests/testthat/test-length.R index b75b8ebf..e749684d 100644 --- a/tests/testthat/test-length.R +++ b/tests/testthat/test-length.R @@ -1,15 +1,226 @@ -suppressWarnings({ - library(haven) - library(readxl) +#' Test `xportr_length()` function +#' +#' Tests will check for: +#' * Errors +#' * Result of call will create `SASlength` attribute (`width` for each +#' variable) + +test_that("xportr_length: Accepts valid domain names in metadata object", { + adsl <- minimal_table(30) + metadata <- minimal_metadata(dataset = TRUE, length = TRUE, var_names = colnames(adsl)) + + # Setup temporary options with active verbose + withr::local_options(list(xportr.length_verbose = "message")) + + # Test minimal call with valid data and without domain + xportr_length(adsl, metadata) %>% + expect_silent() %>% + expect_attr_width(metadata$length) + + # Test minimal call with valid data with a valid domain + xportr_length(adsl, metadata, domain = "adsl") %>% + expect_silent() %>% + expect_attr_width(metadata$length) %>% + NROW() %>% + expect_equal(30) + + # Test minimal call without datasets + metadata_without_dataset <- metadata %>% select(-"dataset") + + xportr_length(adsl, metadata_without_dataset) %>% + expect_silent() %>% + expect_attr_width(metadata_without_dataset$length) %>% + NROW() %>% + expect_equal(30) + + # Test minimal call without datasets and ignores domain + xportr_length(adsl, metadata_without_dataset, domain = "something_else") %>% + expect_silent() %>% + expect_attr_width(metadata_without_dataset$length) %>% + NROW() %>% + expect_equal(30) +}) + +test_that("xportr_length: CDISC data frame is being piped after another xportr function", { + adsl <- minimal_table(30) + metadata <- minimal_metadata( + dataset = TRUE, length = TRUE, type = TRUE, format = TRUE, var_names = colnames(adsl) + ) + + # Setup temporary options with active verbose + withr::local_options(list(xportr.length_verbose = "message")) + + adsl %>% + xportr_type(metadata, domain = "adsl", verbose = "message") %>% + xportr_length(metadata) %>% + expect_silent() %>% + expect_attr_width(metadata$length) %>% + attr("_xportr.df_arg_") %>% + expect_equal("adsl") +}) + +test_that("xportr_length: CDISC data frame domain is being recognized from pipe", { + adsl <- minimal_table(30) + metadata <- minimal_metadata(dataset = TRUE, length = TRUE, var_names = colnames(adsl)) + + # Setup temporary options with `verbose = "message"` + withr::local_options(list(xportr.length_verbose = "message")) + + # Remove empty lines in cli theme + local_cli_theme() + + # With domain manually set + not_adsl <- adsl + result <- not_adsl %>% + xportr_length(metadata) %>% + expect_message("Variable lengths missing from metadata") %>% + expect_message("lengths resolved") %>% + expect_message("Variable\\(s\\) present in dataframe but doesn't exist in `metadata`") + + suppressMessages({ + result <- not_adsl %>% + xportr_length(metadata, verbose = "none") + }) + + expect_no_match(attr(result, "_xportr.df_arg_"), "^adsl$") + + # Test results with piping + result <- adsl %>% + xportr_length(metadata) + + attr(result, "_xportr.df_arg_") %>% + expect_equal("adsl") }) -test_that("Domain not in character format", { - +test_that("xportr_length: Impute character lengths based on class", { + adsl <- minimal_table(30, cols = c("x", "b")) + metadata <- minimal_metadata( + dataset = TRUE, length = TRUE, var_names = colnames(adsl) + ) %>% + mutate(length = length - 1) + + # Setup temporary options with `verbose = "none"` + withr::local_options(list(xportr.length_verbose = "none")) + # Define controlled `character_types` for this test + withr::local_options(list(xportr.character_types = c("character", "date"))) + + # Remove empty lines in cli theme + local_cli_theme() + + # Test length imputation of character and numeric (not valid character type) + result <- adsl %>% + xportr_length(metadata) %>% + expect_silent() + + expect_attr_width(result, c(7, 199)) + + # Test length imputation of two valid character types (both should have + # `width = 200``) + adsl <- adsl %>% + mutate( + new_date = as.Date(.data$x, origin = "1970-01-01"), + new_char = as.character(.data$b), + new_num = as.numeric(.data$x) + ) + + adsl %>% + xportr_length(metadata) %>% + expect_message("Variable lengths missing from metadata") %>% + expect_message("lengths resolved") %>% + expect_attr_width(c(7, 199, 200, 200, 8)) +}) + +test_that("xportr_length: Throws message when variables not present in metadata", { + adsl <- minimal_table(30, cols = c("x", "y")) + metadata <- minimal_metadata(dataset = TRUE, length = TRUE, var_names = c("x")) + + # Setup temporary options with `verbose = "message"` + withr::local_options(list(xportr.length_verbose = "message")) + # Remove empty lines in cli theme + local_cli_theme() + + # Test that message is given which indicates that variable is not present + xportr_length(adsl, metadata) %>% + expect_message("Variable lengths missing from metadata") %>% + expect_message("lengths resolved") %>% + expect_message(regexp = "Problem with `y`") +}) + +test_that("xportr_length: Metacore instance can be used", { + skip_if_not_installed("metacore") + adsl <- minimal_table(30, cols = c("x", "b")) + + # Build a minimal metacore object + metadata <- suppressMessages( + suppressWarnings( + metacore::metacore( + ds_spec = dplyr::tibble( + dataset = "ADSL" + ), + ds_vars = dplyr::tibble( + dataset = "ADSL", + variable = colnames(adsl) + ), + var_spec = minimal_metadata( + length = TRUE, + type = TRUE, + label = TRUE, + format = TRUE, + order = TRUE + ) + ) + ) + ) + + # Test metacore parameter with `metacore` class instead of data.frame + xportr_length(adsl, metadata, domain = "adsl", verbose = "message") %>% + expect_silent() %>% + NROW() %>% + expect_equal(30) %>% + expect_attr_width(metadata$length) +}) + +test_that("xportr_length: Domain not in character format", { + skip_if_not_installed("haven") + skip_if_not_installed("readxl") + + require(haven, quietly = TRUE) + require(readxl, quietly = TRUE) + ADAE <- read_sas(system.file("extdata", "adae.sas7bdat", package = "xportr")) met <- read_excel(system.file("specs", "ADaM_spec.xlsx", package = "xportr"), 3) - + expect_error( - xportr_length(ADAE, metacore = met, domain = ADAE, verbose = "none") - ) - + xportr_length(ADAE, met, domain = ADAE, verbose = "none") + ) +}) + +test_that("xportr_length: Column length of known/unkown character types is 200/8 ", { + expect_equal(impute_length(123), 8) + expect_equal(impute_length(123L), 8) + expect_equal(impute_length("string"), 200) + expect_equal(impute_length(Sys.Date()), 200) + expect_equal(impute_length(Sys.time()), 200) + + withr::local_options(list(xportr.character_types = c("character", "date"))) + expect_equal(impute_length(Sys.time()), 8) +}) + +test_that("xportr_length: error when metadata is not set", { + adsl <- minimal_table(30) + + expect_error( + xportr_length(adsl), + regexp = "Metadata must be set with `metadata` or `xportr_metadata\\(\\)`" + ) +}) + +test_that("xportr_length: Gets warning when metadata has multiple rows with same variable", { + # This test uses the (2) functions below to reduce code duplication + # All `expect_*` are being called inside the functions + # + # Checks that message appears when xportr.domain_name is invalid + multiple_vars_in_spec_helper(xportr_length) + # Checks that message doesn't appear when xportr.domain_name is valid + multiple_vars_in_spec_helper2(xportr_length) }) diff --git a/tests/testthat/test-messages.R b/tests/testthat/test-messages.R new file mode 100644 index 00000000..2055914e --- /dev/null +++ b/tests/testthat/test-messages.R @@ -0,0 +1,71 @@ +#' Test `R/messages.R` functions + +test_that("xportr_logger: Type parameter will create correct message type", { + xportr_logger("A message", type = "none") %>% + expect_silent() + + xportr_logger("A message", type = "message") %>% + expect_message("A message") + + xportr_logger("A message", type = "warn") %>% + expect_warning("A message") + + xportr_logger("A message", type = "stop") %>% + expect_error("A message") + + # Supports additional parameters to rlang::stop + xportr_logger("A message", type = "stop", footer = "A footer") %>% + expect_error("A message", class = "rlang_error") +}) + +test_that("length_log: Missing lengths messages are shown", { + # Remove empty lines in cli theme + local_cli_theme() + + length_log(c("var1", "var2", "var3"), "message") %>% + expect_message("Variable lengths missing from metadata.") %>% + expect_message("lengths resolved") %>% + expect_message("Problem with `var1`.*`var2`.*`var3`") +}) + +test_that("length_log: Missing variables messages are shown", { + # Remove empty lines in cli theme + local_cli_theme() + + label_log(c("var1", "var2", "var3"), "message") %>% + # cli messages + expect_message("Variable labels missing from metadata.") %>% + expect_message("labels skipped") %>% + # xportr_logger messages + expect_message("Problem with `var1`.*`var2`.*`var3`") +}) + +test_that("var_names_log: Renamed variables messages are shown", { + # Remove empty lines in cli theme + local_cli_theme() + + tidy_names_df <- data.frame( + original_varname = c("var1", "var2", "var3", "var4", "VAR5", "VAR6"), + renamed_var = c("VAR1", "VAR2", "VAR3", "VAR4", "VAR5", "VAR6"), + col_pos = seq(1, 6), + renamed_msg = glue("renamed message {seq(1, 6)}"), + renamed_n = 0 + ) + + tidy_names_df %>% + mutate( + renamed_n = c( + 2, + sample(c(0, 1, 2), size = NROW(.data$renamed_n) - 1, replace = TRUE) + ) + ) %>% + var_names_log("message") %>% + expect_message( + ".*[0-9]+ of [0-9]+ \\([0-9]+(\\.[0-9]+)%\\) variables were renamed.*" + ) %>% + expect_message("Var . : '.*' was renamed to '.*'") %>% + expect_message("Var . : '.*' was renamed to '.*'") %>% + expect_message("Var . : '.*' was renamed to '.*'") %>% + expect_message("Var . : '.*' was renamed to '.*'") %>% + expect_message("Duplicate renamed term\\(s\\) were created") +}) diff --git a/tests/testthat/test-metadata.R b/tests/testthat/test-metadata.R index 264cc657..b232ea2d 100644 --- a/tests/testthat/test-metadata.R +++ b/tests/testthat/test-metadata.R @@ -1,128 +1,625 @@ - extract_format <- function(.x) { format_ <- character(length(.x)) - for (i in 1:length(.x)) { + for (i in seq_along(.x)) { format_[i] <- attr(.x[[i]], "format.sas") } format_ } -test_that("Variable label", { +extract_var_label <- function(.x) { + vapply(.x, function(.x) attr(.x, "label"), character(1), USE.NAMES = FALSE) +} + +test_that("xportr_label: Correctly applies label from data.frame spec", { df <- data.frame(x = "a", y = "b") - varmeta <- data.frame(dataset = rep("df", 2), - variable = c("x", "y"), - label = c("foo", "bar")) - - extract_varlabel <- function(.x) { - vapply(.x, function(.x) attr(.x, "label"), character(1), USE.NAMES = FALSE) - } - - df <- xportr_label(df, varmeta) - df_dput <- dput(df) - - expect_equal(extract_varlabel(df), c("foo", "bar")) - expect_equal(df_dput, - structure(list(x = structure("a", label = "foo"), - y = structure("b", label = "bar")), - row.names = c(NA, -1L), class = "data.frame")) + df_meta <- data.frame(dataset = "df", variable = c("x", "y"), label = c("foo", "bar")) + + df_labeled_df <- xportr_label(df, df_meta) + + expect_equal(extract_var_label(df_labeled_df), c("foo", "bar")) + + expect_equal( + df_labeled_df, + structure( + list( + x = structure("a", label = "foo"), + y = structure("b", label = "bar") + ), + row.names = c(NA, -1L), + `_xportr.df_arg_` = "df", + class = "data.frame" + ) + ) +}) + +test_that("xportr_label: Correctly applies label when data is piped", { + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame(dataset = "df", variable = c("x", "y"), label = c("foo", "bar")) + + df_labeled_df <- df %>% xportr_label(df_meta) + + expect_equal(extract_var_label(df_labeled_df), c("foo", "bar")) + expect_equal( + df_labeled_df, + structure( + list( + x = structure("a", label = "foo"), + y = structure("b", label = "bar") + ), + row.names = c(NA, -1L), + `_xportr.df_arg_` = "df", + class = "data.frame" + ) + ) +}) + +test_that("xportr_label: Correctly applies label for custom domain", { + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame(dataset = rep("DOMAIN", 2), variable = c("x", "y"), label = c("foo", "bar")) + + df_labeled_df <- xportr_label(df, df_meta, domain = "DOMAIN") + + expect_equal(extract_var_label(df_labeled_df), c("foo", "bar")) + expect_equal( + df_labeled_df, + structure( + list( + x = structure("a", label = "foo"), + y = structure("b", label = "bar") + ), + row.names = c(NA, -1L), + `_xportr.df_arg_` = "DOMAIN", + class = "data.frame" + ) + ) +}) + +test_that("xportr_label: Correctly applies label from metacore spec", { + skip_if_not_installed("metacore") + + df <- data.frame(x = "a", y = "b", variable = "value") + metacore_meta <- suppressMessages(suppressWarnings( + metacore::metacore( + var_spec = data.frame( + variable = c("x", "y"), + type = "text", + label = c("X Label", "Y Label"), + length = c(4, 4), + common = NA_character_, + format = NA_character_ + ) + ) + )) + + metacoes_labeled_df <- suppressMessages( + xportr_label(df, metacore_meta) + ) + + expect_equal(extract_var_label(metacoes_labeled_df), c("X Label", "Y Label", "")) + expect_equal( + metacoes_labeled_df, + structure( + list( + x = structure("a", label = "X Label"), + y = structure("b", label = "Y Label"), + variable = structure("value", label = "") + ), + row.names = c(NA, -1L), + `_xportr.df_arg_` = "df", + class = "data.frame" + ) + ) +}) + +test_that("xportr_label: Expect error if any variable does not exist in metadata", { + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame( + dataset = "df", + variable = "x", + label = "foo" + ) + suppressMessages( + xportr_label(df, df_meta, verbose = "stop") + ) %>% + expect_error() +}) + +test_that("xportr_label: Expect error if label exceeds 40 characters", { + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame( + dataset = "df", + variable = "x", + label = strrep("a", 41) + ) + + suppressMessages(xportr_label(df, df_meta)) %>% + expect_warning("variable label must be 40 characters or less") }) -test_that("Dataset label", { +test_that("xportr_label: Expect error if domain is not a character", { df <- data.frame(x = "a", y = "b") - dfmeta <- data.frame(dataset = "df", - label = "Label") - - df <- xportr_df_label(df, dfmeta) - expect_equal(attr(df, "label"), "Label") - expect_equal(dput(df), structure(list(x = "a", y = "b"), class = "data.frame", - row.names = c(NA, -1L), label = "Label")) + df_meta <- data.frame( + dataset = "df", + variable = "x", + label = "foo" + ) + + expect_error( + xportr_label(df, df_meta, domain = 1), + "`domain` must be a vector with type ." + ) + expect_error( + xportr_label(df, df_meta, domain = NA), + "`domain` must be a vector with type ." + ) +}) + +test_that("xportr_df_label: Correctly applies label from data.frame spec", { + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame(dataset = "df", label = "Label") + + df_spec_labeled_df <- xportr_df_label(df, df_meta) + + expect_equal(attr(df_spec_labeled_df, "label"), "Label") + expect_equal( + df_spec_labeled_df, + structure( + list(x = "a", y = "b"), + class = "data.frame", + `_xportr.df_arg_` = "df", + row.names = c(NA, -1L), + label = "Label" + ) + ) }) -test_that("Expect error if any variable doesn't exist in var. metadata", { +test_that("xportr_df_label: Correctly applies label when data is piped", { df <- data.frame(x = "a", y = "b") - varmeta <- data.frame(dataset = "df", - variable = "x", - label = "foo") - - # expect_error(xportr_label(df, varmeta, verbose = "stop"), - # "present in `.df` but doesn't exist in `datadef`") + df_meta <- data.frame(dataset = "df", label = "Label") + + df_spec_labeled_df <- df %>% + xportr_df_label(df_meta) %>% + xportr_df_label(df_meta) + + expect_equal(attr(df_spec_labeled_df, "label"), "Label") + expect_equal( + df_spec_labeled_df, + structure( + list(x = "a", y = "b"), + class = "data.frame", row.names = c(NA, -1L), `_xportr.df_arg_` = "df", label = "Label" + ) + ) }) -test_that("Expect error if any label exceeds 40 character", { +test_that("xportr_df_label: Correctly applies label for custom domain", { df <- data.frame(x = "a", y = "b") - varmeta <- data.frame(dataset = rep("df", 2), - variable = c("x", "y"), - label = c("foo", "Lorem ipsum dolor sit amet, consectetur adipiscing elit")) - dfmeta <- data.frame(dataset = "df", - label = "Lorem ipsum dolor sit amet, consectetur adipiscing elit") - - expect_warning(xportr_label(df, varmeta), - "variable label must be 40 characters or less") - expect_error(xportr_df_label(df, dfmeta), - "dataset label must be 40 characters or less") + df_meta <- data.frame(dataset = "DOMAIN", label = "Label") + + df_spec_labeled_df <- xportr_df_label(df, df_meta, domain = "DOMAIN") + + expect_equal(attr(df_spec_labeled_df, "label"), "Label") + expect_equal( + df_spec_labeled_df, + structure( + list(x = "a", y = "b"), + class = "data.frame", row.names = c(NA, -1L), `_xportr.df_arg_` = "DOMAIN", label = "Label" + ) + ) +}) + +test_that("xportr_df_label: Correctly applies label from metacore spec", { + skip_if_not_installed("metacore") + + df <- data.frame(x = "a", y = "b") + metacore_meta <- suppressMessages(suppressWarnings( + metacore::metacore( + ds_spec = data.frame( + dataset = c("df"), + structure = "", + label = c("Label") + ) + ) + )) + + metacore_spec_labeled_df <- xportr_df_label(df, metacore_meta) + + expect_equal(attr(metacore_spec_labeled_df, "label"), "Label") + expect_equal( + metacore_spec_labeled_df, + structure( + list(x = "a", y = "b"), + class = "data.frame", + `_xportr.df_arg_` = "df", + row.names = c(NA, -1L), label = "Label" + ) + ) +}) + +test_that("xportr_df_label: Expect error if label exceeds 40 characters", { + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame( + dataset = "df", + label = strrep("a", 41) + ) + + expect_error( + xportr_df_label(df, df_meta), + "dataset label must be 40 characters or less" + ) +}) + +test_that("xportr_df_label: Expect error if domain is not a character", { + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame( + dataset = "df", + label = "foo" + ) + + expect_error( + xportr_df_label(df, df_meta, domain = 1), + "`domain` must be a vector with type ." + ) + expect_error( + xportr_df_label(df, df_meta, domain = NA), + "`domain` must be a vector with type ." + ) +}) + +test_that("xportr_format: Set formats as expected", { + df <- data.frame(x = 1, y = 2) + df_meta <- data.frame( + dataset = "df", + variable = c("x", "y"), + format = c("date9.", "datetime20.") + ) + + formatted_df <- xportr_format(df, df_meta) + + expect_equal(extract_format(formatted_df), c("DATE9.", "DATETIME20.")) + expect_equal(formatted_df, structure( + list( + x = structure(1, format.sas = "DATE9."), + y = structure(2, format.sas = "DATETIME20.") + ), + row.names = c(NA, -1L), `_xportr.df_arg_` = "df", class = "data.frame" + )) }) -test_that("xportr_format will set formats as expected", { +test_that("xportr_format: Set formats as expected when data is piped", { df <- data.frame(x = 1, y = 2) - varmeta <- data.frame(dataset = rep("df", 2), - variable = c("x", "y"), - format = c("date9.", "datetime20.")) - + df_meta <- data.frame( + dataset = "df", + variable = c("x", "y"), + format = c("date9.", "datetime20.") + ) - - out <- xportr_format(df, varmeta) - - expect_equal(extract_format(out), c("DATE9.", "DATETIME20.")) - expect_equal(dput(out), structure(list(x = structure(1, format.sas = "DATE9."), - y = structure(2, format.sas = "DATETIME20.")), - row.names = c(NA, -1L), class = "data.frame")) + formatted_df <- df %>% xportr_format(df_meta) + + expect_equal(extract_format(formatted_df), c("DATE9.", "DATETIME20.")) + expect_equal(formatted_df, structure( + list( + x = structure(1, format.sas = "DATE9."), + y = structure(2, format.sas = "DATETIME20.") + ), + row.names = c(NA, -1L), `_xportr.df_arg_` = "df", class = "data.frame" + )) +}) + +test_that("xportr_format: Set formats as expected for metacore spec", { + skip_if_not_installed("metacore") + df <- data.frame(x = 1, y = 2) + metacore_meta <- suppressMessages(suppressWarnings( + metacore::metacore( + var_spec = data.frame( + variable = c("x", "y"), + type = "text", + label = c("X Label", "Y Label"), + length = c(1, 2), + common = NA_character_, + format = c("date9.", "datetime20.") + ) + ) + )) + + formatted_df <- xportr_format(df, metacore_meta) + + expect_equal(extract_format(formatted_df), c("DATE9.", "DATETIME20.")) + expect_equal(formatted_df, structure( + list( + x = structure(1, format.sas = "DATE9."), + y = structure(2, format.sas = "DATETIME20.") + ), + row.names = c(NA, -1L), `_xportr.df_arg_` = "df", class = "data.frame" + )) }) -test_that("xportr_format will handle NA values and won't error", { +test_that("xportr_format: Set formats as expected for custom domain", { + df <- data.frame(x = 1, y = 2) + df_meta <- data.frame( + dataset = "DOMAIN", + variable = c("x", "y"), + format = c("date9.", "datetime20.") + ) + + formatted_df <- xportr_format(df, df_meta, domain = "DOMAIN") + + expect_equal(extract_format(formatted_df), c("DATE9.", "DATETIME20.")) + expect_equal(formatted_df, structure( + list( + x = structure(1, format.sas = "DATE9."), + y = structure(2, format.sas = "DATETIME20.") + ), + row.names = c(NA, -1L), `_xportr.df_arg_` = "DOMAIN", class = "data.frame" + )) +}) + +test_that("xportr_format: Handle NA values without raising an error", { df <- data.frame(x = 1, y = 2, z = 3, a = 4) - varmeta <- data.frame(dataset = rep("df", 4), - variable = c("x", "y", "z", "abc"), - format = c("date9.", "datetime20.", NA, "text")) - - out <- xportr_format(df, varmeta) - - expect_equal(extract_format(out), c("DATE9.", "DATETIME20.", "", "")) - expect_equal(dput(out), structure(list(x = structure(1, format.sas = "DATE9."), - y = structure(2, format.sas = "DATETIME20."), - z = structure(3, format.sas = ""), - a = structure(4, format.sas = "")), - row.names = c(NA, -1L), class = "data.frame")) -}) - -test_that("Error ", { - df1 <- data.frame(x = 1, y = 2) - df2 <- data.frame(x = 3, y = 4) - expect_error(xportr_label(df1, df2, domain = 1), - "`domain` must be a vector with type .") - expect_error(xportr_df_label(df1, df2, domain = mtcars), - "`domain` must be a vector with type .") - expect_error(xportr_format(df1, df2, domain = 1L), - "`domain` must be a vector with type .") -}) - -test_that("SAS length", { - df <- data.frame(x = "a", y = "b") - varmeta <- data.frame(dataset = rep("df", 2), - variable = c("x", "y"), - type = c("text", "text"), - length = c(1, 1)) - - extract_length <- function(.x) { - vapply(.x, function(.x) attr(.x, "width"), character(1), USE.NAMES = FALSE) - } + df_meta <- data.frame( + dataset = rep("df", 4), + variable = c("x", "y", "z", "abc"), + format = c("date9.", "datetime20.", NA, "text") + ) + + formatted_df <- xportr_format(df, df_meta) + + expect_equal(extract_format(formatted_df), c("DATE9.", "DATETIME20.", "", "")) + expect_equal(formatted_df, structure( + list( + x = structure(1, format.sas = "DATE9."), + y = structure(2, format.sas = "DATETIME20."), + z = structure(3, format.sas = ""), + a = structure(4, format.sas = "") + ), + row.names = c(NA, -1L), `_xportr.df_arg_` = "df", class = "data.frame" + )) +}) + +test_that("xportr_format: Expect error if domain is not a character", { + df <- data.frame(x = 1, y = 2, z = 3, a = 4) + df_meta <- data.frame( + dataset = "df", + variable = "x", + format = c("date9.") + ) + + expect_error( + xportr_format(df, df_meta, 1), + "`domain` must be a vector with type ." + ) + expect_error( + xportr_format(df, df_meta, NA), + "`domain` must be a vector with type ." + ) +}) + +test_that("xportr_length: Check if width attribute is set properly", { + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame( + dataset = "df", + variable = c("x", "y"), + type = c("text", "text"), + length = c(1, 2) + ) + + df_with_width <- xportr_length(df, df_meta) + + expect_equal(c(x = 1, y = 2), map_dbl(df_with_width, attr, "width")) + expect_equal(df_with_width, structure( + list( + x = structure("a", width = 1), + y = structure("b", width = 2) + ), + row.names = c(NA, -1L), `_xportr.df_arg_` = "df", class = "data.frame" + )) +}) + +test_that("xportr_length: Check if width attribute is set properly when data is piped", { + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame( + dataset = "df", + variable = c("x", "y"), + type = c("text", "text"), + length = c(1, 2) + ) + + df_with_width <- df %>% xportr_length(df_meta) + + expect_equal(c(x = 1, y = 2), map_dbl(df_with_width, attr, "width")) + expect_equal(df_with_width, structure( + list( + x = structure("a", width = 1), + y = structure("b", width = 2) + ), + row.names = c(NA, -1L), `_xportr.df_arg_` = "df", class = "data.frame" + )) +}) - out <- xportr_length(df, varmeta) +test_that("xportr_length: Check if width attribute is set properly for metacore spec", { + skip_if_not_installed("metacore") + df <- data.frame(x = "a", y = "b") + metacore_meta <- suppressMessages(suppressWarnings( + metacore::metacore( + var_spec = data.frame( + variable = c("x", "y"), + type = "text", + label = c("X Label", "Y Label"), + length = c(1, 2), + common = NA_character_, + format = NA_character_ + ) + ) + )) + + df_with_width <- xportr_length(df, metacore_meta) + + expect_equal(c(x = 1, y = 2), map_dbl(df_with_width, attr, "width")) + expect_equal(df_with_width, structure( + list( + x = structure("a", width = 1), + y = structure("b", width = 2) + ), + row.names = c(NA, -1L), `_xportr.df_arg_` = "df", class = "data.frame" + )) +}) - expect_equal(c(x = 1, y = 1), map_dbl(out, attr, "width")) - expect_equal(dput(out), structure(list(x = structure("a", width = 1), - y = structure("b", width = 1)), - row.names = c(NA, -1L), class = "data.frame")) +test_that("xportr_length: Check if width attribute is set properly when custom domain is passed", { + df <- data.frame(x = "a", y = "b") + df_meta <- data.frame( + dataset = rep("DOMAIN", 2), + variable = c("x", "y"), + type = c("text", "text"), + length = c(1, 2) + ) + + df_with_width <- xportr_length(df, df_meta, domain = "DOMAIN") + + expect_equal(c(x = 1, y = 2), map_dbl(df_with_width, attr, "width")) + expect_equal(df_with_width, structure( + list( + x = structure("a", width = 1), + y = structure("b", width = 2) + ), + row.names = c(NA, -1L), `_xportr.df_arg_` = "DOMAIN", class = "data.frame" + )) +}) + +test_that("xportr_length: Expect error when a variable is not present in metadata", { + df <- data.frame(x = "a", y = "b", z = "c") + df_meta <- data.frame( + dataset = "df", + variable = c("x", "y"), + type = c("text", "text"), + length = c(1, 2) + ) + + suppressMessages( + xportr_length(df, df_meta, verbose = "stop") + ) %>% + expect_error("doesn't exist") +}) + +test_that("xportr_length: Check if length gets imputed when a new variable is passed", { + df <- data.frame(x = "a", y = "b", z = 3) + df_meta <- data.frame( + dataset = "df", + variable = "x", + type = "text", + length = 1 + ) + + df_with_width <- suppressMessages( + xportr_length(df, df_meta) + ) + + # 200 is the imputed length for character and 8 for other data types as in impute_length() + expect_equal(c(x = 1, y = 200, z = 8), map_dbl(df_with_width, attr, "width")) + expect_equal(df_with_width, structure( + list( + x = structure("a", width = 1), + y = structure("b", width = 200), + z = structure(3, width = 8) + ), + row.names = c(NA, -1L), `_xportr.df_arg_` = "df", class = "data.frame" + )) +}) - df <- cbind(df, z = 3) - expect_error(xportr_length(df, varmeta, verbose = "stop"), "doesn't exist") -}) \ No newline at end of file +test_that("xportr_length: Expect error if domain is not a character", { + df <- data.frame(x = "a", y = "b", z = 3) + df_meta <- data.frame( + dataset = "df", + variable = "x", + type = "text", + length = 1 + ) + + expect_error( + xportr_length(df, df_meta, 1), + "`domain` must be a vector with type ." + ) + expect_error( + xportr_length(df, df_meta, NA), + "`domain` must be a vector with type ." + ) +}) + +# many tests here are more like qualification/domain testing - this section adds +# tests for `xportr_metadata()` basic functionality +# start +test_that("xportr_metadata: Check metadata interaction with other functions", { + adsl <- admiral::admiral_adsl + + var_spec <- + readxl::read_xlsx( + system.file("specs", "ADaM_admiral_spec.xlsx", package = "xportr"), + sheet = "Variables" + ) %>% + dplyr::rename(type = "Data Type") %>% + rlang::set_names(tolower) + + expect_equal( + structure(xportr_type(adsl, var_spec), `_xportr.df_metadata_` = var_spec), + suppressMessages( + xportr_metadata(adsl, var_spec) %>% xportr_type() + ) + ) + + expect_equal( + structure( + suppressMessages(xportr_length(adsl, var_spec)), + `_xportr.df_metadata_` = var_spec + ), + suppressMessages( + xportr_metadata(adsl, var_spec) %>% xportr_length() + ) + ) + + expect_equal( + structure( + suppressMessages(xportr_label(adsl, var_spec)), + `_xportr.df_metadata_` = var_spec + ), + suppressMessages( + xportr_metadata(adsl, var_spec) %>% xportr_label() + ) + ) + + expect_equal( + structure( + suppressMessages(xportr_order(adsl, var_spec)), + `_xportr.df_metadata_` = var_spec + ), + suppressMessages( + xportr_metadata(adsl, var_spec) %>% xportr_order() + ) + ) + + expect_equal( + structure( + suppressMessages(xportr_format(adsl, var_spec)), + `_xportr.df_metadata_` = var_spec + ), + suppressMessages( + xportr_metadata(adsl, var_spec) %>% xportr_format() + ) + ) +}) + +test_that("xportr_metadata: Correctly extract domain from var name", { + metadata <- data.frame( + dataset = "adlb", + variable = c("Subj", "Param", "Val", "NotUsed"), + type = c("numeric", "character", "numeric", "character"), + order = c(1, 3, 4, 2) + ) + + adlb <- data.frame( + Subj = as.character(123, 456, 789), + Different = c("a", "b", "c"), + Val = c("1", "2", "3"), + Param = c("param1", "param2", "param3") + ) + + expect_equal(attr(xportr_metadata(adlb, metadata), "_xportr.df_arg_"), "adlb") +}) +# end diff --git a/tests/testthat/test-options.R b/tests/testthat/test-options.R index c907aa41..1a13ff23 100644 --- a/tests/testthat/test-options.R +++ b/tests/testthat/test-options.R @@ -1,16 +1,12 @@ test_that("options are originally set as expected", { op <- options() - + expect_equal(op$xportr.df_domain_name, "dataset") expect_equal(op$xportr.df_label, "label") - expect_equal(op$xportr.coerse, "none") expect_equal(op$xportr.domain_name, "dataset") expect_equal(op$xportr.variable_name, "variable") expect_equal(op$xportr.type_name, "type") expect_equal(op$xportr.label, "label") expect_equal(op$xportr.length, "length") expect_equal(op$xportr.format_name, "format") - - }) - diff --git a/tests/testthat/test-order.R b/tests/testthat/test-order.R index 92b31ed0..801108c4 100644 --- a/tests/testthat/test-order.R +++ b/tests/testthat/test-order.R @@ -1,45 +1,171 @@ -# library(dplyr) -suppressWarnings({ - library(haven) - library(readxl) +test_that("xportr_order: Variable are ordered correctly for data.frame spec", { + df <- data.frame(c = 1:5, a = "a", d = 5:1, b = LETTERS[1:5]) + df_meta <- data.frame( + dataset = "df", + variable = letters[1:4], + order = 1:4 + ) + + ordered_df <- suppressMessages(xportr_order(df, df_meta)) + + expect_equal(names(ordered_df), df_meta$variable) }) -# -# #context("xportr_seq correctly order dataset according to spec") -# +test_that("xportr_order: Variable are ordered correctly when data is piped", { + df <- data.frame(c = 1:5, a = "a", d = 5:1, b = LETTERS[1:5]) + df_meta <- data.frame( + dataset = "df", + variable = letters[1:4], + order = 1:4 + ) -test_that("Variable are ordered correctly", { + ordered_df <- suppressMessages( + df %>% + xportr_order(df_meta) %>% + xportr_order(df_meta) + ) - ADAE <- read_sas(system.file("extdata", "adae.sas7bdat", package = "xportr")) - met <- read_excel(system.file("specs", "ADaM_spec.xlsx", package = "xportr"), 3) + expect_equal(names(ordered_df), df_meta$variable) +}) - withr::with_options( - list(xportr.order_name = "Order", xportr.variable_name = "Variable"), { - ADAE_xportr <- xportr_order(ADAE, metacore = met, "ADAE", verbose = "none") - } +test_that("xportr_order: Variable are ordered correctly for custom domain", { + df <- data.frame(c = 1:5, a = "a", d = 5:1, b = LETTERS[1:5]) + df_meta <- data.frame( + dataset = "DOMAIN", + variable = letters[1:4], + order = 1:4 ) - - after_names <- c("STUDYID", "USUBJID", "AEDECOD", "AESOC", "AETERM", "AESER", - "ASTDT", "AENDT", "ATOXGR", "TRT01A", "TRT01AN", "SAFFL", "SUBJID", - "WEIGHTBL", "SEX", "AGE", "AGEU", "RACE", "SITEID", "RACEN", - "ASTTM", "ADURC", "AEACN", "AEOUT", "AEREL", "ATOXGRN", "AFTRTSTC", - "AEWDFL") - expect_equal(names(ADAE_xportr), after_names) + ordered_df <- suppressMessages( + xportr_order(df, df_meta, domain = "DOMAIN") + ) + + expect_equal(names(ordered_df), df_meta$variable) }) -test_that("Domain not in character format", { - - ADAE <- read_sas(system.file("extdata", "adae.sas7bdat", package = "xportr")) - met <- read_excel(system.file("specs", "ADaM_spec.xlsx", package = "xportr"), 3) - - expect_error( - withr::with_options( - list(xportr.order_name = "Order", xportr.variable_name = "Variable"), { - ADAE_xportr <- xportr_order(ADAE, metacore = met, domain = ADAE, verbose = "none") - } +test_that("xportr_order: Variable are ordered correctly for metacore spec", { + skip_if_not_installed("metacore") + + df <- data.frame(c = 1:5, a = "a", d = 5:1, b = LETTERS[1:5]) + ordered_columns <- letters[1:4] + metacore_meta <- suppressMessages(suppressWarnings( + metacore::metacore( + ds_vars = data.frame( + dataset = "df", + variable = ordered_columns, + keep = TRUE, + key_seq = NA, + order = 1:4, + core = NA_character_, + supp_flag = NA + ), + # ds_spec required to avoid empty line output + ds_spec = dplyr::tibble( + dataset = "df" + ) + ) )) - - + + ordered_df <- suppressMessages( + xportr_order(df, metacore_meta) + ) + + expect_equal(names(ordered_df), ordered_columns) +}) + +test_that("xportr_order: Variable are ordered when custom domain_name is passed", { + df <- data.frame(c = 1:5, a = "a", d = 5:1, b = LETTERS[1:5]) + df_meta <- data.frame( + custom_domain = "df", + variable = letters[1:4], + order = 1:4 + ) + + ordered_df <- suppressMessages( + xportr_order(df, df_meta, domain = "df") + ) + + expect_equal(names(ordered_df), df_meta$variable) +}) + +test_that("xportr_order: Expect error if domain is not a character", { + df <- data.frame(c = 1:5, a = "a", d = 5:1, b = LETTERS[1:5]) + df_meta <- data.frame( + custom_domain = "df", + variable = letters[1:4], + order = 1:4 + ) + + expect_error(xportr_order(df, df_meta, domain = NA, verbose = "none")) + expect_error(xportr_order(df, df_meta, domain = 1, verbose = "none")) +}) + +test_that("xportr_order: error when metadata is not set", { + df <- data.frame(c = 1:5, a = "a", d = 5:1, b = LETTERS[1:5]) + + expect_error( + xportr_order(df), + regexp = "Metadata must be set with `metadata` or `xportr_metadata\\(\\)`" + ) +}) + +test_that("xportr_order: Variable ordering messaging is correct", { + skip_if_not_installed("haven") + skip_if_not_installed("readxl") + + require(haven, quietly = TRUE) + require(readxl, quietly = TRUE) + + df <- data.frame(c = 1:5, a = "a", d = 5:1, b = LETTERS[1:5]) + df2 <- data.frame(a = "a", z = "z") + df_meta <- data.frame( + dataset = "df", + variable = letters[1:4], + order = 1:4 + ) + + # Remove empty lines in cli theme + local_cli_theme() + + xportr_order(df, df_meta, verbose = "message") %>% + expect_message("All variables in specification file are in dataset") %>% + expect_condition("4 reordered in dataset") %>% + expect_message("Variable reordered in `.df`: `a`, `b`, `c`, and `d`") + + xportr_order(df2, df_meta, verbose = "message") %>% + expect_message("2 variables not in spec and moved to end") %>% + expect_message("Variable moved to end in `.df`: `a` and `z`") %>% + expect_message("All variables in dataset are ordered") }) +test_that("xportr_order: Metadata order columns are coersed to numeric", { + df <- data.frame(c = 1:5, a = "a", d = 5:1, b = LETTERS[1:5]) + df_meta <- data.frame( + dataset = "df", + variable = letters[1:4], + order = c("1", "2", "11", "90") + ) + + ordered_df <- suppressMessages( + xportr_order(df, df_meta) + ) + + expect_equal(names(ordered_df), df_meta$variable) +}) + +test_that("xportr_order: Gets warning when metadata has multiple rows with same variable", { + # This test uses the (2) functions below to reduce code duplication + # All `expect_*` are being called inside the functions + # + # Checks that message appears when xportr.domain_name is invalid + multiple_vars_in_spec_helper(xportr_order) %>% + # expect_message() are being caught to provide clean test without output + expect_message("All variables in specification file are in dataset") %>% + expect_message("All variables in dataset are ordered") + + # Checks that message doesn't appear when xportr.domain_name is valid + multiple_vars_in_spec_helper2(xportr_order) %>% + # expect_message() are being caught to provide clean test without output + expect_message("All variables in specification file are in dataset") %>% + expect_message("All variables in dataset are ordered") +}) diff --git a/tests/testthat/test-pipe.R b/tests/testthat/test-pipe.R new file mode 100644 index 00000000..c4d18d83 --- /dev/null +++ b/tests/testthat/test-pipe.R @@ -0,0 +1,227 @@ +test_that("xportr_*: Domain is obtained from a call without pipe", { + adsl <- minimal_table(30) + + metadata <- minimal_metadata( + dataset = TRUE, length = TRUE, label = TRUE, type = TRUE, format = TRUE, + order = TRUE + ) + + # Divert all messages to tempfile, instead of printing them + # note: be aware as this should only be used in tests that don't track + # messages + withr::local_message_sink(tempfile()) + + xportr_metadata(adsl, metadata) %>% + attr("_xportr.df_arg_") %>% + expect_equal("adsl") + xportr_label(adsl, metadata) %>% + attr("_xportr.df_arg_") %>% + expect_equal("adsl") + xportr_length(adsl, metadata) %>% + attr("_xportr.df_arg_") %>% + expect_equal("adsl") + xportr_order(adsl, metadata) %>% + attr("_xportr.df_arg_") %>% + expect_equal("adsl") + xportr_format(adsl, metadata) %>% + attr("_xportr.df_arg_") %>% + expect_equal("adsl") + xportr_type(adsl, metadata) %>% + attr("_xportr.df_arg_") %>% + expect_equal("adsl") +}) + + +test_that("xportr_*: Domain is kept in between calls", { + # Divert all messages to tempfile, instead of printing them + # note: be aware as this should only be used in tests that don't track + # messages + withr::local_message_sink(tempfile()) + + adsl <- minimal_table(30) + + metadata <- minimal_metadata( + dataset = TRUE, length = TRUE, label = TRUE, type = TRUE, format = TRUE, + order = TRUE + ) + + df2 <- adsl %>% + xportr_type(metadata) + + df3 <- df2 %>% + xportr_label(metadata) %>% + xportr_length(metadata) %>% + xportr_order(metadata) %>% + xportr_format(metadata) + + expect_equal(attr(df3, "_xportr.df_arg_"), "adsl") + + df4 <- adsl %>% + xportr_type(metadata) + + df5 <- df4 %>% + xportr_label(metadata) %>% + xportr_length(metadata) %>% + xportr_order(metadata) %>% + xportr_format(metadata) + + expect_equal(attr(df5, "_xportr.df_arg_"), "adsl") +}) + +test_that("xportr_*: Can use magrittr pipe and aquire domain from call", { + # Divert all messages to tempfile, instead of printing them + # note: be aware as this should only be used in tests that don't track + # messages + withr::local_message_sink(tempfile()) + + adsl <- minimal_table(30) + + metadata <- minimal_metadata( + dataset = TRUE, length = TRUE, label = TRUE, type = TRUE, format = TRUE, + order = TRUE + ) + + non_standard_name <- adsl + result <- non_standard_name %>% + xportr_type(metadata) %>% + xportr_label(metadata) %>% + xportr_length(metadata) %>% + xportr_order(metadata) %>% + xportr_format(metadata) %>% + xportr_df_label(metadata) + + expect_equal(attr(result, "_xportr.df_arg_"), "non_standard_name") + + # Different sequence call by moving first and last around + result2 <- non_standard_name %>% + xportr_label(metadata) %>% + xportr_length(metadata) %>% + xportr_order(metadata) %>% + xportr_df_label(metadata) %>% + xportr_type(metadata) %>% + xportr_format(metadata) + + expect_equal(attr(result2, "_xportr.df_arg_"), "non_standard_name") +}) + +test_that("xportr_*: Can use magrittr pipe and aquire domain from call (metadata)", { + # Divert all messages to tempfile, instead of printing them + # note: be aware as this should only be used in tests that don't track + # messages + withr::local_message_sink(tempfile()) + + adsl <- minimal_table(30) + + metadata <- minimal_metadata( + dataset = TRUE, length = TRUE, label = TRUE, type = TRUE, format = TRUE, + order = TRUE + ) + + non_standard_name <- adsl + result <- non_standard_name %>% + xportr_metadata(metadata) %>% + xportr_type() %>% + xportr_label() %>% + xportr_length() %>% + xportr_order() %>% + xportr_format() %>% + xportr_df_label() + + expect_equal(attr(result, "_xportr.df_arg_"), "non_standard_name") + + # Different sequence call by moving first and last around + result2 <- non_standard_name %>% + xportr_metadata(metadata) %>% + xportr_label() %>% + xportr_length() %>% + xportr_order() %>% + xportr_df_label() %>% + xportr_type() %>% + xportr_format() + + expect_equal(attr(result2, "_xportr.df_arg_"), "non_standard_name") +}) + +test_that("xportr_*: Can use R native pipe (R>4.1) and aquire domain from call", { + skip_if( + compareVersion(glue("{R.version$major}.{R.version$minor}"), "4.1.0") < 0, + "R Version doesn't support native pipe (<4.1)" + ) + + # Divert all messages to tempfile, instead of printing them + # note: be aware as this should only be used in tests that don't track + # messages + withr::local_message_sink(tempfile()) + + adsl <- minimal_table(30) + + metadata <- minimal_metadata( + dataset = TRUE, length = TRUE, label = TRUE, type = TRUE, format = TRUE, + order = TRUE + ) + + non_standard_name_native <- adsl + result <- non_standard_name_native |> + xportr_type(metadata) |> + xportr_label(metadata) |> + xportr_length(metadata) |> + xportr_order(metadata) |> + xportr_format(metadata) |> + xportr_df_label(metadata) + + expect_equal(attr(result, "_xportr.df_arg_"), "non_standard_name_native") + + # Different sequence call by moving first and last around + result2 <- non_standard_name_native |> + xportr_label(metadata) |> + xportr_length(metadata) |> + xportr_order(metadata) |> + xportr_df_label(metadata) |> + xportr_type(metadata) |> + xportr_format(metadata) + + expect_equal(attr(result2, "_xportr.df_arg_"), "non_standard_name_native") +}) + +test_that("xportr_*: Can use R native pipe (R>4.1) and aquire domain from call (metadata)", { + skip_if( + compareVersion(glue("{R.version$major}.{R.version$minor}"), "4.1.0") < 0, + "R Version doesn't support native pipe (<4.1)" + ) + + # Divert all messages to tempfile, instead of printing them + # note: be aware as this should only be used in tests that don't track + # messages + withr::local_message_sink(tempfile()) + + adsl <- minimal_table(30) + + metadata <- minimal_metadata( + dataset = TRUE, length = TRUE, label = TRUE, type = TRUE, format = TRUE, + order = TRUE + ) + + non_standard_name_native <- adsl + result <- non_standard_name_native |> + xportr_metadata(metadata) |> + xportr_type() |> + xportr_label() |> + xportr_length() |> + xportr_order() |> + xportr_format() |> + xportr_df_label() + + expect_equal(attr(result, "_xportr.df_arg_"), "non_standard_name_native") + + # Different sequence call by moving first and last around + result2 <- non_standard_name_native |> + xportr_metadata(metadata) |> + xportr_label() |> + xportr_length() |> + xportr_order() |> + xportr_df_label() |> + xportr_type() |> + xportr_format() + + expect_equal(attr(result2, "_xportr.df_arg_"), "non_standard_name_native") +}) diff --git a/tests/testthat/test-pkg-load.R b/tests/testthat/test-pkg-load.R new file mode 100644 index 00000000..82341de1 --- /dev/null +++ b/tests/testthat/test-pkg-load.R @@ -0,0 +1,21 @@ +test_that(".onLoad: Unset options get initialised on package load with defaults", { + skip_if(getOption("testthat_interactive")) + withr::with_options( + list(xportr.df_domain_name = NULL), + { + expect_no_error(.onLoad()) + expect_equal(getOption("xportr.df_domain_name"), "dataset") + } + ) +}) + +test_that(".onLoad: Initialised options are retained and not overwritten", { + skip_if(getOption("testthat_interactive")) + withr::with_options( + list(xportr.df_domain_name = "custom_domain"), + { + expect_no_error(.onLoad()) + expect_equal(getOption("xportr.df_domain_name"), "custom_domain") + } + ) +}) diff --git a/tests/testthat/test-support-for-tests.R b/tests/testthat/test-support-for-tests.R new file mode 100644 index 00000000..5e4136ce --- /dev/null +++ b/tests/testthat/test-support-for-tests.R @@ -0,0 +1,32 @@ +test_that("minimal_table: builds minimal data frame with data", { + minimal_table(31) %>% + NROW() %>% + expect_equal(31) + + (colnames(minimal_table(31)) %in% c("x", "y")) %>% + all() %>% + expect_true() +}) + +test_that("minimal_metadata: builds minimal metadata data frame", { + sample_metadata <- minimal_metadata( + dataset = TRUE, + length = TRUE, + label = TRUE, + type = TRUE, + format = TRUE, + order = TRUE + ) + + (sample_metadata$variable %in% c("x", "y", "z", "a", "b", "c", "d")) %>% + all() %>% + expect_true() +}) + +test_that("minimal_metadata: columns in minimal_table are all in metadata", { + sample_data <- minimal_table(31, cols = c("x", "y", "z", "a", "b", "c", "d")) + sample_metadata <- minimal_metadata(dataset = TRUE) + (colnames(sample_data) %in% sample_metadata$variable) %>% + all() %>% + expect_true() +}) diff --git a/tests/testthat/test-type.R b/tests/testthat/test-type.R index b7a9124a..3f936f87 100644 --- a/tests/testthat/test-type.R +++ b/tests/testthat/test-type.R @@ -1,32 +1,280 @@ - meta_example <- data.frame( dataset = "df", variable = c("Subj", "Param", "Val", "NotUsed"), - type = c("numeric", "character", "numeric", "character") + type = c("numeric", "character", "numeric", "character"), + format = NA ) df <- data.frame( - Subj = as.character(123, 456, 789), - Different = c("a", "b", "c"), - Val = c("1", "2", "3"), - Param = c("param1", "param2", "param3") + Subj = as.character(123, 456, 789), + Different = c("a", "b", "c"), + Val = c("1", "2", "3"), + Param = c("param1", "param2", "param3") ) -test_that("variable types are coerced as expected and can raise messages", { +test_that("xportr_type: NAs are handled as expected", { + # Namely that "" isn't converted to NA or vice versa + # Numeric columns will become NA but that is the nature of as.numeric + df <- data.frame( + Subj = as.character(c(123, 456, 789, "", NA, NA_integer_)), + Different = c("a", "b", "c", "", NA, NA_character_), + Val = c("1", "2", "3", "", NA, NA_character_), + Param = c("param1", "param2", "param3", "", NA, NA_character_) + ) + meta_example <- data.frame( + dataset = "df", + variable = c("Subj", "Param", "Val", "NotUsed"), + type = c("numeric", "character", "numeric", "character"), + format = NA + ) + + df2 <- suppressMessages( + xportr_type(df, meta_example) + ) + + expect_equal( + df2, + structure( + list( + Subj = c(123, 456, 789, NA, NA, NA), + Different = c("a", "b", "c", "", NA, NA), + Val = c(1, 2, 3, NA, NA, NA), + Param = c("param1", "param2", "param3", "", NA, NA) + ), + row.names = c(NA, -6L), + `_xportr.df_arg_` = "df", + class = "data.frame" + ) + ) +}) + +test_that("xportr_type: Variable types are coerced as expected and can raise messages", { + # Remove empty lines in cli theme + local_cli_theme() + + (df2 <- xportr_type(df, meta_example)) %>% + expect_message("Variable type mismatches found.") %>% + expect_message("[0-9+] variables coerced") - expect_message(df2 <- xportr_type(df, meta_example), - "-- Variable type mismatches found. --") - - expect_equal(purrr::map_chr(df2, class), c(Subj = "numeric", Different = "character", - Val = "numeric", Param = "character")) + expect_equal(purrr::map_chr(df2, class), c( + Subj = "numeric", Different = "character", + Val = "numeric", Param = "character" + )) expect_error(xportr_type(df, meta_example, verbose = "stop")) - expect_warning(df3 <- xportr_type(df, meta_example, verbose = "warn")) - expect_equal(purrr::map_chr(df3, class), c(Subj = "numeric", Different = "character", - Val = "numeric", Param = "character")) + (df3 <- suppressMessages(xportr_type(df, meta_example, verbose = "warn"))) %>% + expect_warning() + + expect_equal(purrr::map_chr(df3, class), c( + Subj = "numeric", Different = "character", + Val = "numeric", Param = "character" + )) + + # Ignore other messages + suppressMessages( + (df4 <- xportr_type(df, meta_example, verbose = "message")) %>% + expect_message("Variable type\\(s\\) in dataframe don't match metadata") + ) + + expect_equal(purrr::map_chr(df4, class), c( + Subj = "numeric", Different = "character", + Val = "numeric", Param = "character" + )) +}) + +test_that("xportr_metadata: Var types coerced as expected and raise messages", { + # Remove empty lines in cli theme + local_cli_theme() + + ( + df2 <- xportr_metadata(df, meta_example) %>% + xportr_type() + ) %>% + expect_message("Variable type mismatches found.") %>% + expect_message("[0-9+] variables coerced") + + expect_equal(purrr::map_chr(df2, class), c( + Subj = "numeric", Different = "character", + Val = "numeric", Param = "character" + )) + + suppressMessages( + xportr_metadata(df, meta_example) %>% xportr_type(verbose = "stop") + ) %>% + expect_error() + + suppressMessages( + df3 <- xportr_metadata(df, meta_example) %>% xportr_type(verbose = "warn") + ) %>% + expect_warning() + + expect_equal(purrr::map_chr(df3, class), c( + Subj = "numeric", Different = "character", + Val = "numeric", Param = "character" + )) + + suppressMessages({ + ( + df4 <- xportr_metadata(df, meta_example) + %>% xportr_type(verbose = "message") + ) %>% + expect_message("Variable type\\(s\\) in dataframe don't match metadata: `Subj` and `Val`") + }) + + expect_equal(purrr::map_chr(df4, class), c( + Subj = "numeric", Different = "character", + Val = "numeric", Param = "character" + )) +}) + +test_that("xportr_type: Variables retain column attributes, besides class", { + adsl <- dplyr::tibble( + USUBJID = c(1001, 1002, 1003), + SITEID = c(001, 002, 003), + ADATE = readr::parse_date(c("2023-04-11", "2023-04-12", "2023-04-13")), + AGE = c(63, 35, 27), + SEX = c("M", "F", "M") + ) + + metadata <- dplyr::tibble( + dataset = "adsl", + variable = c("USUBJID", "SITEID", "ADATE", "AGE", "SEX"), + label = c("Unique Subject Identifier", "Study Site Identifier", "Study Dates", "Age", "Sex"), + type = c("character", "character", "character", "numeric", "character"), + length = c(10, 10, 10, 8, 10), + format = c(NA, NA, "DATE9.", NA, NA) + ) + + # Remove empty lines in cli theme + local_cli_theme() + + # Divert all messages to tempfile, instead of printing them + # note: be aware as this should only be used in tests that don't track + # messages + withr::local_message_sink(tempfile()) + + df_type_label <- adsl %>% + xportr_type(metadata) %>% + xportr_label(metadata) %>% + xportr_length(metadata) %>% + xportr_format(metadata) + + df_label_type <- adsl %>% + xportr_label(metadata) %>% + xportr_length(metadata) %>% + xportr_format(metadata) %>% + xportr_type(metadata) + + expect_equal(df_type_label, df_label_type) +}) + +test_that("xportr_type: expect error when domain is not a character", { + df <- data.frame(x = 1, y = 2) + df_meta <- data.frame( + variable = c("x", "y"), + type = "text", + label = c("X Label", "Y Label"), + length = c(1, 2), + common = NA_character_, + format = c("date9.", "datetime20.") + ) + expect_error(xportr_type(df, df_meta, domain = 1)) + expect_error(xportr_type(df, df_meta, domain = NA)) +}) + +test_that("xportr_type: works fine from metacore spec", { + skip_if_not_installed("metacore") + + df <- data.frame(x = 1, y = 2) + metacore_meta <- suppressMessages(suppressWarnings( + metacore::metacore( + var_spec = data.frame( + variable = c("x", "y"), + type = "text", + label = c("X Label", "Y Label"), + length = c(1, 2), + common = NA_character_, + format = c("date9.", "datetime20.") + ) + ) + )) + processed_df <- suppressMessages( + xportr_type(df, metacore_meta) + ) + expect_equal(processed_df$x, "1") +}) + +test_that("xportr_type: error when metadata is not set", { + expect_error( + xportr_type(df), + regexp = "Metadata must be set with `metadata` or `xportr_metadata\\(\\)`" + ) +}) + +test_that("xportr_type: date variables are not converted to numeric", { + df <- data.frame(RFICDT = as.Date("2017-03-30"), RFICDTM = as.POSIXct("2017-03-30")) + metacore_meta <- suppressWarnings( + metacore::metacore( + var_spec = data.frame( + variable = c("RFICDT", "RFICDTM"), + type = "integer", + label = c("RFICDT Label", "RFICDTM Label"), + length = c(1, 2), + common = NA_character_, + format = c("date9.", "datetime20.") + ) + ) + ) + expect_message( + { + processed_df <- xportr_type(df, metacore_meta) + }, + NA + ) + expect_equal(lapply(df, class), lapply(processed_df, class)) + expect_equal(df$RFICDT, processed_df$RFICDT) + expect_equal(df$RFICDTM, processed_df$RFICDTM) + + xportr_write(processed_df, file.path(tempdir(), "dfdates.xpt")) + df_xpt <- read_xpt(file.path(tempdir(), "dfdates.xpt")) + + expect_equal(lapply(df, class), lapply(df_xpt, class)) + expect_equal(df$RFICDT, df_xpt$RFICDT, ignore_attr = TRUE) + expect_equal(as.character(df$RFICDTM), as.character(df_xpt$RFICDTM), ignore_attr = TRUE) + + metadata <- data.frame( + dataset = c("adsl", "adsl", "adsl", "adsl"), + variable = c("USUBJID", "DMDTC", "RFICDT", "RFICDTM"), + type = c("text", "date", "integer", "integer"), + format = c(NA, NA, "date9.", "datetime15.") + ) + + adsl_original <- data.frame( + USUBJID = c("test1", "test2"), + DMDTC = c("2017-03-30", "2017-01-08"), + RFICDT = c("2017-03-30", "2017-01-08"), + RFICDTM = c("2017-03-30", "2017-01-08") + ) + + + adsl_original$RFICDT <- as.Date(adsl_original$RFICDT) + adsl_original$RFICDTM <- as.POSIXct(adsl_original$RFICDTM) + + expect_message(adsl_xpt2 <- adsl_original %>% + xportr_type(metadata), NA) + + attr(adsl_original, "_xportr.df_arg_") <- "adsl_original" - expect_message(df4 <- xportr_type(df, meta_example, verbose = "message")) - expect_equal(purrr::map_chr(df4, class), c(Subj = "numeric", Different = "character", - Val = "numeric", Param = "character"))}) + expect_equal(adsl_original, adsl_xpt2) +}) +test_that("xportr_type: Gets warning when metadata has multiple rows with same variable", { + # This test uses the (2) functions below to reduce code duplication + # All `expect_*` are being called inside the functions + # + # Checks that message appears when xportr.domain_name is invalid + multiple_vars_in_spec_helper(xportr_type) + # Checks that message doesn't appear when xportr.domain_name is valid + multiple_vars_in_spec_helper2(xportr_type) +}) diff --git a/tests/testthat/test-utils-xportr.R b/tests/testthat/test-utils-xportr.R index ad8d3776..4167b698 100644 --- a/tests/testthat/test-utils-xportr.R +++ b/tests/testthat/test-utils-xportr.R @@ -2,11 +2,112 @@ test_that("Get magrittr lhs side value", { x <- function(df, var) { get_pipe_call() } - + y <- function(df) { get_pipe_call() } - - expect_equal({mtcars %>% x("cyl")}, "mtcars") - expect_equal({mtcars %>% subset(cyl == 6) %>% x("cyl")}, "mtcars") + + expect_equal( + { + mtcars %>% x("cyl") + }, + "mtcars" + ) + expect_equal( + { + mtcars %>% + subset(cyl == 6) %>% + x("cyl") + }, + "mtcars" + ) +}) + + +test_that("fmt_vars: the message returns properly formatted variables", { + expect_equal(fmt_vars(4), "Variable 4") + expect_equal(fmt_vars(4:6), "Variables 4, 5, and 6") +}) + +test_that("fmt_labs: the message returns properly formatted labels", { + expect_equal(fmt_labs(4), "Label '=4'") + expect_equal(fmt_labs(4:6), "Labels '=4', '=5', and '=6'") +}) + +test_that("xpt_validate_var_names: Get error message when the variable is over 8 characters", { + expect_equal( + xpt_validate_var_names(c("FOO", "BAR", "ABCDEFGHI")), + "Variable `ABCDEFGHI` must be 8 characters or less." + ) +}) + +test_that("xpt_validate_var_names: Get error message when the variable does not start with a letter", { + expect_equal( + xpt_validate_var_names(c("FOO", "2BAR")), + "Variable `2BAR` must start with a letter." + ) +}) + +test_that("xpt_validate_var_names: Get error message when the variable contains non-ASCII characters or underscore", { + expect_equal( + xpt_validate_var_names(c("FOO", "BAR", "FOO-BAR")), + c( + "Variable `FOO-BAR` cannot contain any non-ASCII, symbol or underscore characters.", + "Variable `FOO-BAR` cannot contain any lowercase characters." + ) + ) + expect_equal( + xpt_validate_var_names(c("FOO", "BAR", "FOO_BAR")), + c( + "Variable `FOO_BAR` cannot contain any non-ASCII, symbol or underscore characters.", + "Variable `FOO_BAR` cannot contain any lowercase characters." + ) + ) +}) + +test_that("xpt_validate_var_names: Get error message when tje variable contains lowercase character", { + xpt_validate_var_names(c("FOO", "bar")) + expect_equal( + xpt_validate_var_names(c("FOO", "bar")), + "Variable `bar` cannot contain any lowercase characters." + ) +}) + +test_that("xpt_validate: Get error message when the label contains over 40 characters", { + df <- data.frame(A = 1, B = 2) + long_label <- paste(rep("a", 41), collapse = "") + attr(df$A, "label") <- long_label + expect_equal( + xpt_validate(df), + paste0("Label 'A=", long_label, "' must be 40 characters or less.") + ) +}) + +test_that("xpt_validate: Get error message when the variable type is invalid", { + df <- data.frame(A = 1, B = 2) + attr(df$A, "SAStype") <- "integer" + attr(df$B, "SAStype") <- "list" + expect_equal( + xpt_validate(df), + "Variables `A` and `B` must have a valid type." + ) +}) + +test_that("xpt_validate: Doesn't error out with iso8601 format", { + df <- data.frame(A = 1, B = 2) + attr(df$A, "format.sas") <- "E8601LX." + attr(df$B, "format.sas") <- "E8601DX20." + expect_equal( + xpt_validate(df), + character(0) + ) +}) + +test_that("xpt_validate: Get error message when the label contains non-ASCII, symbol or special characters", { + df <- data.frame(A = 1, B = 2) + attr(df$A, "label") <- "fooรงbar" + expect_equal( + xpt_validate(df), + "Label 'A=fooรงbar' cannot contain any non-ASCII, symbol or special characters." + ) }) diff --git a/tests/testthat/test-var_checks.R b/tests/testthat/test-var_checks.R deleted file mode 100644 index 6ad4615b..00000000 --- a/tests/testthat/test-var_checks.R +++ /dev/null @@ -1,135 +0,0 @@ -# test_that("Variables have length <= 8", { -# -# library(haven) -# -# path <- system.file("extdata", "adsl.sas7bdat", package = "xportr") -# adsl <- haven::read_sas(path) -# adsl_renamed <- adsl %>% rename("STUDYIDSTUDYID" = STUDYID) -# -# test_var_len_exp <- data.frame( value = -# c("STUDYIDSTUDYID" -# ), -# var_length = -# c(14L -# )) -# expect_identical(xpt_check_var_length(adsl_renamed), test_var_len_exp) -# -# -# }) -# -# test_that("Variable with lower case are found", { -# -# library(haven) -# -# path <- system.file("extdata", "adsl.sas7bdat", package = "xportr") -# adsl <- haven::read_sas(path) -# adsl_lower <- adsl %>% rename_with(tolower) %>% select(studyid, sex, randdt) -# adsl_removed <- adsl %>% select(-STUDYID, -SEX, -RANDDT) -# adsl_cmb <- bind_cols(adsl_lower, adsl_removed) -# -# test_vars_exp <- data.frame( value = -# c("studyid", -# "sex", -# "randdt" -# ), -# -# flag = -# -# c(FALSE, -# FALSE, -# FALSE -# )) -# -# expect_identical(xpt_check_var_case(adsl_cmb), test_vars_exp) -# -# }) -# -# # Need a test for no issues found with variable case -# -# -# test_that("Found variable labels length that are too long!", { -# -# library(haven) -# -# path <- system.file("extdata", "adsl.sas7bdat", package = "xportr") -# adsl <- haven::read_sas(path) -# adsl_lbls <- adsl %>% add_labels( -# USUBJID = "Unique Subject ID's", -# AGE = "Age of Subject at Start of Study Age of Subject at Start of Study") -# -# test_lbls_exp <- data.frame( name = -# c("AGE" -# ), -# value = -# c("Age of Subject at Start of Study Age of Subject at Start of Study" -# ), -# label_length = -# c(65L)) -# -# expect_identical(xpt_check_label_length(adsl_lbls), test_lbls_exp) -# -# }) -# -# # Need a test for no issues found with variable case -# -# test_that("non-ASCII Characters found in Variable Names", { -# -# test_ascii_data <- data.frame( dum = -# c("Subj1", -# "Subj2", -# "Subj3" -# ), -# `Test` = -# c("Test1", -# "Test2", -# "Test3" -# )) -# -# colnames(test_ascii_data) <- c(stri_unescape_unicode('\\u00c0'), "Test") -# -# test_ascii_exp <- data.frame( value = -# c(stri_unescape_unicode('\\u00c0') -# ), -# flag = -# c(strtrim("non-ASCII Found", 15) -# )) -# -# expect_equal(xpt_check_ascii_vars(test_ascii_data), test_ascii_exp) -# -# }) -# -# -# test_that("non-ASCII Characters found in Variable Labels", { -# -# test_ascii_data <- data.frame(dum = -# c("Subj1", -# "Subj2", -# "Subj3" -# ), -# `Test` = -# c("Test1", -# "Test2", -# "Test3" -# )) -# -# test_ascii_data_lbls <- test_ascii_data %>% add_labels( -# dum = paste0("Unique Subject ", stri_unescape_unicode('\\u00c0'), "Ds"), -# `Team Members` = "Test") -# -# colnames(test_ascii_data_lbls) <- c(stri_unescape_unicode('\\u00c0'), "Test") -# -# -# -# test_ascii_lbl_exp <- data.frame( -# name = -# c(stri_unescape_unicode('\\u00c0')), -# value = -# c(paste0("Unique Subject ", stri_unescape_unicode('\\u00c0'), "Ds")), -# flag = -# c(strtrim("non-ASCII Found", 15))) -# -# -# expect_equivalent(xpt_check_ascii_lbls(test_ascii_data_lbls), test_ascii_lbl_exp) -# -# }) -# diff --git a/tests/testthat/test-write.R b/tests/testthat/test-write.R index f97326c9..ba165e3c 100644 --- a/tests/testthat/test-write.R +++ b/tests/testthat/test-write.R @@ -1,66 +1,109 @@ -test_that("SAS Transport file", { +data_to_save <- dplyr::tibble(X = c(1, 2, NA), Y = c("a", "", "c"), Z = c(1, 2, 3)) +test_that("xportr_write: exported data can be saved to a file", { tmpdir <- tempdir() tmp <- file.path(tmpdir, "xyz.xpt") - + on.exit(unlink(tmpdir)) - - df <- data.frame(X = c(1, 2, NA), Y = c("a", "", "c"), Z = c(1, 2, 3)) - #SASxport::SASformat(df$x, "format") <- "date7." - attr(df$X, "label") <- "foo" - attr(df$Y, "label") <- "bar" - attr(df$Z, "label") <- "baz" + xportr_write(data_to_save, path = tmp) + expect_equal(read_xpt(tmp), data_to_save) +}) - xportr_write(df, path = tmp) - #expect_output(str(read_xpt(tmp)), "$ X: labelled, format", fixed =TRUE) - # expect_output(str(read_xpt(tmp)), "$ Y: 'labelled' chr", fixed = TRUE) - # expect_output(str(SASxport::read.xport(tmp)), "$ Z: 'labelled' int", fixed = TRUE) +test_that("xportr_write: exported data can be saved to a file with a label", { + tmpdir <- tempdir() + tmp <- file.path(tmpdir, "xyz.xpt") - xportr_write(df, path = tmp, label = "Lorem ipsum dolor sit amet") + on.exit(unlink(tmpdir)) + + xportr_write(data_to_save, path = tmp, label = "Lorem ipsum dolor sit amet") expect_output(str(read_xpt(tmp)), "Lorem ipsum dolor sit amet") +}) - xportr_write(df, path = tmp, label = "Lorem ipsum dolor sit amet") - expect_error( - xportr_write(df, tmp, label = "Lorizzle ipsizzle dolizzle pizzle go to hizzle"), - "must be 40 characters or less") - expect_error(xportr_write(df, tmp, label = "Lorizzle ipsizzle dolizzl\xe7 pizzle")) - - # df <- data.frame(loremipsum = "a", y_ = 1) - # attr(df$y_, "label") <- "var2" - # expect_warning( - # xportr_write(df, tmp), - # "Truncated 1 long names to 8 characters.") +test_that("xportr_write: expect error when invalid multibyte string is passed in label", { + tmpdir <- tempdir() + tmp <- file.path(tmpdir, "xyz.xpt") + + on.exit(unlink(tmpdir)) + + expect_error(xportr_write(data_to_save, tmp, label = "Lorizzle ipsizzle dolizzl\xe7 pizzle")) +}) + +test_that("xportr_write: expect error when file name is over 8 characters long", { + tmpdir <- tempdir() + tmp <- file.path(tmpdir, paste0(paste(letters[1:9], collapse = ""), ".xpt")) + + on.exit(unlink(tmpdir)) + + expect_error(xportr_write(data_to_save, tmp, label = "asdf")) +}) + +test_that("xportr_write: expect error when file name contains non-ASCII symbols or special characters", { + tmpdir <- tempdir() + tmp <- file.path(tmpdir, ".xpt") + + on.exit(unlink(tmpdir)) + + expect_error(xportr_write(data_to_save, tmp, label = "asdf")) +}) + +test_that("xportr_write: expect error when label contains non-ASCII symbols or special characters", { + tmpdir <- tempdir() + tmp <- file.path(tmpdir, "xyz.xpt") + + on.exit(unlink(tmpdir)) + + expect_error(xportr_write(data_to_save, tmp, label = "รงtestรง")) }) -test_that("Error message given if file name is greater than 8 characters",{ +test_that("xportr_write: expect error when label is over 40 characters", { + tmpdir <- tempdir() + tmp <- file.path(tmpdir, "xyz.xpt") + + on.exit(unlink(tmpdir)) + + expect_error(xportr_write(data_to_save, tmp, label = paste(rep("a", 41), collapse = ""))) +}) +test_that("xportr_write: expect error when an xpt validation fails with strict_checks set to TRUE", { tmpdir <- tempdir() - tmp <- file.path(tmpdir, "abc.xpt") + tmp <- file.path(tmpdir, "xyz.xpt") + attr(data_to_save$X, "format.sas") <- "foo" on.exit(unlink(tmpdir)) - nameover8 <- data.frame(a = c(1, 2, NA), - b = c("a", "", "c"), - c = c(1, 2, 3)) + expect_error(xportr_write(data_to_save, tmp, label = "label", strict_checks = TRUE)) +}) - expect_error(xportr_write(df, path = tmp)) - +test_that("xportr_write: expect warning when an xpt validation fails with strict_checks set to FALSE", { + tmpdir <- tempdir() + tmp <- file.path(tmpdir, "xyz.xpt") + attr(data_to_save$X, "format.sas") <- "foo" + + on.exit(unlink(tmpdir)) + expect_warning(xportr_write(data_to_save, tmp, label = "label", strict_checks = FALSE)) }) -test_that("Format message given if unexpected formats", { +test_that("xportr_write: expect warning when an xpt validation fails with strict_checks set to FALSE", { tmpdir <- tempdir() tmp <- file.path(tmpdir, "xyz.xpt") - + attr(data_to_save$X, "format.sas") <- "foo" + on.exit(unlink(tmpdir)) - - df <- data.frame(USUBJID = c("1001", "1002", "10003"), - AGE = c("M", "F", "M"), - BIRTHDT = as.Date(c("2001-01-01", "1997-11-11", "1995-12-12"), "%Y-%m-%d")) - - # Forget the period in date9. - attr(df$BIRTHDT, "format.sas") <- "date9" - - expect_error(xportr_write(df, tmp)) + + expect_warning(xportr_write(data_to_save, tmp, label = "label", strict_checks = FALSE)) +}) + +test_that("xportr_write: Capture errors by haven and report them as such", { + tmpdir <- tempdir() + tmp <- file.path(tmpdir, "xyz.xpt") + attr(data_to_save$X, "format.sas") <- "E8601LXw.asdf" + + on.exit(unlink(tmpdir)) + + expect_error( + suppressWarnings(xportr_write(data_to_save, tmp, label = "label", strict_checks = FALSE)), + "Error reported by haven" + ) }) diff --git a/vignettes/deepdive.Rmd b/vignettes/deepdive.Rmd new file mode 100644 index 00000000..8f1ccac0 --- /dev/null +++ b/vignettes/deepdive.Rmd @@ -0,0 +1,448 @@ +--- +title: "Deep Dive into xportr" +output: + rmarkdown::html_vignette: + toc: true + check_title: TRUE +vignette: > + %\VignetteIndexEntry{Deep Dive into xportr} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = " " +) + +options(cli.num_colors = 1) + +library(DT) +``` + +```{r, include=FALSE} +# Used to control str() output later on + +local({ + hook_output <- knitr::knit_hooks$get("output") + knitr::knit_hooks$set(output = function(x, options) { + if (!is.null(options$max.height)) { + options$attr.output <- c( + options$attr.output, + sprintf('style="max-height: %s;"', options$max.height) + ) + } + hook_output(x, options) + }) +}) +``` + +```{css, echo=FALSE} +/* Used to control DT outputs */ +.text-left { + text-align: left; +} +``` + +# Introduction + +This vignette will explore in detail all the possibilities of the `{xportr}` package for applying information from a metadata object to an R created dataset using the core `{xportr}` functions. + +We will also explore the following: + +* What goes in a Submission to a Health Authority, and what role does `{xportr}` play in that Submission? +* What is `{xportr}` validating behind the scenes? +* Breakdown of `{xportr}` and a ADaM dataset specification file. +* Using `options()` and `xportr_metadata()` to enhance your `{xportr}` experience. +* Understanding the warning and error messages for each `{xportr}` function. +* A brief discussion on future work. + + +**NOTE:** We use the phrase _metadata object_ throughout this package. A _metadata object_ can either be a specification file read into R as a dataframe or a `{metacore}` object. The _metadata object_ created via the `{metacore}` package has additional features not covered here, but at its core is using a specification file. However, `{xportr}` will work with either a dataframe or a `{metacore}` object. + +# What goes in a Submission to a Health Authority? + +Quite a bit! We will focus on the data deliverables and supporting documentation needed for a successful submission to a Health Authority and how `{xportr}` can play a key role. We will briefly look at three parts: + +1) Study Data Standardization Plan +2) SDTM Data Package +3) ADaM Data Package + +## Study Data Standardization Plan + +The Study Data Standardization Plan (SDSP) establishes and documents a plan for describing the data standardization approach for clinical and nonclinical studies within a development program. The SDSP also assists the FDA in identifying potential data standardization issues early in the development program. We hope the brevity of this section does not belie the huge importance of this document. Please see [Study Data Standardisation Plan (SDSP) Package](https://advance.phuse.global/display/WEL/Study+Data+Standardisation+Plan+%28SDSP%29+Package) maintained by the [PHUSE working group](https://advance.phuse.global/display/WEL/Welcome+to+the+PHUSE+Advance+Hub). However, we want to focus more on the actual data and how `{xportr}` can play a role in the submission. + +## SDTM and ADaM Data Packages + +__SDTM:__ The primary pieces of the SDTM package are the SDTM annotated case report forms (acrf.pdf), the data definitions document (define.xml), the Study Data Reviewer's Guide (sdrg.pdf) and the datasets in xpt Version 5 format. The Version 5 xpt file is the **required** submission format for all datasets going to the Health Authorities. + +__ADaM:__ The key components of the ADaM package are very similar to SDTM package with a few additions: define.xml, Analysis Study Data Reviewer's Guide (adrg.pdf), Analysis Results Metadata (analysis-results-metadata.pdf) and datasets as Version 5 xpt format. + +As both Data Packages need compliant `xpt` files, we feel that `{xportr}` can play a pivotal role here. The core functions in `{xportr}` can be used to apply information from the _metadata object_ to the datasets giving users feedback on the quality of the metadata and data. `xportr_write()` can then be used to write out the final dataset as an `xpt` file, which can be submitted to a Health Authority. + +## What is `{xportr}` validating in these Data Packages? + +The `xpt` Version 5 files form the backbone of any successful Submission and are govern by quite a lot of rules and suggested guidelines. As you are preparing your data packages for submission the suite of core `{xportr}` functions, plus `xportr_write()`, helps to check that your datasets are submission compliant. The package checks many of the latest rules laid out in the [Study Data Technical Conformance Guide](https://www.fda.gov/regulatory-information/search-fda-guidance-documents/study-data-technical-conformance-guide-technical-specifications-document), but please note that it is not yet an exhaustive list of checks. We envision that users are also submitting their `xpts` and metadata to additional validation software. + +Each of the core `{xportr}` functions for applying labels, types, formats, order and lengths provides feedback to users on submission compliance. However, a final check is implemented when `xportr_write()` is called to create the `xpt`. `xportr_write()` calls [`xpt_validate()`](https://github.com/atorus-research/xportr/blob/231e959b84aa0f1e71113c85332de33a827e650a/R/utils-xportr.R#L174), which is a behind the scenes/non-exported function that does a final check for compliance. At the time of `{xportr} v0.3.0` we are checking the following when a user writes out an `xpt` file.: + +validate + +# {xportr} in action + +In this section, we are going to explore the 5 core `{xportr}` functions using: + +* `xportr::adsl` - An ADSL ADaM dataset from the Pilot 3 Submission to the FDA +* `xportr::var_spec` - The ADSL ADaM Specification File from the Pilot 3 Submission to the FDA + +We will focus on warning and error messaging with contrived examples from these functions by manipulating either the datasets or the specification files. + +**NOTE:** We have made the ADSL and Spec available in this package. Users can find additional datasets and specification files on our [repo](https://github.com/atorus-research/xportr) in the `example_data_specs` folder. This is to keep the package to a minimum size. + + +## Using `options()` and `xportr_metadata()` to enhance your experience. + +Before we dive into the functions, we want to point out some quality of life utilities to make your `xpt` generation life a little bit easier. + +* `options()` +* `xportr_metadata()` + +**NOTE:** As long as you have a well-defined _metadata object_ you do NOT need to use `options()` or `xportr_metadata()`, but we find these handy to use and think they deserve a quick mention! + +## You got `options()` + +`{xportr}` is built with certain assumptions around specification column names and information in those columns. We have found that each company specification file can differ slightly from our assumptions. For example, one company might call a column `Variables`, another `Variable` and another `variables`. Rather than trying to regex ourselves out of this situation, we have introduced `options()`. `options()` allows users to control those assumptions inside `{xportr}` functions based on their needs. + +Let's take a look at our example specification file names available in this package. We can see that all the columns start with an upper case letter and have spaces in several of them. We could convert all the column names to lower case and deal with the spacing using some `{dplyr}` functions or base R, or we could just use `options()`! + +```{r, message = FALSE} +library(rlang) +library(xportr) +library(dplyr) +library(haven) + +colnames(var_spec) +``` + +By using `options()` at the beginning of our script we can tell `{xportr}` what the valid names are (see chunk below). Please note that before we set the options the package assumed every thing was in lowercase and there were no spaces in the names. After running `options()`, `{xportr}` sees the column `Variable` as the valid name rather than `variable`. You can inspect [`zzz.R`](https://github.com/atorus-research/xportr/blob/main/R/zzz.R) to look at additional options. + +```{r, eval = FALSE} +options( + xportr.variable_name = "Variable", + xportr.label = "Label", + xportr.type_name = "Data Type", + xportr.format = "Format", + xportr.length = "Length", + xportr.order_name = "Order" +) +``` + +## Are we being too verbose? + +One final note on `options()`. 4 of the core `{xportr}` functions have the ability to set messaging as `"none", "message", "warn", "stop"`. Setting each of these in all your calls can be a bit repetitive. You can use `options()` to set these at a higher level and avoid this repetition. + +```{r, eval = FALSE} +# Default +options( + xportr.format_verbose = "none", + xportr.label_verbose = "none", + xportr.length_verbose = "none", + xportr.type_verbose = "none", +) + +# Will send Warning Message to Console +options( + xportr.format_verbose = "warn", + xportr.label_verbose = "warn", + xportr.length_verbose = "warn", + xportr.type_verbose = "warn", +) +``` + +## Going meta + +Each of the core `{xportr}` functions requires several inputs: A valid dataframe, a metadata object and a domain name, along with optional messaging. For example, here is a simple call using all of the functions. As you can see, a lot of information is repeated in each call. + +```{r, eval = FALSE} +adsl %>% + xportr_type(var_spec, "ADSL", "message") %>% + xportr_length(var_spec, "ADSL", "message") %>% + xportr_label(var_spec, "ADSL", "message") %>% + xportr_order(var_spec, "ADSL", "message") %>% + xportr_format(var_spec, "ADSL") %>% + xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset") +``` + +To help reduce these repetitive calls, we have created `xportr_metadata()`. A user can just **set** the _metadata object_ and the Domain name in the first call, and this will be passed on to the other functions. Much cleaner! + + +```{r, eval = FALSE} +adsl %>% + xportr_metadata(var_spec, "ADSL") %>% + xportr_type() %>% + xportr_length() %>% + xportr_label() %>% + xportr_order() %>% + xportr_format() %>% + xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset") +``` + + +## Warnings and Errors + +For the next six sections, we are going to explore the Warnings and Errors messages generated by the `{xportr}` core functions. To better explore these, we will either manipulate the ADaM dataset or specification file to help showcase the ability of the `{xportr}` functions to detect issues. + +**NOTE:** We have made the ADSL, `xportr::adsl`, and Specification File, `xportr::var_spec`, available in this package. Users can find additional datasets and specification files on our [repo](https://github.com/atorus-research/xportr) in the `example_data_specs` folder. + +### Setting up our metadata object + +First, let's read in the specification file and call it `var_spec`. Note that we are not using `options()` here. We will do some slight manipulation to the column names by doing all lower case, and changing `Data Type` to `type` and making the Order column numeric. You can also use `options()` for this step as well. The `var_spec` object has five dataset specification files stacked on top of each other. We will make use of the `ADSL` subset of `var_spec`. You can make use of the Search field above the dataset column to subset the specification file for `ADSL` + +```{r} +var_spec <- var_spec %>% + rename(type = "Data Type") %>% + set_names(tolower) +``` + +```{r, echo = FALSE} +columns2hide <- c( + "significant digits", "mandatory", "assigned value", "codelist", "common", + "origin", "pages", "method", "predecessor", "role", "comment", + "developer notes" +) + +datatable( + var_spec %>% select(-all_of(columns2hide)), + rownames = FALSE, + filter = "top", + options = list( + dom = "Bfrtip", + columnDefs = list( + list( + width = "10px", + targets = c("order", "length", "format", "type", "dataset", "variable") + ), + list( + className = "text-left", + targets = c("label") + ) + ), + searching = FALSE, + autoWidth = TRUE + ) +) %>% + formatStyle(0, + target = "row", + color = "black", + backgroundColor = "white", + fontWeight = "500", + lineHeight = "85%", + textAlign = "center", + fontSize = ".875em" # same as code + ) +``` + +## `xportr_type()` + +We are going to explore the type column in the metadata object. A submission to a Health Authority should only have character and numeric types in the data. In the `ADSL` data we have several columns that are in the Date type: `TRTSDT`, `TRTEDT`, `DISONSDT`, `VISIT1DT` and `RFENDT` - under the hood these are actually numeric values and will be left as is. We will change one variable type to a [factor variable](https://forcats.tidyverse.org/), which is a common data structure in R, to give us some educational opportunities to see `xportr_type()` in action. + +```{r} +adsl_fct <- adsl %>% + mutate(STUDYID = as_factor(STUDYID)) +``` + +```{r, echo = FALSE} +adsl_glimpse <- adsl_fct %>% + select(STUDYID, TRTSDT, TRTEDT, DISONSDT, VISIT1DT, RFENDT) +``` + +```{r, echo = FALSE} +glimpse(adsl_glimpse) +``` + +```{r, echo = TRUE} +adsl_type <- xportr_type(.df = adsl_fct, metadata = var_spec, domain = "ADSL", verbose = "warn") +``` + +```{r, echo = FALSE} +adsl_type_glimpse <- adsl_type %>% + select(STUDYID, TRTSDT, TRTEDT, DISONSDT, VISIT1DT, RFENDT) +``` + +Success! As we can see below, `xportr_type()` applied the types from the metadata object to the `STUDYID` variables converting to the proper type. The functions in `{xportr}` also display this coercion to the user in the console, which is seen above. + +```{r, echo = TRUE} +glimpse(adsl_type_glimpse) +``` + +Note that `xportr_type(verbose = "warn")` was set so the function has provided feedback, which would show up in the console, on which variables were converted as a warning message. However, you can set `verbose = "stop"` so that the types are not applied if the data does not match what is in the specification file. Using `verbose = "stop"` will instantly stop the processing of this function and not create the object. A user will need to alter the variables in their R script before using `xportr_type()` + +```{r, echo = TRUE, error = TRUE} +adsl_type <- xportr_type(.df = adsl, metadata = var_spec, domain = "ADSL", verbose = "stop") +``` + +## `xportr_length()` + +Next we will use `xportr_length()` to apply the length column of the _metadata object_ to the `ADSL` dataset. Using the `str()` function we have displayed all the variables with their attributes. You can see that each variable has a label, but there is no information on the lengths of the variable. + +```{r, max.height='300px', attr.output='.numberLines', echo = FALSE} +str(adsl) +``` + +```{r, echo = TRUE} +adsl_length <- xportr_length(.df = adsl, metadata = var_spec, domain = "ADSL", verbose = "warn") +``` + +Using `xportr_length()` with `verbose = "warn"` we can apply the length column to all the columns in the dataset. The function detects that two variables, `TRTDUR` and `DCREASCD` are missing from the metadata file. Note that the variables have slight misspellings in the dataset and metadata, which is a great catch! However, lengths are still applied with TRTDUR being give a length of 8 and DCREASCD a length of 200. + +Using the `str()` function, you can see below that `xportr_length()` successfully applied all the lengths of the variable to the variables in the dataset. + + +```{r, max.height='300px', attr.output='.numberLines', echo = FALSE} +str(adsl_length) +``` + +Just like we did for `xportr_type()`, setting `verbose = "stop"` immediately stops R from processing the lengths. Here the function detects the missing variables and will not apply any lengths to the dataset until corrective action is applied. + +```{r, echo = TRUE, error = TRUE} +adsl_length <- xportr_length(.df = adsl, metadata = var_spec, domain = "ADSL", verbose = "stop") +``` + + +## `xportr_label()` + +As you are creating your dataset in R you will often find that R removes the label of your variable. Using `xportr_label()` you can easily re-apply all your labels to your variables in one quick action. + +For this example, we are going to manipulate both the metadata and the `ADSL` dataset: + +* The metadata will have the variable `TRTSDT` label be greater than 40 characters. +* The `ADSL` dataset will have all its labels stripped from it. + +Remember in the length example, the labels were on the original dataset as seen in the `str()` output. + +```{r, echo = TRUE} +var_spec_lbl <- var_spec %>% + mutate(label = if_else(variable == "TRTSDT", + "Length of variable label must be 40 characters or less", label + )) + +adsl_lbl <- adsl + +adsl_lbl <- haven::zap_label(adsl) +``` + +We have successfully removed all the labels. + +```{r, max.height='300px', attr.output='.numberLines', echo = FALSE} +str(adsl_lbl) +``` + +Using `xportr_label()` we will apply all the labels from our metadata to the dataset. Please note again that we are using `verbose = "warn"` and the same two issues for `TRTDUR` and `DCREASCD` are reported as missing from the metadata file. An additional message is sent around the `TRTSDT` label having a length of greater than 40. + +```{r} +adsl_lbl <- xportr_label(.df = adsl_lbl, metadata = var_spec_lbl, domain = "ADSL", verbose = "warn") +``` + +Success! All labels have been applied that are present in the both the metadata and the dataset. However, please note that the `TRTSDT` variable has had the label with characters greater than 40 **applied** to the dataset and the `TRTDUR` and `DCREASCD` have empty variable labels. + +```{r, max.height='300px', attr.output='.numberLines', echo = FALSE} +str(adsl_lbl) +``` + +Just like we did for the other functions, setting `verbose = "stop"` immediately stops R from processing the labels. Here the function detects the mismatches between the variables and labels as well as the label that is greater than 40 characters. As this stops the process, none of the labels will be applied to the dataset until corrective action is applied. + +```{r, echo = TRUE, error = TRUE} +adsl_label <- xportr_label(.df = adsl_lbl, metadata = var_spec_lbl, domain = "ADSL", verbose = "stop") +``` + +## `xportr_order()` + +The order of the dataset can greatly increase readability of the dataset for downstream stakeholders. For example, having all the treatment related variables or analysis variables grouped together can help with inspection and understanding of the dataset. `xportr_order()` can take the order information from the metadata and apply it to your dataset. + +```{r} +adsl_ord <- xportr_order(.df = adsl, metadata = var_spec, domain = "ADSL", verbose = "warn") +``` + +Readers are encouraged to inspect the dataset and metadata to see the past order and updated order after calling the function. Note the messaging from `xportr_order()`: + +* Variables not in the metadata are moved to the end +* Variables not in order are re-ordered and a message is printed out on which ones were re-ordered. + + +```{r, echo = TRUE, error = TRUE} +adsl_ord <- xportr_order(.df = adsl, metadata = var_spec, domain = "ADSL", verbose = "stop") +``` + +Just like we did for the other functions, setting `verbose = "stop"` immediately stops R from processing the order. If variables or metadata are missing from either, the re-ordering will not process until corrective action is performed. + +## `xportr_format()` + +Formats play an important role in the SAS language and have a column in specification files. Being able to easily apply formats into your `xpt` file will allow downstream users of SAS to quickly format the data appropriately when reading into a SAS-based system. `xportr_format()` can take these formats and apply them. Please reference `xportr_length()` or `xportr_label()` to note the missing `attr()` for formats in our `ADSL` dataset. + +This example is slightly different from previous examples. You will need to use `xportr_type()` to coerce R Date variables and others types to character or numeric. Only then can you use `xportr_format()` to apply the format column to the dataset. + +```{r, echo = TRUE} +adsl_fmt <- adsl %>% + xportr_type(metadata = var_spec, domain = "ADSL", verbose = "warn") %>% + xportr_format(metadata = var_spec, domain = "ADSL") +``` + +Success! We have taken the metadata formats and applied them to the dataset. Please inspect variables like `TRTSDT` or `DISONSDT` to see the `DATE9.` format being applied. + +```{r, max.height='300px', attr.output='.numberLines', echo = FALSE} +str(adsl_fmt) +``` + +At the time of `{xportr} v0.3.0` we have not implemented any warnings or error messaging for this function. However, `xportr_write()` through `xpt_validate()` will check that formats applied are valid SAS formats. + +## `xportr_write()` + +Finally, we want to write out an `xpt` dataset with all our metadata applied. + +We will make use of `xportr_metadata()` to reduce repetitive metadata and domain specifications. We will use default option for verbose, which is just `message` and so not set anything for `verbose`. In `xportr_write()` we will specify the path, which will just be our current working directory, set the dataset label and toggle the `strict_checks` to be `FALSE`. + +```{r, echo = TRUE, error = TRUE} +adsl %>% + xportr_metadata(var_spec, "ADSL") %>% + xportr_type() %>% + xportr_length() %>% + xportr_label() %>% + xportr_order() %>% + xportr_format() %>% + xportr_write(path = "adsl.xpt", label = "Subject-Level Analysis Dataset", strict_checks = FALSE) +``` + +Success! We have applied types, lengths, labels, ordering and formats to our dataset. Note the messages written out to the console. Remember the `TRTDUR` and `DCREASCD` and how these are not present in the metadata, but in the dataset. This impacts the messaging for lengths and labels where `{xportr}` is printing out some feedback to us on the two issues. 5 types are coerced, as well as 36 variables re-ordered. Note that `strict_checks` was set to `FALSE`. + +The next two examples showcase the `strict_checks = TRUE` option in `xportr_write()` where we will look at formats and labels. + +```{r, echo = TRUE, error = TRUE} +adsl %>% + xportr_write(path = "adsl.xpt", label = "Subject-Level Analysis Dataset", strict_checks = TRUE) +``` + + +As there at several `---DT` type variables, `xportr_write()` detects the lack of formats being applied. To correct this remember you can use `xportr_type()` and `xportr_format()` to apply formats to your xpt dataset. + +Below we have manipulated the labels to again be greater than 40 characters for `TRTSDT`. We have turned off `xportr_label()` verbose options to only produce a message. However, `xportr_write()` with `strict_checks = TRUE` will error out as this is one of the many `xpt_validate()` checks going on behind the scenes. + +```{r, echo = TRUE, error = TRUE} +var_spec_lbl <- var_spec %>% + mutate(label = if_else(variable == "TRTSDT", + "Length of variable label must be 40 characters or less", label + )) + + +adsl %>% + xportr_metadata(var_spec_lbl, "ADSL") %>% + xportr_label() %>% + xportr_type() %>% + xportr_format() %>% + xportr_write(path = "adsl.xpt", label = "Subject-Level Analysis Dataset", strict_checks = TRUE) +``` + + +## Future Work + +`{xportr}` is still undergoing development. We hope to produce more vignettes and functions that will allow users to bulk process multiple datasets as well as have examples of piping `xpt` files and related documentation to a validation software service. As always, please let us know of any feature requests, documentation updates or bugs on [our GitHub repo](https://github.com/Atorus-Research/xportr/issues). diff --git a/vignettes/xportr.Rmd b/vignettes/xportr.Rmd index 8df23271..1c6acdb0 100644 --- a/vignettes/xportr.Rmd +++ b/vignettes/xportr.Rmd @@ -17,24 +17,73 @@ knitr::opts_chunk$set( ) library(DT) + +options(cli.num_colors = 1) ``` ```{r, include=FALSE} +options(width = 60) local({ - hook_output <- knitr::knit_hooks$get('output') + hook_output <- knitr::knit_hooks$get("output") knitr::knit_hooks$set(output = function(x, options) { - if (!is.null(options$max.height)) options$attr.output <- c( - options$attr.output, - sprintf('style="max-height: %s;"', options$max.height) - ) + if (!is.null(options$max.height)) { + options$attr.output <- c( + options$attr.output, + sprintf('style="max-height: %s;"', options$max.height) + ) + } hook_output(x, options) }) }) ``` + +```{r, include=FALSE} +knitr::knit_hooks$set(output = function(x, options) { + if (!is.null(options$max_height)) { + paste('
    ',
    +      x, "
    ", + sep = "" + ) + } else { + x + } +}) +``` + +```{r, include=FALSE} +datatable_template <- function(input_data) { + datatable( + input_data, + rownames = FALSE, + options = list( + autoWidth = FALSE, + scrollX = TRUE, + pageLength = 5, + lengthMenu = c(5, 10, 15, 20) + ) + ) %>% + formatStyle( + 0, + target = "row", + color = "black", + backgroundColor = "white", + fontWeight = "500", + lineHeight = "85%", + fontSize = ".875em" # same as code + ) +} +``` + + # Getting Started with xportr -The demo will make use of a small `ADSL` data set that is apart of the [`{admiral}`](https://pharmaverse.github.io/admiral/index.html) package. The script that generates this `ADSL` dataset can be created by using this command `admiral::use_ad_template("adsl")`. +The demo will make use of a small `ADSL` data set that is apart of the [`{admiral}`](https://pharmaverse.github.io/admiral/index.html) package. +The script that generates this `ADSL` dataset can be created by using this command +`admiral::use_ad_template("adsl")`. For a deeper discussion into `{xportr}` be sure +to check out the [Deep Dive](deepdive.html) User Guide. The `ADSL` has the following features: @@ -43,7 +92,7 @@ The `ADSL` has the following features: * Data types other than character and numeric * Missing labels on variables * Missing label for data set -* Order of varibles not following specification file +* Order of variables not following specification file * Formats missing To create a fully compliant v5 xpt `ADSL` dataset, that was developed using R, we will need to apply the 6 main functions within the `xportr` package: @@ -62,115 +111,86 @@ library(dplyr) library(labelled) library(xportr) library(admiral) +library(rlang) +library(readxl) # Loading in our example data adsl <- admiral::admiral_adsl ``` -
    + ```{r, echo = FALSE} -DT::datatable(adsl, options = list( - autoWidth = FALSE, scrollX = TRUE, pageLength = 5, - lengthMenu = c(5, 10, 15, 20) -)) +datatable_template(adsl) ``` -
    -**NOTE:** Dataset can be created by using this command `admiral::use_ad_template("adsl")`. +**NOTE:** The `ADSL` dataset can be created by using this command `admiral::use_ad_template("adsl")`. # Preparing your Specification Files -
    - - -In order to make use of the functions within `xportr` you will need to create an R data frame that contains your specification file. You will most likely need to do some pre-processing of your spec sheets after loading in the spec files for them to work appropriately with the `xportr` functions. Please see our example spec sheets in `system.file(paste0("specs/", "ADaM_admiral_spec.xlsx"), package = "xportr")` to see how `xportr` expects the specification sheets. - -
    +In order to make use of the functions within `{xportr}` you will need to create an R data frame that contains your specification file. You will most likely need to do some pre-processing of your spec sheets after loading in the spec files for them to work appropriately with the `xportr` functions. Please see our example spec sheets in `system.file(paste0("specs/", "ADaM_admiral_spec.xlsx"), package = "xportr")` to see how `xportr` expects the specification sheets. ```{r} -var_spec <- readxl::read_xlsx( - system.file(paste0("specs/", "ADaM_admiral_spec.xlsx"), package = "xportr"), sheet = "Variables") %>% - dplyr::rename(type = "Data Type") %>% - rlang::set_names(tolower) - +var_spec <- read_xlsx( + system.file(paste0("specs/", "ADaM_admiral_spec.xlsx"), package = "xportr"), + sheet = "Variables" +) %>% + rename(type = "Data Type") %>% + set_names(tolower) ``` -
    - -Below is a quick snapshot of the specification file pertaining to the `ADSL` data set, which we will make use of in the 6 `xportr` function calls below. Take note of the order, label, type, length and format columns. - -
    +Below is a quick snapshot of the specification file pertaining to the `ADSL` data set, which we will make use of in the 6 `{xportr}` function calls below. Take note of the order, label, type, length and format columns. ```{r, echo = FALSE, eval = TRUE} -var_spec_view <- var_spec %>% filter(dataset == "ADSL") +var_spec_view <- var_spec %>% + filter(dataset == "ADSL") -DT::datatable(var_spec_view, options = list( - autoWidth = FALSE, scrollX = TRUE, pageLength = 5, - lengthMenu = c(5, 10, 15, 20) -)) +datatable_template(var_spec_view) ``` -
    - # xportr_type() -
    +**NOTE:** We make use of `str()` to expose the attributes (length, labels, formats, type) +of the datasets. We have suppressed these calls for the sake of brevity. + In order to be compliant with transport v5 specifications an `xpt` file can only have two data types: character and numeric/dbl. Currently the `ADSL` data set has chr, dbl, time, factor and date. -```{r, eval = TRUE} -look_for(adsl, details = TRUE) +```{r, max_height = "200px", echo = FALSE} +str(adsl) ``` -
    +Using `xportr_type()` and the supplied specification file, we can *coerce* the variables in the `ADSL` set to be either numeric or character. -Using `xport_type` and the supplied specification file, we can *coerce* the variables in the `ADSL` set to be either numeric or character. -
    - -```{r, warning=FALSE, message=FALSE, echo = TRUE, results='hide'} -adsl_type <- xportr_type(adsl, var_spec, domain = "ADSL", verbose = "message") +```{r, echo = TRUE} +adsl_type <- xportr_type(adsl, var_spec, domain = "ADSL", verbose = "message") ``` -
    + Now all appropriate types have been applied to the dataset as seen below. -
    -```{r, eval = TRUE} -look_for(adsl_type, details = TRUE) + +```{r, max_height = "200px", echo = FALSE} +str(adsl_type) ``` # xportr_length() -
    - -Next we can apply the lengths from a variable level specification file to the data frame. `xportr_length` will identify variables that are missing from your specification file. The function will also alert you to how many lengths have been applied successfully. Before we apply the lengths lets verify that no lengths have been applied to the original dataframe. - -
    - +Next we can apply the lengths from a variable level specification file to the data frame. `xportr_length()` will identify variables that are missing from your specification file. The function will also alert you to how many lengths have been applied successfully. Before we apply the lengths lets verify that no lengths have been applied to the original dataframe. - -```{r, echo = TRUE, eval = FALSE} -str(adsl) -``` - -```{r, max.height='300px', attr.output='.numberLines', echo = FALSE} +```{r, max_height = "200px", echo = FALSE} str(adsl) ``` -
    - -No lengths have been applied to the variables as seen in the printout - the lengths would be in the `attr` part of each variables. Let's now use `xportr_length` to apply our lengths from the specification file. -
    +No lengths have been applied to the variables as seen in the printout - the lengths would be in the `attr()` part of each variables. Let's now use `xportr_length()` to apply our lengths from the specification file. ```{r} adsl_length <- adsl %>% xportr_length(var_spec, domain = "ADSL", "message") ``` -
    -```{r, max.height='300px', attr.output='.numberLines', echo = TRUE} +```{r, max_height = "200px", echo = FALSE} str(adsl_length) ``` @@ -178,86 +198,94 @@ Note the additional `attr(*, "width")=` after each variable with the width. The # xportr_order() -Please note that the order of the `ADSL` variables, see above, does not match specification file order column. We can quickly remedy this with a call to `xportr_order()`. Note that the variable `SITEID` has been moved as well as many others to match the specification file order column. +Please note that the order of the `ADSL` variables, see above, does not match the specification file `order` column. We can quickly remedy this with a call to `xportr_order()`. Note that the variable `SITEID` has been moved as well as many others to match the specification file order column. Variables not in the spec are moved to the end of the data and a message is written to the console. -```{r, warning=FALSE, message=FALSE, echo = TRUE, results='hide'} -adsl_order <- xportr_order(adsl,var_spec, domain = "ADSL", verbose = "message") +```{r, echo = TRUE} +adsl_order <- xportr_order(adsl, var_spec, domain = "ADSL", verbose = "message") ``` ```{r, echo = FALSE} -DT::datatable(adsl_order, options = list( - autoWidth = FALSE, scrollX = TRUE, pageLength = 5, - lengthMenu = c(5, 10, 15, 20) -)) +datatable_template(adsl_order) ``` # xportr_format() -Now we apply formats to the dataset. These will typically be `DATE9.`, `DATETIME20` or `TIME5`, but many others can be used. Notice that 8 Date/Time variables are missing a format in our `ADSL` dataset. Here we just take a peak at a few `TRT` variables, which have a `NULL` format. +Now we apply formats to the dataset. These will typically be `DATE9.`, `DATETIME20` or `TIME5`, but many others can be used. Notice that in the `ADSL` dataset there are 8 Date/Time variables and they are missing formats. Here we just take a peak at a few `TRT` variables, which have a `NULL` format. -```{r} -attr(adsl$TRTSDT, "format.sas") -attr(adsl$TRTEDT, "format.sas") -attr(adsl$TRTSDTM, "format.sas") -attr(adsl$TRTEDTM, "format.sas") +```{r, max_height = "200px", echo = FALSE} +adsl_fmt_pre <- adsl %>% + select(TRTSDT, TRTEDT, TRTSDTM, TRTEDTM) + +tribble( + ~Variable, ~Format, + "TRTSDT", attr(adsl_fmt_pre$TRTSDT, which = "format"), + "TRTEDT", attr(adsl_fmt_pre$TRTEDT, which = "format"), + "TRTSDTM", attr(adsl_fmt_pre$TRTSDTM, which = "format"), + "TRTEDTM", attr(adsl_fmt_pre$TRTEDTM, which = "format") +) ``` -Using our `xportr_format()` we apply our formats. +Using our `xportr_format()` we can apply our formats to the dataset. ```{r} -adsl_fmt <- adsl %>% xportr_format(var_spec, domain = "ADSL", "message") +adsl_fmt <- adsl %>% xportr_format(var_spec, domain = "ADSL") ``` -```{r} -attr(adsl_fmt$TRTSDT, "format.sas") -attr(adsl_fmt$TRTEDT, "format.sas") -attr(adsl_fmt$TRTSDTM, "format.sas") -attr(adsl_fmt$TRTEDTM, "format.sas") +```{r, max_height = "200px", echo = FALSE} +adsl_fmt_post <- adsl_fmt %>% + select(TRTSDT, TRTEDT, TRTSDTM, TRTEDTM) + +tribble( + ~Variable, ~Format, + "TRTSDT", attr(adsl_fmt_post$TRTSDT, which = "format"), + "TRTEDT", attr(adsl_fmt_post$TRTEDT, which = "format"), + "TRTSDTM", attr(adsl_fmt_post$TRTSDTM, which = "format"), + "TRTEDTM", attr(adsl_fmt_post$TRTEDTM, which = "format") +) ``` +**NOTE:** You can use `attr(data$variable, which = "format")` to inspect formats applied +to a dataframe. The above output has these individual calls bound together for easier viewing. + # xportr_label() -
    +Please observe that our `ADSL` dataset is missing many variable labels. Sometimes these labels can be lost while using R's function. However, a CDISC compliant data set needs to have each variable with a label. -Please observe that our `ADSL` dataset is missing many variable labels. Sometimes these labels can be lost while using R's function. However, A CDISC compliant data set needs to have each variable with a variable label. +```{r, max_height = "200px", echo = FALSE} +adsl_no_lbls <- haven::zap_label(adsl) -```{r, eval = TRUE} -look_for(adsl, details = FALSE) +str(adsl_no_lbls) ``` -
    - Using the `xport_label` function we can take the specifications file and label all the variables available. `xportr_label` will produce a warning message if you the variable in the data set is not in the specification file. -
    - ```{r} -adsl_update <- adsl %>% xportr_label(var_spec, domain = "ADSL", "message") +adsl_lbl <- adsl %>% xportr_label(var_spec, domain = "ADSL", "message") ``` -```{r} -look_for(adsl_update, details = FALSE) +```{r, max_height = "200px"} +str(adsl_lbl) ``` # xportr_write() -
    +Finally, we arrive at exporting the R data frame object as a `xpt` file with `xportr_write()`. The `xpt` file will be written directly to your current working directory. To make it more interesting, we have put together all six functions with the magrittr pipe, `%>%`. A user can now apply types, length, variable labels, formats, data set label and write out their final xpt file in one pipe! Appropriate warnings and messages will be supplied to a user to the console for any potential issues before sending off to standard clinical data set validator application or data reviewers. -Finally, we arrive at exporting the R data frame object as a xpt file with the function `xportr_write()`. The xpt file will be written directly to your current working directory. To make it more interesting, we have put together all six functions with the magrittr pipe, `%>%`. A user can now apply types, length, variable labels, formats, data set label and write out their final xpt file in one pipe! Appropriate warnings and messages will be supplied to a user to the console for any potential issues before sending off to standard clinical data set validator application or data reviewers. - -```{r, eval=FALSE} +```{r} adsl %>% xportr_type(var_spec, "ADSL", "message") %>% xportr_length(var_spec, "ADSL", "message") %>% xportr_label(var_spec, "ADSL", "message") %>% - xportr_order(var_spec, "ADSL", "message") %>% - xportr_format(var_spec, "ADSL", "message") %>% + xportr_order(var_spec, "ADSL", "message") %>% + xportr_format(var_spec, "ADSL") %>% xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset") ``` -That's it! We now have a xpt file created in R with all appropriate types, lengths, labels, ordering and formats from our specification file. +That's it! We now have a `xpt` file created in R with all appropriate types, lengths, labels, ordering and formats from our specification file. If you are interested in exploring more of the custom +warnings and error messages as well as more background on `xpt` generation be sure +to check out the [Deep Dive](deepdive.html) User Guide. As always, we welcome your feedback. If you spot a bug, would like to see a new feature, or if any documentation is unclear - submit an issue -on [xportr's Github page](https://github.com/atorus-research/xportr/issues). +on [xportr's GitHub page](https://github.com/atorus-research/xportr/issues). diff --git a/xportr.Rproj b/xportr.Rproj index eaa6b818..7b4ebf08 100644 --- a/xportr.Rproj +++ b/xportr.Rproj @@ -12,7 +12,12 @@ Encoding: UTF-8 RnwWeave: Sweave LaTeX: pdfLaTeX +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes + BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source PackageRoxygenize: rd,collate,namespace + +UseNativePipeOperator: No