├── .Rbuildignore ├── .Rprofile ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── tutorial_idea.md ├── R-version ├── depends.Rds └── workflows │ └── update-readme-tables.yml ├── .gitignore ├── .here ├── .lintr ├── .vscode ├── json.code-snippets └── markdown.code-snippets ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── README.md ├── _dev ├── R │ ├── init.R │ ├── json.R │ └── readme.R ├── data │ └── tutorials.csv ├── dev.R └── update_readme.R ├── data-editor ├── README.md ├── app.R ├── data-editor.Rproj ├── data │ ├── archive │ │ ├── nyc_dogs_20191121-124152.RDS │ │ ├── nyc_dogs_20191121-124217.RDS │ │ ├── nyc_dogs_20191121-124249.RDS │ │ ├── nyc_dogs_20191121-124343.RDS │ │ ├── nyc_dogs_20191121-124434.RDS │ │ ├── nyc_dogs_20191121-124449.RDS │ │ └── nyc_dogs_20201120-195030.RDS │ ├── data_0_source.R │ ├── nyc_dogs.RDS │ └── nyc_dogs.csv └── package.json ├── drag-and-drop ├── R │ └── draggable_card.R ├── README.md ├── app.R ├── drag_and_drop_demo.gif ├── package.json └── www │ ├── index.js │ └── styles.css ├── get-window-dims ├── README.md ├── app.R ├── package.json └── www │ ├── index.js │ └── styles.css ├── internal-links-basic-ex ├── README.md ├── app.R ├── internal-links-basic-ex.Rproj ├── package.json └── www │ └── js │ └── index.js ├── internal-links-demo ├── README.md ├── app.R ├── internal-links-demo.Rproj ├── package.json └── www │ └── js │ └── index.js ├── internal-links ├── README.md ├── app.R ├── internal-links-vis.gif ├── internal-links.Rproj ├── package.json └── www │ ├── imgs │ ├── aditya-vyas-BLkamCbE-so-unsplash.jpg │ ├── hector-arguello-canals-2x6vURol6cM-unsplash.jpg │ └── tomas-eidsvold-qc9OKXKTETk-unsplash.jpg │ └── js │ └── index.js ├── js-handlers ├── README.md ├── app.R ├── js-handlers.Rproj ├── package.json └── www │ ├── index.js │ └── styles.css ├── leaflet-loading-screens ├── R │ └── leaflet_loader.R ├── README.md ├── app.R ├── demo.gif ├── package.json └── www │ └── styles.css ├── login-screen ├── R │ ├── handlers.R │ ├── login.R │ └── main_ui.R ├── README.md ├── app.R ├── login-screen.Rproj ├── package.json └── www │ ├── index.js │ └── styles.css ├── progress-bars-example ├── R │ ├── pages.R │ └── progress.R ├── README.md ├── app.R ├── package.json └── www │ ├── index.js │ └── styles.css ├── renv.lock ├── renv ├── .gitignore ├── activate.R └── settings.json ├── responsive-datatables ├── R │ └── datatable.R ├── README.md ├── app.R ├── data │ ├── birds_summary.RDS │ └── birds_summary.csv ├── package.json ├── responsive-datatables.Rproj ├── tests │ └── test-datatable.R └── www │ └── css │ ├── datatable.css │ └── styles.css ├── rmarkdown-app ├── .gitignore ├── README.md ├── app.R ├── data │ ├── data_0_source.R │ └── librarians_538.RDS ├── package.json ├── report_template.Rmd ├── rmarkdown-app.Rproj └── www │ └── styles.css ├── sass-in-shiny ├── R │ └── datatable.R ├── README.md ├── app.R ├── data │ └── birds_summary.RDS ├── package.json ├── sass-in-shiny.Rproj ├── src │ ├── _base.scss │ ├── _datatable.scss │ ├── _header.scss │ ├── _variables.scss │ └── index.scss └── www │ └── styles.css ├── select-input-styling ├── README.md ├── app.R ├── package.json ├── select-input-styling.Rproj └── www │ └── css │ ├── menu-chevron-down.svg │ └── styles.css ├── setting-html-attributes ├── R │ └── set_html_attribs.R ├── README.md ├── app.R ├── package.json ├── setting-html-attributes.Rproj └── www │ └── set_html_attribs.js ├── shiny-accordion ├── R │ └── accordion.R ├── README.md ├── app.R ├── package.json └── www │ ├── accordion.css │ ├── accordion.js │ └── app.css ├── shiny-landing-page ├── README.md ├── app.R ├── package.json ├── preview.png ├── shiny-landing-page.Rproj └── www │ ├── css │ └── styles.css │ └── imgs │ ├── colton-jones-_ZX2WYM3_BM-unsplash.jpg │ ├── david-tostado-woMvsY6KHac-unsplash.jpg │ ├── hilary-bird-F_aYxIFPnfk-unsplash.jpg │ └── lloyd-blunk-Sv0xUKiu6ek-unsplash.jpg ├── shiny-links ├── R │ └── shinyLink.R ├── README.md ├── app.R ├── package.json ├── shiny-links.Rproj └── www │ ├── chain-image.jpg │ ├── shinyLink.js │ └── styles.css ├── shiny-listbox ├── .babelrc ├── .gitignore ├── .postcssrc ├── R │ └── listbox.R ├── README.md ├── app.R ├── dev │ └── dev.R ├── package.json ├── shiny-listbox.Rproj ├── src │ ├── index.scss │ └── listbox.scss └── www │ ├── css │ ├── index.css │ └── listbox.css │ └── js │ └── listbox.js ├── shinyAppTutorials.code-workspace ├── shinytutorials.png └── time-input ├── R └── time_input.R ├── README.md ├── app.R ├── package.json ├── time-input.Rproj └── www ├── styles.css ├── time_input.css └── time_input.js /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^renv$ 2 | ^renv\.lock$ 3 | -------------------------------------------------------------------------------- /.Rprofile: -------------------------------------------------------------------------------- 1 | #' //////////////////////////////////////////////////////////////////////////// 2 | #' FILE: .Rprofile 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2021-07-29 5 | #' MODIFIED: 2021-10-12 6 | #' PURPOSE: workspace configurations and useful functions for vscode+R env 7 | #' STATUS: working; ongoing 8 | #' PACKAGES: NA 9 | #' COMMENTS: https://github.com/davidruvolo51/rprofile 10 | #' //////////////////////////////////////////////////////////////////////////// 11 | 12 | # set options 13 | options( 14 | 15 | # options: shiny 16 | shiny.port = 8000, 17 | shiny.launch.browser = FALSE, 18 | 19 | # options: radian 20 | radian.insert_new_line = FALSE, 21 | radian.prompt = "\033[0;34m>\033[0m ", 22 | 23 | # options: vscode R 24 | vsc.use_httpgd = TRUE, 25 | vsc.helpPanel = "Beside", 26 | vsc.viewer = "Beside", 27 | vsc.browser = "Beside", 28 | vsc.show_object_size = FALSE, 29 | 30 | # options: languageserver 31 | languageserver.formatting_style = function(options) { 32 | styler::tidyverse_style( 33 | start_comments_with_one_space = TRUE, 34 | indent_by = 4 35 | ) 36 | } 37 | ) 38 | 39 | 40 | #' @title Clear 41 | #' @name clear 42 | #' @description clear the active terminal 43 | #' @noRD 44 | clear <- function() { 45 | cmds <- list("unix" = "clear", "windows" = "cls") 46 | system(cmds[[.Platform$OS.type]]) 47 | } 48 | 49 | 50 | #' @title Quietly Load Package 51 | #' @name library2 52 | #' @description suppress messages when loading a package 53 | #' @param pkg the name of the package 54 | #' @noRd 55 | library2 <- function(pkg) { 56 | suppressPackageStartupMessages(library(pkg, character.only = TRUE)) 57 | } 58 | 59 | 60 | #' @title Remove2 61 | #' @name rm2 62 | #' @description Force remove all objects from the current environment 63 | #' @param except optional an array of object names to ignore 64 | #' @noRd 65 | rm2 <- function(except = NULL) { 66 | ignore <- c("clear", "library2", "rm2") 67 | if (!is.null(except)) ignore <- c(ignore, except) 68 | rm(list = setdiff(ls(envir = .GlobalEnv), ignore), envir = .GlobalEnv) 69 | } 70 | 71 | # start renv: make sure this is always last! 72 | source("renv/activate.R") 73 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/tutorial_idea.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Tutorial Idea 3 | about: Propose a new tutorial topic 4 | title: '' 5 | labels: tutorial ideas, tutorials 6 | assignees: davidruvolo51 7 | --- 8 | 9 | ### Aim 10 | 11 | 12 | 13 | ### Overview 14 | 15 | 16 | 17 | ### Steps 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/R-version: -------------------------------------------------------------------------------- 1 | R-4.3 2 | -------------------------------------------------------------------------------- /.github/depends.Rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/.github/depends.Rds -------------------------------------------------------------------------------- /.github/workflows/update-readme-tables.yml: -------------------------------------------------------------------------------- 1 | name: Update README 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | update-readme: 11 | name: Update README 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | config: 18 | - { os: ubuntu-latest, r: "release" } 19 | 20 | env: 21 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 22 | RSPM: ${{ matrix.config.rspm }} 23 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | steps: 26 | - uses: actions/checkout@v3 27 | 28 | - uses: r-lib/actions/setup-r@v2 29 | with: 30 | r-version: ${{ matrix.config.r }} 31 | http-user-agent: ${{ matrix.config.http-user-agent }} 32 | 33 | # - uses: r-lib/actions/setup-pandoc@master 34 | 35 | - name: Query dependencies 36 | run: | 37 | install.packages('remotes') 38 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 39 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 40 | shell: Rscript {0} 41 | 42 | - name: Cache R packages 43 | if: runner.os != 'Windows' 44 | uses: actions/cache@v3 45 | with: 46 | path: ${{ env.R_LIBS_USER }} 47 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 48 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 49 | 50 | - name: Install system dependencies 51 | if: runner.os == 'Linux' 52 | run: | 53 | while read -r cmd 54 | do 55 | eval sudo $cmd 56 | done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "16.04"))') 57 | 58 | - name: Install dependencies 59 | run: | 60 | remotes::install_deps(dependencies = TRUE) 61 | remotes::install_cran("rcmdcheck") 62 | shell: Rscript {0} 63 | 64 | # Run main R scripts 65 | - name: Update README 66 | run: | 67 | source("_dev/R/readme.R") 68 | source("_dev/update_readme.R") 69 | shell: Rscript {0} 70 | 71 | # Commit changes using the var `should_commit` 72 | - name: Commit 73 | continue-on-error: true 74 | run: | 75 | git config --local user.email "actions@github.com" 76 | git config --local user.name "GitHub Actions" 77 | git add --all 78 | git commit -m 'updated readme tables' 79 | git push 80 | 81 | # Log session info (from r-lib/actions) 82 | - name: Session info 83 | run: | 84 | options(width = 100) 85 | pkgs <- installed.packages()[, "Package"] 86 | sessioninfo::session_info(pkgs, include_base = TRUE) 87 | shell: Rscript {0} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .DS_Store 6 | .sass-cache 7 | .cache 8 | node_modules 9 | yarn.lock 10 | rmarkdown-app/report_template.html -------------------------------------------------------------------------------- /.here: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/.here -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: with_defaults(line_length_linter = line_length_linter(81), commented_code_linter = NULL, object_usage_linter = NULL) 2 | exclusions: list("renv/") 3 | exclude_start: "# nolint start" 4 | exclude_end: "# nolint end" 5 | -------------------------------------------------------------------------------- /.vscode/json.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Package JSON Template": { 3 | "prefix": "package", 4 | "description": "create package.json template", 5 | "body": [ 6 | "{", 7 | "\t\"name\": \"${TM_DIRECTORY/^.+\\/(.*)$/$1/}\",", 8 | "\t\"version\": \"0.9.0\",", 9 | "\t\"description\": \"\",", 10 | "\t\"author\": \"@dcruvolo\",", 11 | "\t\"status\": \"active\",", 12 | "\t\"repository\": {", 13 | "\t\t\"type\": \"git\",", 14 | "\t\t\"url\": \"git+https://github.com/davidruvolo51/shinyAppTutorials.git\"", 15 | "\t},", 16 | "\t\"keywords\": [", 17 | "\t\t\"r\",", 18 | "\t\t\"shiny\",", 19 | "\t\t\"tutorials\"", 20 | "\t],", 21 | "\t\"license\": \"MIT\",", 22 | "\t\"bugs\": {", 23 | "\t\t\"url\": \"https://github.com/davidruvolo51/shinyAppTutorials/issues\"", 24 | "\t},", 25 | "\t\"homepage\": \"https://github.com/davidruvolo51/shinyAppTutorials\"", 26 | "}" 27 | ] 28 | } 29 | } -------------------------------------------------------------------------------- /.vscode/markdown.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "README Template": { 3 | "prefix": "readme", 4 | "body": [ 5 | "", 6 | "", 7 | "", 8 | "", 9 | "# ${1:title}", 10 | "", 11 | "${2:description}", 12 | "", 13 | "## Getting Started", 14 | "", 15 | "You can run this app locally using the following methods: running within your R environment or cloning this subdirectory.", 16 | "", 17 | "### Running in your R environment", 18 | "", 19 | "You can run this app within your R environment using the `runGithub` function. Enter the following command in the console.", 20 | "", 21 | "```r", 22 | "shiny::runGitHub(username = \"davidruvolo51\", repo = \"shinyAppTutorials\", subdir = \"${3:subdir}\")", 23 | "```", 24 | "", 25 | "### Cloning the subdirectory", 26 | "", 27 | "You can clone the data editor subdirectory using `git sparse-checkout`.", 28 | "", 29 | "```bash", 30 | "git clone --filter=blob:none --sparse https://github.com/davidruvolo51/", 31 | "shinyAppTutorials", 32 | "cd shinyAppTutorials", 33 | "git sparse-checkout init --cone", 34 | "git sparse-checkout set ${3:subdir}", 35 | "```", 36 | "", 37 | "Then you can run the shiny app in your preferred R environment.", 38 | "" 39 | ], 40 | "description": "Generates the standard README structure for new examples" 41 | } 42 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Shiny App Tutorials 2 | 3 | Thank you for your interest in this project! 4 | 5 | Suggestions, comments, ideas for improvement are always warmly welcomed. If you would like to write a tutorial, you are more than welcome and encouraged to do so. In this guide, you can find more information about the project (both the static site repo and the shiny repo) to make contributing to the project easier. If you have any questions, feel free to get in touch with me. 6 | 7 | ## Contents 8 | 9 | 1. Background 10 | - About the project 11 | - The structure of the project 12 | 2. Contributing 13 | 14 | ## Background 15 | 16 | ### About the project 17 | 18 | Why did I start this project? In my early days of learning shiny, I kept a document of all my tips and tricks. It worked nicely, but it quickly became too cluttered and scattered. I decided to create this project to organize the material in to a series of practical examples and templates available for the wider R community. 19 | 20 | I write tutorials on things that I'm learning on at the moment or if there is something that I'm curious if shiny can do. My interests are in web accessibility, data visualization and communication of results, as well as frontend development and good design practices. Many of these tutorials focus heavily on html, css, and javascript, but I will try to keep things simple and provide links for further reading. 21 | 22 | ### The structure of the project 23 | 24 | There are two repos to this project. 25 | 26 | 1. [shinyAppTutorials](https://github.com/davidruvolo51/shinyAppTutorials) 27 | 2. [shinytutorials](https://github.com/davidruvolo51/shinytutorials) 28 | 29 | The `shinyAppTutorials` repo contains all of the shinyapps and examples, as well as all related code discussed in the tutorials (e.g., css, js, etc.). All written tutorials are stored in the `shinytutorials` repo. The static site is built with [gatsbyjs](https://www.gatsbyjs.org) and all tutorials are written in markdown. 30 | 31 | 32 | ## Contributing 33 | 34 | If you have any ideas to improve the tutorials (either in the code or in the writings), fork the repository, make all the changes, and then open a pull request. Make sure to include a README if you are adding a new app. 35 | 36 | If you would like to write or contribute to a tutorial, then create a fork of the shinytutorials repository. The static site is built using gatsbyJS. All development takes place on the `dev` branch. All tutorials are written in markdown using the following YAML header: 37 | 38 | ``` 39 | --- 40 | title: "Some Title" 41 | subtitle: "A Subtitle" 42 | abstract: "A summary of the tutorial" 43 | date: "2019-09-06" 44 | updated: "2019-10-25" 45 | keywords: ["keyword1", "keyword2"] 46 | --- 47 | ``` 48 | 49 | The build process can be a bit confusing, so it might be easier to open a pull request from the dev branch. 50 | 51 | Take a look at the existing directories and files to see how the projects are structured. Let me know if you have any questions. 52 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: shinyAppTutorials 2 | Title: This isn't a real package! 3 | Version: 0.0.0.9000 4 | Authors@R: 5 | person(given = "David", 6 | family = "Ruvolo", 7 | role = c("aut", "cre"), 8 | email = "first.last@example.com", 9 | comment = c(ORCID = "YOUR-ORCID-ID")) 10 | Description: A fake R package for running GitHub Actions 11 | License: MIT + file LICENSE 12 | Encoding: UTF-8 13 | LazyData: true 14 | Roxygen: list(markdown = TRUE) 15 | RoxygenNote: 7.1.1 16 | Imports: 17 | cli, 18 | dplyr, 19 | jsonlite, 20 | knitr, 21 | readr, 22 | stringr 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 dcruvolo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | -------------------------------------------------------------------------------- /_dev/R/init.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: init.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-11-19 5 | #' MODIFIED: 2021-02-13 6 | #' PURPOSE: init subdir files 7 | #' STATUS: working 8 | #' PACKAGES: see below 9 | #' COMMENTS: NA 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | #' source utils 13 | source("_dev/R/json.R") 14 | source("_dev/R/readme.R") 15 | 16 | #' ~ 1 ~ 17 | #' INITIALIZE 18 | #' For the first time, create `package.json` files where applicable. 19 | #' Set version number to `1.0.0`; `description` will be blank 20 | 21 | # compile directories 22 | dirs <- list.dirs(recursive = FALSE) %>% 23 | sapply( 24 | ., 25 | function(d) { 26 | gsub( 27 | pattern = "./", 28 | replacement = "", 29 | x = d 30 | ) 31 | } 32 | ) %>% 33 | as.character(.) %>% 34 | .[!. %in% c(".dev", ".git", ".github")] 35 | 36 | #' init `package.json` 37 | #' system("rm -rf Data-Editor/package.json") # for testing 38 | for (d in seq_len(length(dirs))) { 39 | json$init(dir = dirs[d]) 40 | dat <- json$data( 41 | name = dirs[d], 42 | version = "1.0.0", 43 | description = "", 44 | author = "@dcruvolo", 45 | status = "active" 46 | ) 47 | json$write(, 48 | data = dat, 49 | path = paste0(dirs[d], "/package.json") 50 | ) 51 | } -------------------------------------------------------------------------------- /_dev/R/readme.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: readme.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-11-21 5 | #' MODIFIED: 2020-11-22 6 | #' PURPOSE: tools for updating readme 7 | #' STATUS: in.progress 8 | #' PACKAGES: NA 9 | #' COMMENTS: NA 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | #' README 13 | #' 14 | #' methods for updating README.md 15 | #' 16 | #' @noRd 17 | readme <- list(class = "readme") 18 | 19 | #' fetch package.json data 20 | #' 21 | #' gather specific data from all package json files and update main json 22 | #' file 23 | #' 24 | #' @noRd 25 | readme$as_md_table <- function(data) { 26 | data %>% 27 | select(name, description, version, link) %>% 28 | mutate( 29 | link = case_when( 30 | link == "TBD" ~ "TBD", 31 | TRUE ~ paste0("[tutorial](", link, ")") 32 | ) 33 | ) %>% 34 | knitr::kable(.) 35 | } 36 | 37 | #' write md table 38 | #' 39 | #' In the markdown file, tables must be wrapped in opening and 40 | #' closing tags: and 41 | #' 42 | #' @param path path to markdown file 43 | #' @param id internal markdown table id 44 | #' @param table output from `as_md_table` 45 | #' 46 | #' @noRd 47 | readme$write_md_table <- function(path, id, table) { 48 | md <- readLines(path, warn = FALSE) 49 | tbl_start <- paste0("") 50 | tbl_end <- paste0("") 51 | status <- stringr::str_detect(md, tbl_start) 52 | if (length(status[status == TRUE]) > 0) { 53 | start <- match(tbl_start, md) 54 | end <- match(tbl_end, md) 55 | new_md <- c( 56 | md[1:start], 57 | # "", 58 | table, 59 | # "", 60 | md[end:length(md)] 61 | ) 62 | writeLines(new_md, path) 63 | } else { 64 | cli::cli_alert_danger("Unable to locate table {.val {id}}") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /_dev/data/tutorials.csv: -------------------------------------------------------------------------------- 1 | name,version,description,author,status,link 2 | data-editor,1.0.0,A shiny app for editing dataset in shiny,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/data-editor 3 | drag-and-drop,1.0.1,create a drag and drop component,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/drag-and-drop 4 | get-window-dims,1.1.0,sending data from javascript,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/get-window-dims 5 | internal-links,1.1.0,Create a link from a leaflet popup to a shiny page,@dcruvolo,archived,https://davidruvolo51.github.io/shinytutorials/tutorials/internal-links 6 | internal-links-basic-ex,1.1.0,Create links from one shiny page to another,@dcruvolo,archived,https://davidruvolo51.github.io/shinytutorials/tutorials/internal-links-basic-ex 7 | internal-links-demo,1.1.0,Navigate to a specific tab on another page,@dcruvolo,archived,https://davidruvolo51.github.io/shinytutorials/tutorials/internal-links-demo 8 | js-handlers,1.1.1,getting started with custom handlers,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/js-handlers 9 | leaflet-loading-screens,1.0.0,creating a loading screen for leaflet,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/leaflet-loading-screens 10 | login-screen,1.1.0,Adding *basic* user authentication to shiny apps,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/login-screen 11 | progress-bars-example,1.1.0,creating a shiny progressbar using R6 classes,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/progress-bars-example 12 | responsive-datatables,1.1.0,create responsive datatables in R,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/responsive-datatables 13 | rmarkdown-app,1.1.0,using Rmarkdown as Shiny UI,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/rmarkdown-app 14 | sass-in-shiny,1.1.0,integrating SASS into your Shiny development workflow,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/sass-in-shiny 15 | select-input-styling,1.1.0,Style a select input element using CSS,@dcruvolo,archived,https://davidruvolo51.github.io/shinytutorials/tutorials/select-input-styling 16 | setting-html-attributes,1.0.1,set document attributes,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/setting-html-attributes 17 | shiny-accordion,1.0.0,A component for collapsing HTML elements,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/shiny-accordion 18 | shiny-landing-page,1.1.0,create a landing page,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/shiny-landing-page 19 | shiny-links,1.1.0,a component for within app navigation,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/shiny-links 20 | shiny-listbox,1.1.0,create listbox components in shiny,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/shiny-listbox 21 | time-input,1.1.0,creating a custom time input for use in shiny,@dcruvolo,active,https://davidruvolo51.github.io/shinytutorials/tutorials/time-input 22 | -------------------------------------------------------------------------------- /_dev/dev.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: dev.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-11-19 5 | #' MODIFIED: 2023-07-29 6 | #' PURPOSE: repo management 7 | #' STATUS: working; ongoing 8 | #' PACKAGES: see below 9 | #' COMMENTS: NA 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | #' init r 'package' 13 | #' create a R package-like repo structure, so that we can run GitHub Actions 14 | #' more efficiently 15 | usethis::create_project(path = ".") 16 | usethis::use_description(check_name = FALSE) # this isn't a real pkg! 17 | usethis::use_namespace() 18 | 19 | #' pkgs 20 | #' make sure these packages are installed 21 | usethis::use_package("cli") 22 | usethis::use_package("jsonlite") 23 | usethis::use_package("dplyr") 24 | usethis::use_package("knitr") 25 | usethis::use_package("readr") 26 | usethis::use_package("stringr") 27 | 28 | #' source utils 29 | #' source("_dev/R/json.R") 30 | #' source("_dev/R/readme.R") 31 | 32 | #' ~ 2 ~ 33 | #' Subdir Management 34 | #' Badges 35 | #' You can create badges for subdirectories by using the 36 | #' method: `json$add_readme_badges`. Always use set the value of branch to 37 | #' 'main' unless you know exactly what you are doing! 38 | 39 | #' json$add_readme_badges( 40 | #' dir = "progress-bars-example", 41 | #' branch = "main" 42 | #' ) 43 | 44 | # save changes 45 | #' jsonlite::write_json( 46 | #' x = d, 47 | #' path = "_dev/data/tutorials.json", 48 | #' pretty = TRUE, 49 | #' auto_unbox = TRUE 50 | #' ) 51 | -------------------------------------------------------------------------------- /_dev/update_readme.R: -------------------------------------------------------------------------------- 1 | #' //////////////////////////////////////////////////////////////////////////// 2 | #' FILE: update_readme.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2021-02-15 5 | #' MODIFIED: 2023-07-29 6 | #' PURPOSE: update tables in readme 7 | #' STATUS: working 8 | #' PACKAGES: NA 9 | #' COMMENTS: NA 10 | #' //////////////////////////////////////////////////////////////////////////// 11 | 12 | msg <- function(text) message("[", Sys.time(), "] ", text) 13 | msg("Starting workflow") 14 | 15 | # pkgs 16 | suppressPackageStartupMessages(library(dplyr)) 17 | 18 | #' testing locally 19 | #' source("_dev/R/readme.R") 20 | 21 | #' ////////////////////////////////////// 22 | 23 | #' ~ 0 ~ 24 | #' Utils 25 | 26 | #' ~ 0a ~ 27 | # function that gather directories and excludes named dirs 28 | list_dirs <- function(exclude = NULL) { 29 | dirs <- as.character( 30 | sapply(list.dirs(recursive = FALSE), function(d) { 31 | gsub(pattern = "./", replacement = "", x = d) 32 | }) 33 | ) 34 | if (!is.null(exclude)) dirs <- dirs[!dirs %in% exclude] 35 | return(dirs) 36 | } 37 | 38 | #' ~ 0b ~ 39 | #' define a function that reads package.json from each dir 40 | #' and pulls relevant information 41 | pull_data <- function(dirs) { 42 | out <- data.frame() 43 | for (d in seq_len(length(dirs))) { 44 | j <- jsonlite::read_json(paste0(dirs[d], "/package.json")) 45 | out <- dplyr::bind_rows( 46 | out, 47 | data.frame( 48 | name = j$name, 49 | version = j$version, 50 | description = j$description, 51 | author = j$author, 52 | status = j$status, 53 | link = paste0( 54 | "https://davidruvolo51.github.io/shinytutorials/tutorials/", 55 | dirs[d] 56 | ) 57 | ) 58 | ) 59 | } 60 | out 61 | } 62 | 63 | #' ////////////////////////////////////// 64 | 65 | 66 | #' ~ 1 ~ 67 | #' Compile all subdirectories 68 | # all avilable subdirectories and exclude non-shiny apps 69 | 70 | msg("Building subdirectory list") 71 | dirs <- list_dirs( 72 | exclude = c( 73 | "_dev", 74 | "renv", 75 | ".github", 76 | ".git", 77 | ".vscode" 78 | ) 79 | ) 80 | 81 | # error checking 82 | if (!is.null(dirs)) { 83 | msg(paste0("Build directory data (n = ", length(dirs), ")")) 84 | } else { 85 | msg("Failed to compile data") 86 | } 87 | 88 | 89 | # build tutorials 90 | tutorials <- pull_data(dirs) 91 | if (NROW(tutorials) > 0) { 92 | msg("Collated subdirectory data") 93 | } else { 94 | msg("Failed to collate subdirectory data") 95 | } 96 | 97 | msg("Generating markdown tables") 98 | 99 | # write 'activeTutorial' tables 100 | msg("Writing 'active' table") 101 | tutorials %>% 102 | filter(status == "active") %>% 103 | readme$as_md_table(data = .) %>% 104 | readme$write_md_table( 105 | path = "README.md", 106 | id = "activeTutorials", 107 | table = . 108 | ) 109 | 110 | # write 'archivedTutorials' table 111 | msg("Writing 'archived' table") 112 | tutorials %>% 113 | filter(status == "archived") %>% 114 | readme$as_md_table(data = .) %>% 115 | readme$write_md_table( 116 | path = "README.md", 117 | id = "archivedTutorials", 118 | table = . 119 | ) 120 | 121 | # save data 122 | msg("Writing data to '_dev/data/tutorials.csv'") 123 | readr::write_csv(tutorials, "_dev/data/tutorials.csv") 124 | -------------------------------------------------------------------------------- /data-editor/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fdata-editor%2Fpackage.json) 4 | ![status](https://img.shields.io/badge/dynamic/json?color=sucess&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fdata-editor%2Fpackage.json) 5 | 6 | 7 | # Data Editor 8 | 9 | This example demonstrates how to build a data editor within Shiny. You can find more information about this application in the blog post [A shiny app for editing data in a shiny app](https://davidruvolo51.github.io/shinytutorials/tutorials/data-editor/). 10 | 11 | ## Getting Started 12 | 13 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 14 | 15 | ### Running in your R environment 16 | 17 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 18 | 19 | ```r 20 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "data-editor") 21 | ``` 22 | 23 | ### Cloning the subdirectory 24 | 25 | You can clone the data editor subdirectory using `git sparse-checkout`. 26 | 27 | ```bash 28 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 29 | cd shinyAppTutorials 30 | git sparse-checkout init --cone 31 | git sparse-checkout set data-editor 32 | ``` 33 | 34 | Then you can run the shiny app in your preferred R environment. 35 | -------------------------------------------------------------------------------- /data-editor/data-editor.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /data-editor/data/archive/nyc_dogs_20191121-124152.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/data-editor/data/archive/nyc_dogs_20191121-124152.RDS -------------------------------------------------------------------------------- /data-editor/data/archive/nyc_dogs_20191121-124217.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/data-editor/data/archive/nyc_dogs_20191121-124217.RDS -------------------------------------------------------------------------------- /data-editor/data/archive/nyc_dogs_20191121-124249.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/data-editor/data/archive/nyc_dogs_20191121-124249.RDS -------------------------------------------------------------------------------- /data-editor/data/archive/nyc_dogs_20191121-124343.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/data-editor/data/archive/nyc_dogs_20191121-124343.RDS -------------------------------------------------------------------------------- /data-editor/data/archive/nyc_dogs_20191121-124434.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/data-editor/data/archive/nyc_dogs_20191121-124434.RDS -------------------------------------------------------------------------------- /data-editor/data/archive/nyc_dogs_20191121-124449.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/data-editor/data/archive/nyc_dogs_20191121-124449.RDS -------------------------------------------------------------------------------- /data-editor/data/archive/nyc_dogs_20201120-195030.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/data-editor/data/archive/nyc_dogs_20201120-195030.RDS -------------------------------------------------------------------------------- /data-editor/data/data_0_source.R: -------------------------------------------------------------------------------- 1 | #'////////////////////////////////////////////////////////////////////////////// 2 | #' FILE: data_0_source.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 21 November 2019 5 | #' MODIFIED: 21 November 2019 6 | #' PURPOSE: source data and pull 100 rows 7 | #' PACKAGES: NA 8 | #' STATUS: working 9 | #' COMMENTS: download data from https://data.cityofnewyork.us/Health/NYC-Dog-Licensing-Dataset/nu7n-tubp. Adjust the file path to the location of the downloaded file 10 | #'////////////////////////////////////////////////////////////////////////////// 11 | #' GLOBAL OPTIONS: 12 | options(stringsAsFactors = FALSE) 13 | 14 | 15 | # ~ 0 ~ 16 | # set path and read 17 | path <- "~/Downloads/NYC_Dog_Licensing_Dataset.csv" 18 | df <- read.csv(path) 19 | 20 | 21 | # ~ 1 ~ 22 | # randomly pull 100 rows 23 | set.seed(12345) 24 | ceiling <- NROW(df) 25 | array <- sample(1:ceiling, size = 100) 26 | subsetDF <- df[array,] 27 | 28 | # ~ 2 ~ 29 | # save 30 | write.csv(subsetDF, "data/nyc_dogs.csv", row.names = FALSE) 31 | saveRDS(subsetDF, "data/nyc_dogs.RDS") 32 | -------------------------------------------------------------------------------- /data-editor/data/nyc_dogs.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/data-editor/data/nyc_dogs.RDS -------------------------------------------------------------------------------- /data-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data-editor", 3 | "version": "1.0.0", 4 | "description": "A shiny app for editing dataset in shiny", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } 22 | -------------------------------------------------------------------------------- /drag-and-drop/R/draggable_card.R: -------------------------------------------------------------------------------- 1 | #' //////////////////////////////////////////////////////////////////////////// 2 | #' FILE: draggable_card.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-05-05 5 | #' MODIFIED: 2020-11-20 6 | #' PURPOSE: functional component for creating a draggable card 7 | #' STATUS: working 8 | #' PACKAGES: shiny 9 | #' COMMENTS: NA 10 | #' //////////////////////////////////////////////////////////////////////////// 11 | 12 | # functional component for building draggable cards 13 | draggable_card <- function(id, title, text, draggable = TRUE) { 14 | 15 | # validate args 16 | stopifnot(!is.null(id)) 17 | stopifnot(!is.null(title)) 18 | stopifnot(!is.null(text)) 19 | stopifnot(is.logical(draggable)) 20 | 21 | # create icon 22 | fill_color <- "#09BC8A" 23 | line_color <- "#ffffff" 24 | svg <- tag( 25 | "svg", 26 | list( 27 | "width" = "25", 28 | "height" = "25", 29 | "viewBox" = "0 0 25 25", 30 | "class" = "card-icon", 31 | # 32 | tag( 33 | "circle", 34 | list( 35 | "cx" = "12.5", 36 | "cy" = "12.5", 37 | "r" = "12.5", 38 | "fill" = fill_color 39 | ) 40 | ), 41 | # vertical: 42 | tag( 43 | "line", 44 | list( 45 | "x1" = "12.5", 46 | "y1" = "5", 47 | "x2" = "12.5", 48 | "y2" = "20", 49 | "stroke" = line_color, 50 | "stroke-width" = "2.5", 51 | "stroke-linecap" = "butt" 52 | ) 53 | ), 54 | # horizontal: 55 | tag( 56 | "line", 57 | list( 58 | "x1" = "5", 59 | "y1" = "12.5", 60 | "x2" = "20", 61 | "y2" = "12.5", 62 | "stroke" = line_color, 63 | "stroke-width" = "2.5", 64 | "stroke-linecap" = "butt" 65 | ) 66 | ) 67 | ) 68 | ) 69 | 70 | # build parent element:
71 | el <- tags$div( 72 | id = paste0("card-", id), 73 | class = "card", 74 | draggable = tolower(draggable), 75 | `data-value` = title, 76 | tags$h2(class = "card-title", title), 77 | svg, 78 | tags$p(class = "card-message", text) 79 | ) 80 | 81 | # remove element if !draggable 82 | if (!draggable) el$children[[2]] <- NULL 83 | 84 | # return 85 | return(el) 86 | } -------------------------------------------------------------------------------- /drag-and-drop/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fdrag-and-drop%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fdrag-and-drop%2Fpackage.json) 4 | 5 | 6 | # Drag and Drop 7 | 8 | ![drag and drop demo](drag_and_drop_demo.gif) 9 | 10 | This shiny app demonstrates how to create a basic drag and drop area in shiny applications using the drag and drop javascript API (native in browsers). There is some logic that highlights dragged elements and potential "drop areas". There is some basic support for determining if the a card should be placed before or after a new card. 11 | 12 | You can find more information in the blog post [learn how to create movable elements in shiny apps](https://davidruvolo51.github.io/shinytutorials/tutorials/drag-and-drop/). 13 | 14 | ## Getting Started 15 | 16 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 17 | 18 | ### Running in your R environment 19 | 20 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 21 | 22 | ```r 23 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "drag-and-drop") 24 | ``` 25 | 26 | ### Cloning the subdirectory 27 | 28 | You can clone the data editor subdirectory using `git sparse-checkout`. 29 | 30 | ```bash 31 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 32 | cd shinyAppTutorials 33 | git sparse-checkout init --cone 34 | git sparse-checkout set drag-and-drop 35 | ``` 36 | 37 | Then you can run the shiny app in your preferred R environment. 38 | -------------------------------------------------------------------------------- /drag-and-drop/app.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-04-24 5 | #' MODIFIED: 2020-11-20 6 | #' PURPOSE: example application for creating drag/drop elements 7 | #' STATUS: working 8 | #' PACKAGES: shiny 9 | #' COMMENTS: NA 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | # pkgs 13 | suppressPackageStartupMessages(library(shiny)) 14 | 15 | # ui 16 | ui <- tagList( 17 | tags$head( 18 | tags$link(rel = "stylesheet", href = "styles.css") 19 | ), 20 | tags$main( 21 | tags$h2("Moveable Elements"), 22 | tags$p( 23 | "Order the cards by the number of cases or by group.", 24 | "Drag and drop a card into the drop zone or on top of", 25 | "another card. Press 'done' when you are finished." 26 | ), 27 | tags$div( 28 | class = "dragarea", 29 | 30 | # primary elements 31 | draggable_card("groupA", "Group A", "14 cases"), 32 | draggable_card("groupB", "Group B", "3 cases"), 33 | draggable_card("groupC", "Group C", "33 cases"), 34 | draggable_card("groupD", "Group D", "7 cases"), 35 | draggable_card("groupE", "Group E", "21 cases"), 36 | 37 | # extra drop zone 38 | tags$div( 39 | class = "droparea", 40 | tags$p("Drop here") 41 | ) 42 | ), 43 | tags$button( 44 | id = "submit", 45 | type = "submit", 46 | class = "shiny-bound-input action-button", 47 | "Done" 48 | ) 49 | ), 50 | tags$script(src = "index.js") 51 | ) 52 | 53 | # server 54 | server <- function(input, output, session) { 55 | } 56 | 57 | # app 58 | shinyApp(ui, server) -------------------------------------------------------------------------------- /drag-and-drop/drag_and_drop_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/drag-and-drop/drag_and_drop_demo.gif -------------------------------------------------------------------------------- /drag-and-drop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drag-and-drop", 3 | "version": "1.0.1", 4 | "description": "create a drag and drop component", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } 22 | -------------------------------------------------------------------------------- /drag-and-drop/www/styles.css: -------------------------------------------------------------------------------- 1 | /* reseting base styles */ 2 | 3 | html, body { 4 | padding: 0; 5 | margin: 0; 6 | font-family: Helvetica, Arial, sans-serif; 7 | font-size: 16pt; 8 | } 9 | 10 | body { 11 | background-color: #f6f6f6; 12 | } 13 | 14 | h2 { 15 | padding: 0; 16 | margin: 0; 17 | line-height: 1.3; 18 | color: #252525; 19 | } 20 | 21 | p { 22 | padding: 0; 23 | margin: 0; 24 | margin-bottom: 16px; 25 | line-height: 1.6; 26 | color: #3f454b; 27 | } 28 | 29 | 30 | main { 31 | width: 90%; 32 | margin: 32px auto; 33 | margin-bottom: 72px; 34 | } 35 | 36 | @media (min-width: 912px) { 37 | main { 38 | max-width: 712px; 39 | } 40 | } 41 | 42 | /* styling for cards */ 43 | 44 | .card { 45 | position: relative; 46 | box-sizing: border-box; 47 | padding: 0 32px; 48 | border-radius: 8px; 49 | margin-bottom: 24px; 50 | } 51 | 52 | .card { 53 | border: 2px solid transparent; 54 | background-color: white; 55 | } 56 | 57 | /* reset p margins */ 58 | 59 | .card p { 60 | margin-bottom: 0; 61 | } 62 | 63 | /* adjust inner padding */ 64 | 65 | .card :first-child { 66 | padding-top: 16px; 67 | } 68 | 69 | .card :last-child { 70 | padding-bottom: 16px; 71 | } 72 | 73 | /* highlighting class for cards */ 74 | .highlighting { 75 | border-color: #09BC8A; 76 | } 77 | 78 | /* position the card icon */ 79 | 80 | .card .card-icon { 81 | position: absolute; 82 | display: block; 83 | top: 40%; 84 | right: 32px; 85 | } 86 | 87 | /* drag container */ 88 | .dragarea { 89 | margin-bottom: 32px; 90 | } 91 | 92 | /* classes to add when an element is dragged */ 93 | 94 | .drag { 95 | -webkit-box-shadow: 0 0 9px 3px hsla(0, 0%, 0%, 0.3); 96 | box-shadow: 0 0 9px 3px hsla(0, 0%, 0%, 0.3); 97 | } 98 | 99 | /* droparea styles */ 100 | 101 | .droparea { 102 | display: -webkit-flex; 103 | display: -ms-flexbox; 104 | display: flex; 105 | -webkit-box-pack: center; 106 | -ms-flex-pack: center; 107 | justify-content: center; 108 | -webkit-box-align: center; 109 | -ms-flex-align: center; 110 | align-items: center; 111 | height: 3em; 112 | border-radius: 6px; 113 | padding: 16px 32px; 114 | border-width: 3px; 115 | border-style: dashed; 116 | border-color: #bdbdbd; 117 | text-align: center; 118 | } 119 | 120 | .droparea p { 121 | color: #b8b8b8; 122 | margin-bottom: 0; 123 | text-transform: uppercase; 124 | letter-spacing: 2px; 125 | font-size: 11pt; 126 | font-weight: bold; 127 | } 128 | 129 | /* droparea focus */ 130 | .droparea.focus { 131 | background-color: White; 132 | border-color: #09BC8A; 133 | } 134 | 135 | /* submit button */ 136 | #submit { 137 | display: block; 138 | width: 100%; 139 | margin: 0; 140 | padding: 16px 0; 141 | font-size: 14pt; 142 | font-weight: bold; 143 | text-transform: uppercase; 144 | letter-spacing: 2px; 145 | background-color: hsla(163, 91%, 39%, 0.1); 146 | border: 2px solid #09BC8A; 147 | color: #09BC8A; 148 | } -------------------------------------------------------------------------------- /get-window-dims/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fget-window-dims%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fget-window-dims%2Fpackage.json) 4 | 5 | 6 | # Get Window Dimensions 7 | 8 | This shiny app demonstrates how to read and use the browser dimensions in the Shiny server. This example is a demonstration on setting up Shiny and JavaScript communication rather than using window height or width for modifying the client (as this should be used on the JS not shiny). 9 | 10 | You can find more information about this application in the blog post [get window dimensions](https://davidruvolo51.github.io/shinytutorials/tutorials/get-window-dims/). 11 | 12 | ## Getting Started 13 | 14 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 15 | 16 | ### Running in your R environment 17 | 18 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 19 | 20 | ```r 21 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "get-window-dims") 22 | ``` 23 | 24 | ### Cloning the subdirectory 25 | 26 | You can clone the data editor subdirectory using `git sparse-checkout`. 27 | 28 | ```bash 29 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 30 | cd shinyAppTutorials 31 | git sparse-checkout init --cone 32 | git sparse-checkout set get-window-dims 33 | ``` 34 | 35 | Then you can run the shiny app in your preferred R environment. 36 | -------------------------------------------------------------------------------- /get-window-dims/app.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-05-18 5 | #' MODIFIED: 2020-11-20 6 | #' PURPOSE: get window dimensions app 7 | #' STATUS: working 8 | #' PACKAGES: shiny; json 9 | #' COMMENTS: NA 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | # pkgs 13 | suppressPackageStartupMessages(library(shiny)) 14 | 15 | # ui 16 | ui <- tagList( 17 | tags$head( 18 | tags$link(rel = "stylesheet", href = "styles.css"), 19 | tags$title("Get Window Dims | shinyAppTutorials") 20 | ), 21 | tags$main( 22 | tags$h2("Get Window Dimensions Example"), 23 | tags$p("Resize the browser."), 24 | verbatimTextOutput("win_size") 25 | ), 26 | tags$script(src = "index.js") 27 | ) 28 | 29 | # server 30 | server <- function(input, output, session) { 31 | observeEvent(input$window, { 32 | d <- jsonlite::fromJSON(input$window) 33 | output$win_size <- renderPrint({ 34 | d 35 | }) 36 | }) 37 | } 38 | 39 | 40 | # app 41 | shinyApp(ui, server) -------------------------------------------------------------------------------- /get-window-dims/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-window-dims", 3 | "version": "1.1.0", 4 | "description": "sending data from javascript", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } 22 | -------------------------------------------------------------------------------- /get-window-dims/www/index.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // FILE: index.js 3 | // AUTHOR: David Ruvolo 4 | // CREATED: 2020-05-18 5 | // MODIFIED: 2020-05-18 6 | // PURPOSE: js handlers for sending data to the shiny server 7 | // DEPENDENCIES: NA 8 | // STATUS: working 9 | // COMMENTS: NA 10 | //////////////////////////////////////////////////////////////////////////////// 11 | // BEGIN 12 | 13 | // getWindowSize 14 | // define a function that returns window dimensions 15 | function getWindowSize() { 16 | const isWindow = typeof window !== "undefined"; 17 | return { 18 | width: isWindow ? window.innerWidth : undefined, 19 | height: isWindow ? window.innerHeight : undefined 20 | } 21 | } 22 | 23 | // setWindowSize 24 | // define a function that sets window size to a shiny input 25 | function setWindowSize() { 26 | Shiny.setInputValue("window", JSON.stringify(getWindowSize())); 27 | } 28 | 29 | // attach when shiny:connected (need to use jQuery's 'on') 30 | $(document).on("shiny:connected", function() { 31 | 32 | // run on shiny:connected (i.e., initial) 33 | setWindowSize(); 34 | 35 | // add listener (i.e., run when window is resized) 36 | window.addEventListener("resize", setWindowSize); 37 | }); -------------------------------------------------------------------------------- /get-window-dims/www/styles.css: -------------------------------------------------------------------------------- 1 | 2 | /* force 0 margin and padding */ 3 | html, body { 4 | padding: 0; 5 | margin: 0; 6 | font-size: 16pt; 7 | font-family: Arial, Helvetica, sans-serif; 8 | } 9 | 10 | /* main */ 11 | main { 12 | margin: 16px auto; 13 | width: 90%; 14 | } 15 | 16 | /* h2 */ 17 | h2 { 18 | padding: 0; 19 | margin: 0; 20 | margin-bottom: 12px; 21 | color: #353535; 22 | } 23 | 24 | /* paragraphs */ 25 | p { 26 | padding: 0; 27 | margin: 0; 28 | margin-bottom: 12px; 29 | color: #3f454b; 30 | } 31 | 32 | /* text output */ 33 | .shiny-text-output { 34 | display: block; 35 | background-color: #f6f6f6; 36 | color: #353535; 37 | box-sizing: border-box; 38 | padding: 12px; 39 | } 40 | 41 | /* media query */ 42 | @media (min-width: 972px) { 43 | main { 44 | max-width: 972px; 45 | } 46 | } -------------------------------------------------------------------------------- /internal-links-basic-ex/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Finternal-links-basic-ex%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=critical&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Finternal-links-basic-ex%2Fpackage.json) 4 | 5 | 6 | # Internal Links (Basic Example) 7 | 8 | This example is outdated. It still works, but the `shinyLink` example provides an easy-to-use Shiny component. See the [Shiny Link](https://github.com/davidruvolo51/shinyAppTutorials/tree/prod/shiny-links) subdirectory and the [Creating Internal Links: Defining Application Navigation](https://davidruvolo51.github.io/shinytutorials/tutorials/shiny-link/) post for more information. (17 September 2020). 9 | -------------------------------------------------------------------------------- /internal-links-basic-ex/app.R: -------------------------------------------------------------------------------- 1 | #'////////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2017-11-27 5 | #' MODIFIED: 2021-03-23 6 | #' PURPOSE: demonstration how to create links to other tabs in shiny 7 | #' STATUS: working 8 | #' PACKAGES: shiny 9 | #' COMMENTS: using index.js file located in www/js 10 | #'////////////////////////////////////////////////////////////////////////////// 11 | 12 | 13 | suppressPackageStartupMessages(library(shiny)) 14 | 15 | ui <- tagList( 16 | tags$head( 17 | tags$script(type = "text/javascript", src = "js/index.js"), 18 | tags$style(type = "text/css", "a {cursor:pointer;}") 19 | ), 20 | navbarPage( 21 | title = "Internal Links Demo", 22 | tabPanel( 23 | "Home", 24 | value = "home", 25 | h1("Home"), 26 | tags$a("Go to 'about' page", onclick = "customHref('about')") 27 | ), 28 | tabPanel( 29 | "About", 30 | value = "about", 31 | h1("About"), 32 | tags$a("Go to 'contact me' page", onclick = "customHref('contact')") 33 | ), 34 | tabPanel("Contact Me", value = "contact", 35 | tags$h1("Contact Me"), 36 | tags$a("Go to 'home' page", onclick = "customHref('home')") 37 | ) 38 | ) 39 | ) 40 | 41 | 42 | server <- function(input, output, session) { 43 | } 44 | 45 | 46 | shinyApp(ui, server) -------------------------------------------------------------------------------- /internal-links-basic-ex/internal-links-basic-ex.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /internal-links-basic-ex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "internal-links-basic-ex", 3 | "version": "1.1.0", 4 | "description": "Create links from one shiny page to another", 5 | "author": "@dcruvolo", 6 | "status": "archived", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } 22 | -------------------------------------------------------------------------------- /internal-links-basic-ex/www/js/index.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // FILE: index.js 3 | // AUTHOR: David Ruvolo 4 | // CREATED: 2019-11-01 5 | // MODIFIED: 2019-11-01 6 | // PURPOSE: custom href function for use in shiny apps 7 | // DEPENDENCIES: NA 8 | // STATUS: working 9 | // COMMENTS: this script was updated to js ES6 10 | //////////////////////////////////////////////////////////////////////////////// 11 | // BEGIN 12 | const customHref = function(link){ 13 | const links = document.getElementsByTagName("a"); 14 | Object.entries(links).forEach( (elem, i) => { 15 | if(elem[1].getAttribute("data-value") === link){ 16 | elem[1].click() 17 | } 18 | }); 19 | } -------------------------------------------------------------------------------- /internal-links-demo/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Finternal-links-demo%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=critical&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Finternal-links-demo%2Fpackage.json) 4 | 5 | 6 | # Internal Links (demo) 7 | 8 | This example is outdated. It still works, but the `shinyLink` example provides an easy-to-use Shiny component. See the [Shiny Link](https://github.com/davidruvolo51/shinyAppTutorials/tree/prod/shiny-links) subdirectory and the [Creating Internal Links: Defining Application Navigation](https://davidruvolo51.github.io/shinytutorials/tutorials/shiny-link/) post for more information. (17 September 2020). 9 | -------------------------------------------------------------------------------- /internal-links-demo/internal-links-demo.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /internal-links-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "internal-links-demo", 3 | "version": "1.1.0", 4 | "description": "Navigate to a specific tab on another page", 5 | "author": "@dcruvolo", 6 | "status": "archived", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } 22 | -------------------------------------------------------------------------------- /internal-links-demo/www/js/index.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // FILE: index.js 3 | // AUTHOR: David Ruvolo 4 | // CREATED: 2019-11-01 5 | // MODIFIED: 2019-11-01 6 | // PURPOSE: custom href function for use in shiny apps 7 | // DEPENDENCIES: NA 8 | // STATUS: working 9 | // COMMENTS: this script was updated to js ES6 10 | //////////////////////////////////////////////////////////////////////////////// 11 | // BEGIN 12 | const customHref = function(link){ 13 | const links = document.getElementsByTagName("a"); 14 | Object.entries(links).forEach( (elem, i) => { 15 | if(elem[1].getAttribute("data-value") === link){ 16 | elem[1].click() 17 | } 18 | }); 19 | } -------------------------------------------------------------------------------- /internal-links/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Finternal-links%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=critical&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Finternal-links%2Fpackage.json) 4 | 5 | 6 | # Internal Links 7 | 8 | This example is outdated. It still works, but the `shinyLink` example provides an easy-to-use Shiny component. See the [Shiny Link](https://github.com/davidruvolo51/shinyAppTutorials/tree/prod/shiny-links) subdirectory and the [Creating Internal Links: Defining Application Navigation](https://davidruvolo51.github.io/shinytutorials/tutorials/shiny-link/) post for more information. (17 September 2020). 9 | -------------------------------------------------------------------------------- /internal-links/internal-links-vis.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/internal-links/internal-links-vis.gif -------------------------------------------------------------------------------- /internal-links/internal-links.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /internal-links/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "internal-links", 3 | "version": "1.1.0", 4 | "description": "Create a link from a leaflet popup to a shiny page", 5 | "author": "@dcruvolo", 6 | "status": "archived", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } 22 | -------------------------------------------------------------------------------- /internal-links/www/imgs/aditya-vyas-BLkamCbE-so-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/internal-links/www/imgs/aditya-vyas-BLkamCbE-so-unsplash.jpg -------------------------------------------------------------------------------- /internal-links/www/imgs/hector-arguello-canals-2x6vURol6cM-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/internal-links/www/imgs/hector-arguello-canals-2x6vURol6cM-unsplash.jpg -------------------------------------------------------------------------------- /internal-links/www/imgs/tomas-eidsvold-qc9OKXKTETk-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/internal-links/www/imgs/tomas-eidsvold-qc9OKXKTETk-unsplash.jpg -------------------------------------------------------------------------------- /internal-links/www/js/index.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // FILE: index.js 3 | // AUTHOR: David Ruvolo 4 | // CREATED: 2019-11-01 5 | // MODIFIED: 2019-11-01 6 | // PURPOSE: custom href function for use in shiny apps 7 | // DEPENDENCIES: NA 8 | // STATUS: working 9 | // COMMENTS: this script was updated to js ES6 10 | //////////////////////////////////////////////////////////////////////////////// 11 | // BEGIN 12 | const customHref = function(link){ 13 | const links = document.getElementsByTagName("a"); 14 | Object.entries(links).forEach( (elem, i) => { 15 | if(elem[1].getAttribute("data-value") === link){ 16 | elem[1].click() 17 | } 18 | }); 19 | } -------------------------------------------------------------------------------- /js-handlers/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fjs-handlers%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fjs-handlers%2Fpackage.json) 4 | 5 | 6 | # Getting Started with JS Handlers 7 | 8 | Learn how to add custom js functions for more interactivity in shiny apps. In this example, we will create a dark and light theme toggle and learn how to save the user preferences for later use. For more information, checkout the blog post [Getting started with javascript handlers: Linking R and Javascript](https://davidruvolo51.github.io/shinytutorials/tutorials/js-handlers/). 9 | 10 | ## Getting Started 11 | 12 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 13 | 14 | ### Running in your R environment 15 | 16 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 17 | 18 | ```r 19 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "js-handlers") 20 | ``` 21 | 22 | ### Cloning the subdirectory 23 | 24 | You can clone the data editor subdirectory using `git sparse-checkout`. 25 | 26 | ```bash 27 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 28 | cd shinyAppTutorials 29 | git sparse-checkout init --cone 30 | git sparse-checkout set js-handlers 31 | ``` 32 | 33 | Then you can run the shiny app in your preferred R environment. 34 | -------------------------------------------------------------------------------- /js-handlers/app.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2021-02-22 5 | #' MODIFIED: 2021-02-26 6 | #' PURPOSE: Shiny app 7 | #' STATUS: working 8 | #' PACKAGES: Shiny 9 | #' COMMENTS: NA 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | 13 | suppressPackageStartupMessages(library(shiny)) 14 | 15 | 16 | ui <- tagList( 17 | tags$head( 18 | tags$meta(charset = "utf-8"), 19 | tags$meta(`http-equiv` = "X-UA-Compatible", content = "IE=edge"), 20 | tags$meta( 21 | name = "viewport", 22 | content = "width=device-width, initial-scale=1" 23 | ), 24 | tags$link( 25 | type = "text/css", 26 | rel = "stylesheet", 27 | href = "styles.css" 28 | ), 29 | tags$title("Custom JS Handlers | shinyAppTutorials") 30 | ), 31 | tags$header( 32 | tags$span( 33 | class = "theme-label", 34 | "current theme:", tags$output(id = "themeStatus") 35 | ) 36 | ), 37 | tags$main( 38 | tags$section(`aria-labelledby` = "title", 39 | tags$h1("Custom JS Handlers", id = "title"), 40 | tags$p( 41 | "This shiny app demonstrates how to create your own ", 42 | "javascript functions and register them with shiny ", 43 | "server. In this example, we created a simple javascript ", 44 | "that toggles a css class and stores the user's selection to ", 45 | "local storage, as well as display the selected theme in ", 46 | "the top right corner. Click the button." 47 | ), 48 | tags$button( 49 | id = "toggle", 50 | class = "action-button shiny-bound-input", 51 | "Toggle Theme" 52 | ) 53 | ) 54 | ), 55 | tags$script(src = "index.js") 56 | ) 57 | 58 | 59 | 60 | server <- function(input, output, session) { 61 | 62 | session$sendCustomMessage(type = "setDefaultTheme", "body") 63 | 64 | observeEvent(input$toggle, { 65 | session$sendCustomMessage(type = "toggleTheme", "body") 66 | }) 67 | } 68 | 69 | 70 | 71 | shinyApp(ui, server) -------------------------------------------------------------------------------- /js-handlers/js-handlers.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | Version: 1.0 15 | 16 | RestoreWorkspace: Default 17 | SaveWorkspace: Default 18 | AlwaysSaveHistory: Default 19 | 20 | EnableCodeIndexing: Yes 21 | UseSpacesForTab: Yes 22 | NumSpacesForTab: 2 23 | Encoding: UTF-8 24 | RnwWeave: Sweave 25 | LaTeX: pdfLaTeX 26 | -------------------------------------------------------------------------------- /js-handlers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-handlers", 3 | "version": "1.1.1", 4 | "description": "getting started with custom handlers", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } 22 | -------------------------------------------------------------------------------- /js-handlers/www/index.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // FILE: index.js 3 | // AUTHOR: David Ruvolo 4 | // CREATED: 2019-09-09 5 | // MODIFIED: 2021-02-22 6 | // PURPOSE: constructors and Shiny bindings 7 | // DEPENDENCIES: NA 8 | // STATUS: working 9 | // COMMENTS: NA 10 | //////////////////////////////////////////////////////////////////////////////// 11 | 12 | // init with global var 13 | function handlers() { 14 | this.defaultTheme = "light"; 15 | this.lastSavedTheme = null; 16 | this.currentTheme = null; 17 | } 18 | 19 | // add css 20 | // @param elem selector for target element 21 | // @param css name of the css class to add 22 | handlers.prototype.addCSS = function (elem, css) { 23 | var elem = document.querySelector(elem); 24 | elem.classList.add(css); 25 | } 26 | 27 | // add css 28 | // @param elem selector for target element 29 | // @param css name of the css class to remove 30 | handlers.prototype.removeCSS = function (elem, css) { 31 | var elem = document.querySelector(elem); 32 | elem.classList.remove(css); 33 | } 34 | 35 | // setCurrentTheme 36 | // @param elem selector for target element 37 | handlers.prototype.setCurrentTheme = function (elem) { 38 | var target = document.querySelector(elem); 39 | var css = Array.from(target.classList)[0]; 40 | if (typeof css === "undefined" || css === null) { 41 | this.currentTheme = this.defaultTheme; 42 | } else { 43 | this.currentTheme = css; 44 | } 45 | } 46 | 47 | // save option to local storage 48 | handlers.prototype.save = function (value) { 49 | localStorage.setItem("theme", value); 50 | } 51 | 52 | // load option from local storage 53 | handlers.prototype.load = function () { 54 | return this.lastSavedTheme = localStorage.getItem("theme"); 55 | } 56 | 57 | // set default theme 58 | // @param elem selector of target element to bind class to 59 | handlers.prototype.setDefault = function (elem) { 60 | this.load(); 61 | if (typeof this.lastSavedTheme === "undefined" || this.lastSavedTheme === null) { 62 | this.addCSS(elem, this.defaultTheme); 63 | } else { 64 | this.addCSS(elem, this.lastSavedTheme); 65 | } 66 | } 67 | 68 | // set innerHTML of an element 69 | // @param id ID selector of target element 70 | // @param string html string to render 71 | handlers.prototype.setInnerHtmlById = function (id, string) { 72 | var elem = document.getElementById(id); 73 | elem.innerHTML = string 74 | } 75 | 76 | 77 | var h = new handlers(); 78 | 79 | // create handler that toggles the theme and saves to local storage 80 | Shiny.addCustomMessageHandler("toggleTheme", function (value) { 81 | 82 | h.setCurrentTheme(value); 83 | 84 | if (h.currentTheme === "dark") { 85 | h.removeCSS(value, "dark"); 86 | h.addCSS(value, "light"); 87 | h.currentTheme = "light"; 88 | } else { 89 | h.removeCSS(value, "light"); 90 | h.addCSS(value, "dark"); 91 | h.currentTheme = "dark"; 92 | } 93 | 94 | h.setInnerHtmlById("themeStatus", h.currentTheme); 95 | h.save(h.currentTheme); 96 | 97 | }); 98 | 99 | // create a handler that sets the default theme based on value saved in local storage 100 | Shiny.addCustomMessageHandler("setDefaultTheme", function (value) { 101 | h.setDefault(value); 102 | 103 | h.setCurrentTheme(value); 104 | h.setInnerHtmlById("themeStatus", h.currentTheme); 105 | }); -------------------------------------------------------------------------------- /js-handlers/www/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 3 | padding: 0; 4 | margin: 0; 5 | width: 100%; 6 | height: 100vh; 7 | background-color: white; 8 | } 9 | 10 | header { 11 | position: fixed; 12 | display: block; 13 | width: 100%; 14 | top: 0; 15 | left: 0; 16 | text-align: right; 17 | } 18 | 19 | main { 20 | display: block; 21 | margin: 0 auto; 22 | width: 90%; 23 | height: 100%; 24 | background-color: transparent; 25 | } 26 | 27 | section { 28 | background-color: transparent; 29 | padding: 32px 0; 30 | } 31 | 32 | h1 { 33 | padding: 0; 34 | margin: 0; 35 | line-height: 1.3; 36 | font-size: 24pt; 37 | } 38 | 39 | p { 40 | padding: 0; 41 | margin: 0; 42 | margin-top: 8px; 43 | line-height: 1.9; 44 | font-size: 16pt; 45 | } 46 | 47 | button { 48 | display: block; 49 | -webkit-appearance: none; 50 | appearance: none; 51 | outline: none; 52 | cursor: pointer; 53 | width: 200px; 54 | padding: 12px 0; 55 | margin: 8px auto; 56 | font-size: 14pt; 57 | font-weight: 600; 58 | border: none; 59 | border: 2px solid transparent; 60 | background-color: transparent; 61 | border-radius: 4px; 62 | } 63 | 64 | .theme-label { 65 | display: block; 66 | font-size: 11pt; 67 | text-transform: uppercase; 68 | letter-spacing: 2px; 69 | margin-right: 12px; 70 | margin-top: 12px; 71 | font-weight: bold; 72 | color: #3f454b; 73 | } 74 | 75 | 76 | .light { 77 | background-color: white; 78 | } 79 | 80 | .light h1, 81 | .light .theme-label { 82 | color: #252525; 83 | } 84 | 85 | .light p { 86 | color: #3f454b; 87 | } 88 | 89 | .light .theme-label output { 90 | color: hsla(211, 65%, 50%, 1); 91 | } 92 | 93 | 94 | .light button { 95 | border-color: hsla(211, 65%, 50%, 1); 96 | background-color: hsla(211, 65%, 50%, 0.1); 97 | color: hsla(211, 65%, 50%, 1); 98 | } 99 | 100 | .light button:hover, 101 | .light button:focus { 102 | background-color: hsla(211, 65%, 50%, 1); 103 | color: hsla(211, 65%, 50%, 0.3); 104 | color: white; 105 | -webkit-box-shadow: 0 0 12px 0 #3f454b; 106 | box-shadow: 0 0 12px 0 #3f454b; 107 | } 108 | 109 | .dark { 110 | background-color: #1A1C20; 111 | } 112 | 113 | .dark h1, 114 | .dark p, 115 | .dark .theme-label { 116 | color: #f1f1f1; 117 | } 118 | 119 | .dark button { 120 | border-color: #C4DFFF; 121 | background-color: #C4DFFF; 122 | color: #3f454b; 123 | } 124 | 125 | .dark button:hover, 126 | .dark button:focus { 127 | -webkit-box-shadow: 0 0 12px 0 #C4DFFF; 128 | box-shadow: 0 0 12px 0 #C4DFFF; 129 | } 130 | 131 | .dark .theme-label output { 132 | color: #C4DFFF; 133 | } 134 | 135 | @media screen and (min-width: 712px) { 136 | 137 | main { 138 | display: -webkit-flex; 139 | display: -ms-flex; 140 | display: flex; 141 | -webkit-box-pack: center; 142 | -ms-flex-pack: center; 143 | justify-content: center; 144 | -webkit-box-align: center; 145 | -ms-flex-align: center; 146 | align-items: center; 147 | } 148 | 149 | section { 150 | max-width: 912px; 151 | } 152 | } -------------------------------------------------------------------------------- /leaflet-loading-screens/R/leaflet_loader.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: leaflet_loader.R 3 | #' AUTHOR: @dcruvolo 4 | #' CREATED: 2020-04-02 5 | #' MODIFIED: 2020-04-02 6 | #' PURPOSE: functional component for creating custom loaders for leaflet 7 | #' STATUS: working 8 | #' PACKAGES: shiny(loaded in the app) 9 | #' COMMENTS: css can be found in www/styles.css 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | # loading screen functional component: child element 13 | loading_elem <- function(id, text = NULL) { 14 | stopifnot(!is.null(id)) 15 | 16 | # generate element with dots 17 | el <- tags$div( 18 | id = id, 19 | class = "visually-hidden loading-ui loading-dots", 20 | `aria-hidden` = "true", 21 | tags$div( 22 | class = "dots-container", 23 | tags$span(class = "dots", id = "dot1"), 24 | tags$span(class = "dots", id = "dot2"), 25 | tags$span(class = "dots", id = "dot3") 26 | ) 27 | ) 28 | 29 | # add message if specified + update attribs 30 | if (length(text) > 0) { 31 | el$attribs$class <- "loading-ui loading-text" 32 | el$children <- tags$p( 33 | class = "loading-message", 34 | as.character(text) 35 | ) 36 | } 37 | 38 | # return 39 | return(el) 40 | } 41 | 42 | #' loading screen: primary component wrapper around child 43 | #' and leafletOuput 44 | loading_message <- function(..., id, text = NULL) { 45 | tags$div( 46 | class = "loading-container", 47 | tags$span( 48 | class = "visually-hidden", 49 | "map is loading" 50 | ), 51 | loading_elem(id = id, text = text), 52 | ... 53 | ) 54 | } -------------------------------------------------------------------------------- /leaflet-loading-screens/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fleaflet-loading-screens%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fleaflet-loading-screens%2Fpackage.json) 4 | 5 | 6 | # Leaflet Loading UIs in Shiny Apps 7 | 8 | Create custom loading UI to display while leaflet maps are rendering. More information can be found in the longer post [leaflet-loading-screens](https://davidruvolo51.github.io/shinytutorials/tutorials/leaflet-loading-screens/). 9 | 10 | ## Getting Started 11 | 12 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 13 | 14 | ### Running in your R environment 15 | 16 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 17 | 18 | ```r 19 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "leaflet-loading-screens") 20 | ``` 21 | 22 | ### Cloning the subdirectory 23 | 24 | You can clone the data editor subdirectory using `git sparse-checkout`. 25 | 26 | ```bash 27 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 28 | cd shinyAppTutorials 29 | git sparse-checkout init --cone 30 | git sparse-checkout set leaflet-loading-screens 31 | ``` 32 | 33 | Then you can run the shiny app in your preferred R environment. 34 | -------------------------------------------------------------------------------- /leaflet-loading-screens/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/leaflet-loading-screens/demo.gif -------------------------------------------------------------------------------- /leaflet-loading-screens/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-loading-screens", 3 | "version": "1.0.0", 4 | "description": "creating a loading screen for leaflet", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } 22 | -------------------------------------------------------------------------------- /leaflet-loading-screens/www/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | main { 7 | width: 90%; 8 | margin: 0 auto; 9 | } 10 | 11 | /* class to visually hide loading screens */ 12 | .visually-hidden { 13 | position: absolute; 14 | clip: rect(0, 0, 0, 0); 15 | clip: rect(0 0 0 0); 16 | width: 1px; 17 | height: 1px; 18 | overflow: hidden; 19 | white-space: nowrap; 20 | } 21 | 22 | /* styling for loading container */ 23 | .loading-container { 24 | position: relative; 25 | } 26 | 27 | /* loading ui */ 28 | .loading-container .loading-ui { 29 | display: flex; 30 | justify-content: center; 31 | align-items: center; 32 | position: absolute; 33 | width: 100%; 34 | height: 100%; 35 | background-color: #f6f6f6; 36 | text-align: center; 37 | z-index: 9999; 38 | } 39 | 40 | /* styles for loading message */ 41 | .loading-container .loading-message { 42 | font-size: 16pt; 43 | font-weight: bold; 44 | letter-spacing: 1.5px; 45 | } 46 | 47 | 48 | /* default loading: blinking dots */ 49 | .loading-container .dots-container .dots { 50 | display: inline-block; 51 | margin-right: 12px; 52 | background-color: #299996; 53 | width: 18px; 54 | height: 18px; 55 | border-radius: 50%; 56 | border: none; 57 | } 58 | 59 | @-webkit-keyframes dotBlink { 60 | 0% { opacity: 0; } 61 | 75% { opacity: 1; } 62 | } 63 | 64 | @keyframes dotBlink { 65 | 0% { opacity: 0; } 66 | 75% { opacity: 1; } 67 | } 68 | 69 | #dot1 { 70 | -webkit-animation: dotBlink 1s infinite; 71 | animation: dotBlink 1s infinite; 72 | } 73 | 74 | #dot2 { 75 | -webkit-animation: dotBlink 1s .2s infinite; 76 | animation: dotBlink 1s .2s infinite; 77 | } 78 | 79 | #dot3 { 80 | -webkit-animation: dotBlink 1s .4s infinite; 81 | animation: dotBlink 1s .4s infinite; 82 | } -------------------------------------------------------------------------------- /login-screen/R/handlers.R: -------------------------------------------------------------------------------- 1 | #' //////////////////////////////////////////////////////////////////////////// 2 | #' FILE: server_01_handlers.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2019-11-07 5 | #' MODIFIED: 2021-05-29 6 | #' PURPOSE: server handlers for passing data from shiny server to UI via js 7 | #' STATUS: working 8 | #' PACKAGES: NA 9 | #' COMMENTS: see www/js/index.js for corresponding js handlers 10 | #' //////////////////////////////////////////////////////////////////////////// 11 | 12 | #' @title js 13 | #' @description object containing methods for interacting with the client 14 | #' @noRd 15 | js <- list(class = "r-js-client-methods") 16 | 17 | 18 | #' @title inner_html 19 | #' @description render HTML content inside an existing element 20 | #' @param id a string containing the HTML ID of the output element 21 | #' @param html HTML content serialized 22 | #' @noRd 23 | js$inner_html <- function(id, html) { 24 | session <- shiny::getDefaultReactiveDomain() 25 | session$sendCustomMessage( 26 | type = "inner_html", 27 | message = list(id = id, html = html) 28 | ) 29 | } 30 | 31 | #' @title add_css 32 | #' @description apply one or more CSS classes to an HTML element 33 | #' @param id a string containing the HTML ID of the target element 34 | #' @param css a string containing one or more CSS classes 35 | #' @noRd 36 | js$add_css <- function(id, css) { 37 | session <- shiny::getDefaultReactiveDomain() 38 | session$sendCustomMessage( 39 | type = "add_css", 40 | message = list( 41 | id = id, 42 | css = css 43 | ) 44 | ) 45 | } 46 | 47 | #' @title remove_css 48 | #' @description remove a CSS class from an HTML element 49 | #' @param id a string containing the HTML ID of the target element 50 | #' @param css a string containing the CSS classname to remove 51 | #' @noRd 52 | js$remove_css <- function(id, css) { 53 | session <- shiny::getDefaultReactiveDomain() 54 | session$sendCustomMessage( 55 | type = "remove_css", 56 | message = list( 57 | id = id, 58 | css = css 59 | ) 60 | ) 61 | } -------------------------------------------------------------------------------- /login-screen/R/main_ui.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: main_ui.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2021-05-29 5 | #' MODIFIED: 2021-07-10 6 | #' PURPOSE: main screen to be rendered on successful login attempt 7 | #' STATUS: working 8 | #' PACKAGES: shiny 9 | #' COMMENTS: NA 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | #' Main UI 13 | #' @param userdata object containing username and user emoji 14 | #' @noRd 15 | main_ui <- function(id, userdata) { 16 | ns <- NS(id) 17 | tags$main( 18 | class = "main", 19 | tags$div( 20 | class = "header", 21 | tags$h1( 22 | "Welcome,", 23 | tags$span(userdata$username), 24 | tags$span(userdata$emoji), 25 | "!" 26 | ), 27 | tags$p("You are now signed in."), 28 | actionButton(inputId = ns("signout"), "Sign out") 29 | ) 30 | ) 31 | } 32 | 33 | 34 | #' Main UI Server 35 | #' Process Logging out 36 | #' @param id module instance ID 37 | #' @param logged a global reactive value for managing login state 38 | #' @noRd 39 | main_ui_server <- function(id, logged) { 40 | moduleServer( 41 | id, 42 | function(input, output, session) { 43 | observeEvent(input$signout, logged(FALSE)) 44 | } 45 | ) 46 | } -------------------------------------------------------------------------------- /login-screen/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Flogin-screen%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Flogin-screen%2Fpackage.json) 4 | 5 | 6 | # Custom Log in screen for shinyapps 7 | 8 | Implement user authentication into your shiny app. For more information, checkout the blog post [Login Screen: Building a simple password protected shiny app](https://davidruvolo51.github.io/shinytutorials/tutorials/login-screen/). 9 | 10 | This method offers some sort of "protection" for your app. I've used this method to create a project management tool to manage daily activities and tasks. It was hosted on department servers and I added this login screen only those with an account could view it. If you have sensitive data, you should follow your organization's guidelines for best practices and security guidelines. 11 | 12 | Test out the app with one of the following accounts. 13 | 14 | | type | Username | password | 15 | |----------|----------|----------| 16 | | standard | koala | 8888 | 17 | | admin | kangaroo | 9999 | 18 | 19 | **Note**: Userdata should be stored in an external database. To make a standalone example, the demo accounts are created at the top of `app.R`. Ideally, user data should be stored in another secure location (e.g., SQL database) and the server-side of the login module should send and process the user validation requests. 20 | 21 | ## Getting Started 22 | 23 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 24 | 25 | ### Running in your R environment 26 | 27 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 28 | 29 | ```r 30 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "login-screen") 31 | ``` 32 | 33 | ### Cloning the subdirectory 34 | 35 | You can clone the data editor subdirectory using `git sparse-checkout`. 36 | 37 | ```bash 38 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 39 | cd shinyAppTutorials 40 | git sparse-checkout init --cone 41 | git sparse-checkout set login-screen 42 | ``` 43 | 44 | Then you can run the shiny app in your preferred R environment. 45 | -------------------------------------------------------------------------------- /login-screen/app.R: -------------------------------------------------------------------------------- 1 | #' //////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2016-07-01 5 | #' MODIFIED: 2021-07-19 6 | #' PURPOSE: login screen example 7 | #' STATUS: working 8 | #' PACKAGES: shiny; sodium 9 | #' COMMENTS: NA 10 | #' //////////////////////////////////////////////////////////////////////////// 11 | 12 | suppressPackageStartupMessages(library(shiny)) 13 | 14 | # define users - ideally stored in a database 15 | users <- data.frame( 16 | "username" = c("koala", "kangaroo"), 17 | "password" = sapply(c("8888", "9999"), sodium::password_store), 18 | "emoji" = c("🐨", "🦘") 19 | ) 20 | 21 | 22 | ui <- tagList( 23 | tags$head( 24 | tags$link(type = "text/css", rel = "stylesheet", href = "styles.css") 25 | ), 26 | uiOutput("app"), 27 | tags$script(src = "index.js") 28 | ) 29 | 30 | 31 | server <- function(input, output, session) { 32 | 33 | userdata <- reactiveVal(NA) 34 | logged <- reactiveVal(FALSE) 35 | 36 | signin_server("signin", users, userdata, logged) 37 | main_ui_server("app", logged) 38 | 39 | observe({ 40 | if (logged()) { 41 | output$app <- renderUI(main_ui("app", userdata())) 42 | } else { 43 | output$app <- renderUI(signin_ui("signin")) 44 | } 45 | }) 46 | } 47 | 48 | 49 | shinyApp(ui, server) -------------------------------------------------------------------------------- /login-screen/login-screen.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /login-screen/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login-screen", 3 | "version": "1.1.0", 4 | "description": "Adding *basic* user authentication to shiny apps", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } 22 | -------------------------------------------------------------------------------- /login-screen/www/index.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // FILE: index.js 3 | // AUTHOR: David Ruvolo 4 | // CREATED: 2019-11-07 5 | // MODIFIED: 2021-05-29 6 | // PURPOSE: main index file for app 7 | // DEPENDENCIES: NA 8 | // STATUS: working 9 | // COMMENTS: see R/handlers.R 10 | //////////////////////////////////////////////////////////////////////////////// 11 | 12 | // define methods for receiving data from R 13 | const utils = (function(){ 14 | 15 | function inner_html(id, html) { 16 | document.getElementById(id).innerHTML = html; 17 | } 18 | 19 | function add_css(id, css){ 20 | document.getElementById(id).classList.add(css); 21 | } 22 | 23 | function remove_css(id, css){ 24 | document.getElementById(id).classList.remove(css); 25 | } 26 | 27 | return { 28 | add_css : add_css, 29 | remove_css : remove_css, 30 | inner_html : inner_html 31 | } 32 | 33 | })(); 34 | 35 | // bind 36 | Shiny.addCustomMessageHandler("add_css", (value) => utils.add_css(value.id, value.css)); 37 | Shiny.addCustomMessageHandler("remove_css", (value) => utils.remove_css(value.id, value.css)); 38 | Shiny.addCustomMessageHandler("inner_html", (value) => utils.inner_html(value.id, value.html)) -------------------------------------------------------------------------------- /login-screen/www/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 3 | font-size: 16pt; 4 | color: #3f454b; 5 | padding: 0; 6 | margin: 0; 7 | } 8 | 9 | h1, h2, h3 { 10 | line-height: 1.4; 11 | padding: 0; 12 | margin: 0; 13 | margin-bottom: 12px; 14 | color: #252525; 15 | } 16 | 17 | p { 18 | padding: 0; 19 | margin-bottom: 12px; 20 | line-height: 1.8; 21 | color: #3f454b; 22 | } 23 | 24 | form legend { 25 | font-size: 21pt; 26 | font-weight: 600; 27 | color: #252525; 28 | margin-bottom: 12px; 29 | } 30 | 31 | form label { 32 | font-size: 16pt; 33 | padding: 0; 34 | margin: 0; 35 | line-height: 1.4; 36 | color: #252525; 37 | } 38 | 39 | button, input { 40 | -webkit-appearance: none; 41 | -moz-appearance: none; 42 | appearance: none; 43 | display: block; 44 | border: none; 45 | } 46 | 47 | button{ 48 | width: 100%; 49 | padding: 8px 0; 50 | margin: 0; 51 | margin-top: 16px; 52 | background-color: #252525; 53 | border: 2px solid #252525; 54 | color: #f6f6f6; 55 | font-size: 11pt; 56 | text-transform: uppercase; 57 | letter-spacing: 2px; 58 | font-weight: 600; 59 | border-radius: 4px; 60 | cursor: pointer; 61 | } 62 | 63 | button:hover { 64 | background-color: white; 65 | color: #252525; 66 | } 67 | 68 | input[type=text], input[type=password]{ 69 | width: 95%; 70 | margin: 0; 71 | margin-top: 4px; 72 | padding: 8px; 73 | padding-left: 12px; 74 | font-size: 12pt; 75 | border-radius: 3px; 76 | border: 2px solid transparent; 77 | -webkit-box-shadow: inset 0 1px 4px #c5c5c5; 78 | box-shadow: inset 0 1px 4px #c5c5c5; 79 | } 80 | 81 | input[type=text]:focus, 82 | input[type=password]:focus { 83 | border-color: #252525; 84 | } 85 | 86 | .invalid-usr #username, 87 | .invalid-pwd #password, 88 | .invalid-all #username, 89 | .invalid-all #password { 90 | border: 2px solid red; 91 | } 92 | 93 | 94 | .main { 95 | width: 100%; 96 | min-height: 100vh; 97 | display: flex; 98 | justify-content: center; 99 | align-items: center; 100 | background-color: #f3f3f3; 101 | } 102 | 103 | 104 | .signin-form, .header { 105 | display: block; 106 | width: 85%; 107 | margin: 0 auto; 108 | padding: 24px; 109 | background-color: white; 110 | border-radius: 4px; 111 | box-shadow: 0 0 8px 2px hsla(0, 0%, 0%, 0.18); 112 | } 113 | 114 | .main .header h1 span { 115 | color: #2d7ddd; 116 | } 117 | 118 | .error-message { 119 | display: block; 120 | color: red; 121 | font-size: 11pt; 122 | text-transform: uppercase; 123 | letter-spacing: 2px; 124 | font-weight: 600; 125 | margin-bottom: 16px; 126 | } 127 | 128 | 129 | @media screen and (min-width: 912px){ 130 | 131 | .signin-form, .header { 132 | max-width: 500px; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /progress-bars-example/R/pages.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: pages.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-03-28 5 | #' MODIFIED: 2020-11-21 6 | #' PURPOSE: misc pages to demonstate subpages 7 | #' STATUS: working 8 | #' PACKAGES: stringr 9 | #' COMMENTS: NA 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | # function to generate lipsum paragraphs 13 | lorem_lipsum <- function(n, max) { 14 | paragraphs <- stringi::stri_rand_lipsum(n) 15 | html <- list() 16 | lapply(seq_len(length(paragraphs)), function(d) { 17 | page <- shiny::tagList( 18 | shiny::tags$p( 19 | class = "page-num", 20 | paste0("Page ", d, " of ", max) 21 | ), 22 | shiny::tags$p(paragraphs[d]) 23 | ) 24 | if (d > 1) { 25 | page$children <- tagList( 26 | page$children, 27 | tags$button( 28 | id = "previousPage", 29 | class = "action-button shiny-bound-input secondary", 30 | "Previous" 31 | ) 32 | ) 33 | } 34 | if (d < max) { 35 | page$children <- tagList( 36 | page$children, 37 | tags$button( 38 | id = "nextPage", 39 | class = "action-button shiny-bound-input primary", 40 | "Next" 41 | ) 42 | ) 43 | } 44 | html[[d]] <<- page 45 | }) 46 | return(html) 47 | } 48 | 49 | # save data 50 | pages <- lorem_lipsum(n = 6, max = 6) 51 | -------------------------------------------------------------------------------- /progress-bars-example/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fprogress-bars-example%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fprogress-bars-example%2Fpackage.json) 4 | 5 | 6 | # R6 Progressbars 7 | 8 | This application demonstrates how to create a progress bar for using in Shiny apps using R6 classes. The accompanying blog post is in progress. 9 | 10 | ## Getting Started 11 | 12 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 13 | 14 | ### Running in your R environment 15 | 16 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 17 | 18 | ```r 19 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "progress-bars-examples") 20 | ``` 21 | 22 | ### Cloning the subdirectory 23 | 24 | You can clone the data editor subdirectory using `git sparse-checkout`. 25 | 26 | ```bash 27 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 28 | cd shinyAppTutorials 29 | git sparse-checkout init --cone 30 | git sparse-checkout set progress-bars-example 31 | ``` 32 | 33 | Then you can run the shiny app in your preferred R environment. 34 | -------------------------------------------------------------------------------- /progress-bars-example/app.R: -------------------------------------------------------------------------------- 1 | #' //////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-03-28 5 | #' MODIFIED: 2021-04-28 6 | #' PURPOSE: example app demonstrating progress bars 7 | #' STATUS: working 8 | #' PACKAGES: shiny; R6 9 | #' COMMENTS: NA 10 | #' //////////////////////////////////////////////////////////////////////////// 11 | 12 | # install 13 | # install.packages("shiny") 14 | # install.packages("R6") 15 | 16 | # pkgs 17 | suppressPackageStartupMessages(library(shiny)) 18 | 19 | # init new progress bar 20 | app_progress <- progressbar(min = 0, start = 0, max = 6) 21 | 22 | # ui 23 | ui <- tagList( 24 | browsertools::use_browsertools(), 25 | tags$head( 26 | tags$link(rel = "stylesheet", href = "styles.css") 27 | ), 28 | app_progress$bar( 29 | inputId = "appProgress", 30 | fill = "#299996", 31 | fixed = TRUE, 32 | text = "page {value} of {max}" 33 | ), 34 | tags$main( 35 | id = "main", 36 | class = "main", 37 | tags$section( 38 | tags$h2("Lorem Ipsum"), 39 | uiOutput("page") 40 | ), 41 | tags$script(src = "index.js") 42 | ) 43 | ) 44 | 45 | # server 46 | server <- function(input, output, session) { 47 | 48 | # set default page 49 | page_counter <- reactiveVal(1) 50 | app_progress$increase() 51 | observe({ 52 | output$page <- renderUI({ 53 | pages[[page_counter()]] 54 | }) 55 | }) 56 | 57 | # event for next page 58 | observeEvent(input$nextPage, { 59 | page_counter(page_counter() + 1) 60 | app_progress$increase() 61 | }) 62 | 63 | # # event for previous page 64 | observeEvent(input$previousPage, { 65 | page_counter(page_counter() - 1) 66 | app_progress$decrease() 67 | }) 68 | } 69 | 70 | # app 71 | shinyApp(ui, server) 72 | -------------------------------------------------------------------------------- /progress-bars-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "progress-bars-example", 3 | "version": "1.1.0", 4 | "description": "creating a shiny progressbar using R6 classes", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": ["r", "shiny", "tutorials"], 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 15 | }, 16 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 17 | } 18 | -------------------------------------------------------------------------------- /progress-bars-example/www/index.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // FILE: index.js 3 | // AUTHOR: David Ruvolo 4 | // CREATED: 2021-04-28 5 | // MODIFIED: 2021-04-28 6 | // PURPOSE: progressbar shiny input binding 7 | // DEPENDENCIES: Shiny; jQuery 8 | // STATUS: working 9 | // COMMENTS: NA 10 | //////////////////////////////////////////////////////////////////////////////// 11 | 12 | // init binding 13 | var progress = new Shiny.InputBinding(); 14 | 15 | // round value 16 | // @param x value to round 17 | // @param precision rounding precision 18 | function __round(x, precision){ 19 | const p = Math.pow(10, precision); 20 | return Math.round(x * p) / p; 21 | } 22 | 23 | // gather data 24 | // @param el an element to select 25 | // @param method a string indicating which value to update (min, max, current) 26 | function progressbar__data(el) { 27 | const container = $(el).parent(); 28 | const width = container.width(); 29 | const max = $(el).attr("aria-valuemax"); 30 | const value = $(el).attr("aria-valuecurrent"); 31 | const bins = width / max; 32 | const rate = bins / width 33 | const transform_value = rate * value; 34 | return { 35 | value: value, 36 | max: max, 37 | width: width, 38 | rate: rate, 39 | transform_value: __round(transform_value, 3) 40 | } 41 | } 42 | 43 | // create binding 44 | $.extend(progress,{ 45 | 46 | // locate all instances of the progressbar element 47 | find: function(scope) { 48 | return $(scope).find(".progressbar"); 49 | }, 50 | 51 | // onRender 52 | initialize: function(el) { 53 | $(el).parent().addClass("progressbar__parent"); 54 | var d = progressbar__data(el); 55 | $(el).find(".bar").css("transform", `scaleX(${d.transform_value})`); 56 | }, 57 | 58 | // get value 59 | getValue: function(el) { 60 | return false; 61 | }, 62 | 63 | // receive messages from shiny 64 | receiveMessage: function(el, message) { 65 | $(el).attr("aria-valuecurrent", message.current); 66 | var d = progressbar__data(el); 67 | $(el).find(".bar").css("transform", `scaleX(${d.transform_value})`); 68 | $(el).attr("aria-valuetext", message.text); 69 | 70 | }, 71 | unsubcribe: function(el) { 72 | $(el).off("progress"); 73 | } 74 | }); 75 | 76 | // attach 77 | Shiny.inputBindings.register(progress); 78 | -------------------------------------------------------------------------------- /progress-bars-example/www/styles.css: -------------------------------------------------------------------------------- 1 | /* parent element */ 2 | .progressbar__parent { 3 | position: relative; 4 | } 5 | 6 | .progressbar { 7 | display: block; 8 | width: 100%; 9 | background-color: #ffffff; 10 | } 11 | 12 | .progressbar.progressbar__fixed { 13 | position: fixed; 14 | z-index: 10 15 | } 16 | 17 | .progressbar.position__top { 18 | top: 0; 19 | left: 0 20 | } 21 | 22 | .progressbar .position__bottom { 23 | bottom: 0; 24 | left: 0 25 | } 26 | 27 | .progressbar .bar { 28 | height: 13px; 29 | background-color: #bdbdbd; 30 | -webkit-transform: scaleX(0); 31 | -o-transform: scaleX(0); 32 | transform: scaleX(0); 33 | -webkit-transform-origin: 0 50%; 34 | -moz-transform-origin: 0 50%; 35 | -ms-transform-origin: 0 50%; 36 | -o-transform-origin: 0 50%; 37 | transform-origin: 0 50%; 38 | -webkit-transition: all 1s ease-in-out; 39 | -o-transition: all 1s ease-in-out; 40 | transition: all 1s ease-in-out; 41 | } 42 | 43 | /* example styling */ 44 | 45 | :root { 46 | --brand: #299996; 47 | --brand-alt: hsla(178, 58%, 38%, 0.06); 48 | --light: #f0f0f0; 49 | --text: #3f454b; 50 | --dark: #252525; 51 | --shadow: hsla(0, 0%, 0%, 0.18); 52 | } 53 | 54 | html, body { 55 | padding: 0; 56 | margin: 0; 57 | font-family: Arial, Helvetica, sans-serif; 58 | font-size: 16pt; 59 | } 60 | 61 | p { 62 | color: var(--text); 63 | line-height: 1.7; 64 | } 65 | 66 | main { 67 | width: 90%; 68 | margin: 0 auto; 69 | margin-top: 60px; 70 | } 71 | 72 | header { 73 | display: block; 74 | width: 100%; 75 | position: fixed; 76 | top: 0; 77 | background-color: var(--dark); 78 | -webkit-box-shadow: 0 0 4px 0 var(--shadow); 79 | box-shadow: 0 0 4px 0 var(--shadow); 80 | z-index: 2; 81 | } 82 | 83 | header h1 { 84 | font-size: 11pt; 85 | text-transform: uppercase; 86 | letter-spacing: 2px; 87 | font-weight: 600; 88 | margin: 0; 89 | padding: 16px 0; 90 | padding-left: 32px; 91 | color: var(--light); 92 | } 93 | 94 | button { 95 | display: inline-block; 96 | width: 125px; 97 | border: none; 98 | font-size: 11pt; 99 | text-transform: uppercase; 100 | letter-spacing: 2px; 101 | font-weight: 600; 102 | padding: 4px 6px; 103 | border-radius: 4px; 104 | background-color: transparent; 105 | cursor: pointer; 106 | } 107 | 108 | button.primary { 109 | background-color: #299996; 110 | color: var(--light); 111 | } 112 | 113 | button.secondary { 114 | background-color: var(--light); 115 | color: var(--dark); 116 | } 117 | 118 | @media screen and (min-width:972px) { 119 | main { 120 | max-width: 912px; 121 | } 122 | .progressbar { 123 | background-color: var(--light) 124 | } 125 | } -------------------------------------------------------------------------------- /renv/.gitignore: -------------------------------------------------------------------------------- 1 | library/ 2 | local/ 3 | cellar/ 4 | lock/ 5 | python/ 6 | sandbox/ 7 | staging/ 8 | -------------------------------------------------------------------------------- /renv/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "bioconductor.version": null, 3 | "external.libraries": [], 4 | "ignored.packages": [], 5 | "package.dependency.fields": [ 6 | "Imports", 7 | "Depends", 8 | "LinkingTo" 9 | ], 10 | "ppm.enabled": null, 11 | "ppm.ignored.urls": [], 12 | "r.version": null, 13 | "snapshot.type": "implicit", 14 | "use.cache": true, 15 | "vcs.ignore.cellar": true, 16 | "vcs.ignore.library": true, 17 | "vcs.ignore.local": true, 18 | "vcs.manage.ignores": true 19 | } 20 | -------------------------------------------------------------------------------- /responsive-datatables/app.R: -------------------------------------------------------------------------------- 1 | #'////////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2019-12-05 5 | #' MODIFIED: 2021-04-18 6 | #' PURPOSE: create responsive datatables in shiny 7 | #' STATUS: working 8 | #' PACKAGES: shiny 9 | #' COMMENTS: 10 | #' The datatable function can be found here: R/datatable.R. The 11 | #' re-orgranizing of content is handled by css (see www/css/*.css for info). 12 | #'////////////////////////////////////////////////////////////////////////////// 13 | 14 | # tests 15 | # testthat::test_file("tests/test-datatable.R") 16 | 17 | # pkgs 18 | suppressPackageStartupMessages(library(shiny)) 19 | suppressPackageStartupMessages(library(dplyr)) 20 | 21 | # load assets 22 | birds <- readRDS("data/birds_summary.RDS") 23 | 24 | # client 25 | ui <- tagList( 26 | tags$head( 27 | tags$link( 28 | type = "text/css", 29 | rel = "stylesheet", 30 | href = "css/styles.css" 31 | ), 32 | tags$title("Responsive Datatables | shinyAppTutorials") 33 | ), 34 | tags$body( 35 | tags$header( 36 | class = "header", 37 | tags$h1("shinyTutorials") 38 | ), 39 | tags$main( 40 | class = "main", 41 | tags$section( 42 | class = "section", 43 | tags$h2("Responsive datatables in shiny"), 44 | tags$p( 45 | "This application demonstrates how to create responsive ", 46 | "and accessible datatables in shiny. The custom function ", 47 | "datatable() renders the required table elements from ", 48 | "your dataset and adds visually hidden and displayed ", 49 | "elements when the screen is resized or if accessed on ", 50 | "mobile. In this example, a datatable is generated using ", 51 | "data from birdData Australia. The data is the top 25 ", 52 | "most commonly reported birds in 2018 Birds in Backyards ", 53 | "program. Resize the browser to see how the table ", 54 | "visually changes and examine the code to see how the ", 55 | "table is rendered." 56 | ), 57 | uiOutput("table") 58 | ) 59 | ) 60 | ) 61 | ) 62 | 63 | # server 64 | server <- function(input, output, session) { 65 | output$table <- renderUI({ 66 | birds2018 <- birds %>% 67 | filter(Year == "2018") %>% 68 | arrange(-Count) %>% 69 | slice(1:25) 70 | datatable(data = birds2018, caption = "Top 25 Birds in 2018") 71 | 72 | }) 73 | } 74 | 75 | # app 76 | shinyApp(ui, server) 77 | -------------------------------------------------------------------------------- /responsive-datatables/data/birds_summary.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/responsive-datatables/data/birds_summary.RDS -------------------------------------------------------------------------------- /responsive-datatables/data/birds_summary.csv: -------------------------------------------------------------------------------- 1 | "Common.Name","Year","Count" 2 | "Australasian Figbird","2017",133 3 | "Australian Magpie","2017",354 4 | "Australian Raven","2017",74 5 | "Australian White Ibis","2017",63 6 | "Bar-shouldered Dove","2017",144 7 | "Black-faced Cuckoo-shrike","2017",103 8 | "Blue-faced Honeyeater","2017",113 9 | "Brown Honeyeater","2017",379 10 | "Common Blackbird","2017",153 11 | "Common Myna","2017",95 12 | "Crested Pigeon","2017",162 13 | "Crimson Rosella","2017",78 14 | "Double-barred Finch","2017",131 15 | "Eastern Koel","2017",79 16 | "Forest Kingfisher","2017",96 17 | "Galah","2017",112 18 | "Great Bowerbird","2017",121 19 | "Grey Butcherbird","2017",127 20 | "Grey Fantail","2017",93 21 | "House Sparrow","2017",80 22 | "Laughing Kookaburra","2017",169 23 | "Lemon-bellied Flycatcher","2017",80 24 | "Little Friarbird","2017",115 25 | "Little Raven","2017",93 26 | "Magpie-lark","2017",385 27 | "Mistletoebird","2017",129 28 | "New Holland Honeyeater","2017",124 29 | "Noisy Friarbird","2017",69 30 | "Noisy Miner","2017",142 31 | "Olive-backed Oriole","2017",110 32 | "Olive-backed Sunbird","2017",132 33 | "Pale-headed Rosella","2017",129 34 | "Peaceful Dove","2017",302 35 | "Pied Butcherbird","2017",68 36 | "Pied Currawong","2017",173 37 | "Rainbow Bee-eater","2017",68 38 | "Rainbow Lorikeet","2017",320 39 | "Red Wattlebird","2017",270 40 | "Rufous Whistler","2017",135 41 | "Scaly-breasted Lorikeet","2017",70 42 | "Spangled Drongo","2017",74 43 | "Spotted Dove","2017",145 44 | "Striated Pardalote","2017",224 45 | "Sulphur-crested Cockatoo","2017",81 46 | "Superb Fairy-wren","2017",65 47 | "Welcome Swallow","2017",70 48 | "White-bellied Cuckoo-shrike","2017",112 49 | "White-throated Honeyeater","2017",231 50 | "Willie Wagtail","2017",351 51 | "Yellow Honeyeater","2017",247 52 | "Australasian Figbird","2018",132 53 | "Australian Magpie","2018",806 54 | "Australian Raven","2018",203 55 | "Australian White Ibis","2018",107 56 | "Bar-shouldered Dove","2018",116 57 | "Black-faced Cuckoo-shrike","2018",123 58 | "Blue-faced Honeyeater","2018",129 59 | "Brown Honeyeater","2018",240 60 | "Common Blackbird","2018",315 61 | "Common Myna","2018",168 62 | "Crested Pigeon","2018",352 63 | "Crimson Rosella","2018",225 64 | "Double-barred Finch","2018",128 65 | "Eastern Koel","2018",71 66 | "Forest Kingfisher","2018",53 67 | "Galah","2018",282 68 | "Great Bowerbird","2018",78 69 | "Grey Butcherbird","2018",263 70 | "Grey Fantail","2018",179 71 | "House Sparrow","2018",193 72 | "Laughing Kookaburra","2018",266 73 | "Lemon-bellied Flycatcher","2018",51 74 | "Little Friarbird","2018",123 75 | "Little Raven","2018",62 76 | "Magpie-lark","2018",553 77 | "Mistletoebird","2018",88 78 | "New Holland Honeyeater","2018",191 79 | "Noisy Friarbird","2018",141 80 | "Noisy Miner","2018",538 81 | "Olive-backed Oriole","2018",59 82 | "Olive-backed Sunbird","2018",67 83 | "Pale-headed Rosella","2018",153 84 | "Peaceful Dove","2018",198 85 | "Pied Butcherbird","2018",128 86 | "Pied Currawong","2018",271 87 | "Rainbow Bee-eater","2018",69 88 | "Rainbow Lorikeet","2018",650 89 | "Red Wattlebird","2018",413 90 | "Rufous Whistler","2018",99 91 | "Scaly-breasted Lorikeet","2018",73 92 | "Spangled Drongo","2018",80 93 | "Spotted Dove","2018",226 94 | "Striated Pardalote","2018",201 95 | "Sulphur-crested Cockatoo","2018",324 96 | "Superb Fairy-wren","2018",140 97 | "Welcome Swallow","2018",248 98 | "White-bellied Cuckoo-shrike","2018",74 99 | "White-throated Honeyeater","2018",131 100 | "Willie Wagtail","2018",409 101 | "Yellow Honeyeater","2018",118 102 | -------------------------------------------------------------------------------- /responsive-datatables/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "responsive-datatables", 3 | "version": "1.1.0", 4 | "description": "create responsive datatables in R", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": ["r", "shiny", "tutorials"], 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 15 | }, 16 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 17 | } 18 | -------------------------------------------------------------------------------- /responsive-datatables/responsive-datatables.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /responsive-datatables/tests/test-datatable.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: test-datatable.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2021-04-18 5 | #' MODIFIED: 2021-04-18 6 | #' PURPOSE: unittests 7 | #' STATUS: working 8 | #' PACKAGES: shiny, testthat 9 | #' COMMENTS: usings R/datatable.R 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | # pkgs 13 | suppressPackageStartupMessages(library(testthat)) 14 | source("../R/datatable.R") 15 | 16 | dat <- readRDS("../data/birds_summary.RDS")[1:5, ] 17 | 18 | # `datatable` returns standard elements 19 | test_that("required elements are generated", { 20 | d <- datatable(data = dat, caption = "Birds!!") 21 | expect_equal( 22 | object = c( 23 | d$children[[1]]$name, 24 | d$children[[2]][[1]]$name, 25 | d$children[[2]][[2]]$name 26 | ), 27 | expected = c("caption", "thead", "tbody"), 28 | label = "Function does not return basic elements" 29 | ) 30 | }) 31 | 32 | 33 | # `id` is properly assigned 34 | test_that("ID attribute is properly updated", { 35 | d <- datatable(data = dat, id = "table") 36 | expect_equal( 37 | object = d$attribs$id, 38 | expected = "table", 39 | label = "ID is not added in the correct place" 40 | ) 41 | }) 42 | 43 | 44 | # option `responsive` does not render span when `FALSE` 45 | test_that("When FALSE, responsive markup is not generated", { 46 | d <- datatable(data = dat, options = list(responsive = FALSE)) 47 | row <- d$children[[2]]$children[[1]][[1]] 48 | expect_equal( 49 | object = length(row$children[[1]][[1]]$children), 50 | expected = 1, 51 | label = "Responsive markup is not properly removed when FALSE" 52 | ) 53 | }) 54 | 55 | # option `rowHeaders` does not render header markdup when `FALSE` 56 | test_that("When FALSE, row headers are not generated", { 57 | d <- datatable(data = dat, options = list(rowHeaders = FALSE)) 58 | row <- d$children[[2]]$children[[1]][[1]] 59 | expect_equal( 60 | object = row$children[[1]][[1]]$name, 61 | expected = "td", 62 | label = "Responsive markup is not properly removed when FALSE" 63 | ) 64 | }) 65 | -------------------------------------------------------------------------------- /responsive-datatables/www/css/datatable.css: -------------------------------------------------------------------------------- 1 | .datatable { 2 | width: 100%; 3 | border-spacing: 0; 4 | text-align: left; 5 | font-size: 13pt; 6 | } 7 | 8 | .datatable caption { 9 | text-align: left; 10 | font-size: 16pt; 11 | margin: 12px 0; 12 | color: var(--dark); 13 | font-weight: 600; 14 | } 15 | 16 | .datatable thead tr th { 17 | font-weight: 600; 18 | padding: 4px 12px; 19 | text-transform: uppercase; 20 | letter-spacing: 2px; 21 | border-bottom: 1px solid var(--dark); 22 | color: var(--dark); 23 | } 24 | 25 | .datatable tbody tr th, .datatable tbody tr td { 26 | font-weight: 400; 27 | padding: 24px 12px; 28 | } 29 | 30 | .datatable tbody tr:nth-child(even) { 31 | background-color: var(--light); 32 | } 33 | 34 | .datatable .hidden-colname { 35 | display: inline-block; 36 | clip: rect(1px 1px 1px 1px); 37 | clip: rect(1px, 1px, 1px, 1px); 38 | width: 1px; 39 | height: 1px; 40 | overflow: hidden; 41 | } 42 | 43 | @media (max-width: 892px) { 44 | .datatable thead { 45 | position: absolute; 46 | clip: rect(1px 1px 1px 1px); 47 | clip: rect(1px, 1px, 1px, 1px); 48 | width: 1px; 49 | height: 1px; 50 | overflow: hidden; 51 | } 52 | .datatable tbody tr th, .datatable tbody tr td { 53 | display: block; 54 | padding: 5px 0 5px 12px; 55 | } 56 | .datatable .hidden-colname { 57 | display: inline-block; 58 | clip: auto; 59 | width: 150px; 60 | height: auto; 61 | line-height: 1; 62 | } 63 | } -------------------------------------------------------------------------------- /responsive-datatables/www/css/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light: #f6f6f6; 3 | --semi-light: #D9E3F2; 4 | --text: #3f454b; 5 | --dark: #252525; 6 | --shadow: hsla(0, 0%, 0%, 0.18); 7 | } 8 | 9 | html, body { 10 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 11 | font-size: 16pt; 12 | padding: 0; 13 | margin: 0; 14 | } 15 | 16 | header, main, section, form, footer { 17 | padding: 0; 18 | margin: 0; 19 | } 20 | 21 | h1, h2, h3 { 22 | color: var(--dark); 23 | padding: 0; 24 | margin: 0; 25 | margin-bottom: 12px; 26 | line-height: 1.4; 27 | } 28 | 29 | p { 30 | color: var(--text); 31 | padding: 0; 32 | margin: 12px 0; 33 | line-height: 1.6; 34 | } 35 | 36 | .header { 37 | display: block; 38 | width: 100%; 39 | padding: 12px 0; 40 | background-color: var(--dark); 41 | } 42 | 43 | .header h1 { 44 | font-size: 14pt; 45 | color: var(--light); 46 | padding-left: 32px; 47 | margin: 0; 48 | } 49 | 50 | .section { 51 | width: 90%; 52 | padding: 3em 0; 53 | margin: 0 auto; 54 | } 55 | 56 | @media (min-width: 912px) { 57 | .section { 58 | max-width: 912px; 59 | margin: 0 auto; 60 | } 61 | } 62 | 63 | /* datatable styles */ 64 | 65 | .datatable { 66 | width: 100%; 67 | border-spacing: 0; 68 | text-align: left; 69 | font-size: 13pt; 70 | } 71 | 72 | .datatable caption { 73 | text-align: left; 74 | font-size: 16pt; 75 | margin: 12px 0; 76 | color: var(--dark); 77 | font-weight: 600; 78 | } 79 | 80 | .datatable thead tr th { 81 | font-weight: 600; 82 | padding: 4px 12px; 83 | text-transform: uppercase; 84 | letter-spacing: 2px; 85 | border-bottom: 1px solid var(--dark); 86 | color: var(--dark); 87 | } 88 | 89 | .datatable tbody tr th, .datatable tbody tr td { 90 | font-weight: 400; 91 | padding: 24px 12px; 92 | } 93 | 94 | .datatable tbody tr:nth-child(even) { 95 | background-color: var(--light); 96 | } 97 | 98 | .datatable .hidden-colname { 99 | display: inline-block; 100 | clip: rect(1px 1px 1px 1px); 101 | clip: rect(1px, 1px, 1px, 1px); 102 | width: 1px; 103 | height: 1px; 104 | overflow: hidden; 105 | } 106 | 107 | @media (max-width: 892px) { 108 | .datatable thead { 109 | position: absolute; 110 | clip: rect(1px 1px 1px 1px); 111 | clip: rect(1px, 1px, 1px, 1px); 112 | width: 1px; 113 | height: 1px; 114 | overflow: hidden; 115 | } 116 | .datatable tbody tr th, .datatable tbody tr td { 117 | display: block; 118 | padding: 5px 0 5px 12px; 119 | } 120 | .datatable .hidden-colname { 121 | display: inline-block; 122 | clip: auto; 123 | width: 150px; 124 | height: auto; 125 | line-height: 1; 126 | } 127 | } -------------------------------------------------------------------------------- /rmarkdown-app/.gitignore: -------------------------------------------------------------------------------- 1 | report_template.html -------------------------------------------------------------------------------- /rmarkdown-app/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Frmarkdown-app%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Frmarkdown-app%2Fpackage.json) 4 | 5 | 6 | # Using Rmarkdown in Shiny 7 | 8 | This shiny app was created in response to this [post](https://community.rstudio.com/t/generating-markdown-reports-from-shiny/8676). In this example, the app allows users to make a selection, render a report (Rmarkdown file) based on the selection, and then display the rendered document in shiny. The Tutorial is now available on my shiny tutorials site: [Building your UI with Rmarkdown Reports](https://davidruvolo51.github.io/shinytutorials/tutorials/rmarkdown-shiny/) 9 | 10 | ## Getting Started 11 | 12 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 13 | 14 | ### Running in your R environment 15 | 16 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 17 | 18 | ```r 19 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "rmarkdown-app") 20 | ``` 21 | 22 | ### Cloning the subdirectory 23 | 24 | You can clone the data editor subdirectory using `git sparse-checkout`. 25 | 26 | ```bash 27 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 28 | cd shinyAppTutorials 29 | git sparse-checkout init --cone 30 | git sparse-checkout set rmarkdown-app 31 | ``` 32 | 33 | Then you can run the shiny app in your preferred R environment. 34 | -------------------------------------------------------------------------------- /rmarkdown-app/app.R: -------------------------------------------------------------------------------- 1 | #'////////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2018-05-22 5 | #' MODIFIED: 2021-04-30 6 | #' PURPOSE: how to include Rmarkdown files in shiny 7 | #' PACKAGES: shiny, tidyverse, kableExtra 8 | #' COMMENTS: See `data/data_0_source.R` for data sourcing and procesing steps 9 | #'////////////////////////////////////////////////////////////////////////////// 10 | 11 | # install 12 | # install.packages("dplyr") 13 | # install.packages("shiny") 14 | # install.packages("kableExtra") 15 | # install.packages("ggplot2") 16 | 17 | # imports 18 | suppressPackageStartupMessages(library(shiny)) 19 | librarians <- readRDS("data/librarians_538.RDS") 20 | 21 | 22 | ui <- tagList( 23 | tags$head( 24 | tags$link(rel = "stylesheet", href = "styles.css"), 25 | tags$title("Rmarkdown + Shiny | shinyAppTutorials") 26 | ), 27 | tags$main( 28 | tags$h1("Rmarkdown and Shiny"), 29 | tags$p( 30 | "An example app demonstrating how to use paramaterized ", 31 | "reports in Rmarkdown" 32 | ), 33 | tags$h2("Get started"), 34 | tags$p( 35 | "This example demonstrates the use of paramaterized ", 36 | "reports in shiny. The data used in this example comes ", 37 | "is available on 538's GitHub data repository. See the", 38 | tags$a( 39 | # nolint start 40 | href = "https://github.com/fivethirtyeight/data/tree/master/librarians", 41 | # nolint end 42 | "librarians dataset" 43 | ), 44 | "for more information." 45 | ), 46 | tags$form( 47 | tags$legend("Define Parameters for the Report"), 48 | tags$label(`for` = "state", "Select a State"), 49 | tags$select( 50 | id = "state", 51 | HTML( 52 | c("Select a State", 53 | sapply( 54 | sort(unique(librarians$prim_state)), 55 | function(x) { 56 | paste0( 57 | "" 58 | ) 59 | } 60 | ) 61 | ) 62 | ) 63 | ), 64 | tags$button( 65 | id = "render", 66 | class = "action-button shiny-bound-input", 67 | "Render" 68 | ) 69 | ), 70 | tags$article( 71 | `aria-label` = "report", 72 | htmlOutput("report") 73 | ) 74 | ) 75 | ) 76 | 77 | 78 | server <- function(input, output) { 79 | observeEvent(input$render, { 80 | librarians_subset <- librarians[librarians$prim_state == input$state, ] 81 | output$report <- renderUI({ 82 | includeHTML( 83 | rmarkdown::render( 84 | "report_template.Rmd", 85 | params = list( 86 | selection = input$state, 87 | data = librarians_subset 88 | ) 89 | ) 90 | ) 91 | }) 92 | }) 93 | } 94 | 95 | 96 | shinyApp(ui, server) -------------------------------------------------------------------------------- /rmarkdown-app/data/data_0_source.R: -------------------------------------------------------------------------------- 1 | #'////////////////////////////////////////////////////////////////////////////// 2 | #' FILE: source_data.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2018-05-22 5 | #' MODIFIED: 2020-11-20 6 | #' PURPOSE: source 538's librarians dataset 7 | #' PACKAGES: tidyverse 8 | #' STATUS: working + complete 9 | #' COMMENTS: uses librarian dataset from 538's github repo 10 | #'////////////////////////////////////////////////////////////////////////////// 11 | 12 | # source data 13 | raw <- read.csv( 14 | file = "https://raw.githubusercontent.com/fivethirtyeight/data/master/librarians/librarians-by-msa.csv", 15 | stringsAsFactors = FALSE 16 | ) 17 | 18 | # quick clean 19 | raw <- raw %>% replace(. == "**", NA) 20 | raw[, 3:6] <- as.numeric(sapply(raw[, 3:6], function(x)as.numeric(x))) 21 | 22 | # save 23 | saveRDS(raw, "data/librarians_538.RDS") -------------------------------------------------------------------------------- /rmarkdown-app/data/librarians_538.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/rmarkdown-app/data/librarians_538.RDS -------------------------------------------------------------------------------- /rmarkdown-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rmarkdown-app", 3 | "version": "1.1.0", 4 | "description": "using Rmarkdown as Shiny UI", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": ["r", "shiny", "tutorials"], 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 15 | }, 16 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 17 | } 18 | -------------------------------------------------------------------------------- /rmarkdown-app/report_template.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: 3 | html_document: 4 | theme: null 5 | mathjax: null 6 | params: 7 | selection: null 8 | data: null 9 | --- 10 | 11 | ```{r setup, include=FALSE} 12 | knitr::opts_chunk$set(echo = FALSE, eval=TRUE, message=FALSE, warning=FALSE) 13 | ``` 14 | 15 | # Here is your the report 16 | 17 | You selected `r params$selection`. 18 | 19 | This is an example of a parameterized report in shiny. The markdown template is stored in the same location as the `app.R` file. There isn't much to the markdown template except for the YAML setup. Here's how the data is passed from shiny into the markdown file. 20 | 21 | The YAML looks like this. 22 | 23 | ```{r echo=TRUE, eval=FALSE} 24 | --- 25 | output: html_document 26 | params: 27 | selection: null 28 | data: null 29 | --- 30 | ``` 31 | 32 | I set each param to null in this example. If you want the document to render when the app is started, then set the params accordingly. 33 | 34 | To access the params in the markdown file, use `params$some_param_name`. In this example, we will use `param$selection` and `param$data`. Define other parameters as needed. 35 | 36 | In our shiny server code, we will use the function to `includeMarkdown()` and `render()` from the `rmarkdown` package to render out markdown template. We will also pass objects to our markdown template through the `render` function. Here's what this function will look. 37 | 38 | ```{r echo=TRUE, eval=FALSE} 39 | includeMarkdown( 40 | rmarkdown::render("report_template.Rmd", params = list(selection = input$state, data = librariansDF())) 41 | ) 42 | ``` 43 | 44 | `librariansDF` is an object that is created when the render button is clicked and we can define the selection by calling `input$state`. 45 | 46 | ```{r} 47 | # pkgs 48 | suppressPackageStartupMessages(library(dplyr)) 49 | suppressPackageStartupMessages(library(ggplot2)) 50 | suppressPackageStartupMessages(library(kableExtra)) 51 | 52 | 53 | # find top areas by selected state 54 | top_ten <- params$data %>% 55 | group_by(area_name) %>% 56 | summarize("tot_emp" = sum(tot_emp)) %>% 57 | top_n(10) %>% 58 | arrange(tot_emp) %>% 59 | mutate(area_name = factor(area_name, area_name)) 60 | 61 | ``` 62 | 63 | ## Example Plot 64 | 65 | Now that we have our params defined and passed data into them. Let's use them. 66 | 67 | In this example, we will find the top 10 states that have the most employed librarians. We will create a new object `top_ten` and use that to make a chart and a display a table. Here's the r code that defines our `top_ten` object. We will use `params$data` to use our data object passed through by our render function. 68 | 69 | ```{r echo=TRUE, eval=FALSE} 70 | # find top areas by selected state 71 | top_ten <- params$data %>% 72 | group_by(area_name) %>% 73 | summarize("tot_emp" = sum(tot_emp)) %>% 74 | top_n(10) %>% 75 | arrange(tot_emp) %>% 76 | mutate(area_name = factor(area_name, area_name)) 77 | ``` 78 | 79 | Here's the chart. 80 | 81 | ```{r} 82 | # plot top 10 areas by selected state 83 | ggplot(data = top_ten, aes(x = area_name, y= tot_emp)) + 84 | geom_col() + 85 | coord_flip() + 86 | ggtitle("Which states have the most employed librarians?") + 87 | labs(x = NULL, y= "Total Employed") + 88 | theme_minimal() 89 | 90 | ``` 91 | 92 | Here's the table. 93 | 94 | ```{r,fig.fig.align='center'} 95 | kableExtra::kable(top_ten) %>% kable_styling() 96 | ``` 97 | 98 | For more information on parameterized reports, check out the [documentation](https://rmarkdown.rstudio.com/developer_parameterized_reports.html%23parameter_types%2F). 99 | -------------------------------------------------------------------------------- /rmarkdown-app/rmarkdown-app.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: XeLaTeX 14 | Version: 1.0 15 | 16 | RestoreWorkspace: Default 17 | SaveWorkspace: Default 18 | AlwaysSaveHistory: Default 19 | 20 | EnableCodeIndexing: Yes 21 | UseSpacesForTab: Yes 22 | NumSpacesForTab: 2 23 | Encoding: UTF-8 24 | RnwWeave: Sweave 25 | LaTeX: pdfLaTeX 26 | -------------------------------------------------------------------------------- /rmarkdown-app/www/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | font-size: 16pt; 4 | } 5 | 6 | main { 7 | margin: 0 auto; 8 | width: 90%; 9 | } 10 | 11 | main { 12 | margin: 0 auto; 13 | padding-bottom: 44px; 14 | } 15 | 16 | h1, h2, h3 { 17 | color: #252525; 18 | line-height: 1.4; 19 | margin: 0; 20 | padding: 0; 21 | margin-bottom: 12px; 22 | } 23 | 24 | p { 25 | color: #3f454b; 26 | line-height: 1.7; 27 | font-size: 16pt; 28 | } 29 | 30 | button { 31 | width: 125px; 32 | padding: 4px 0; 33 | border-radius: 4px; 34 | margin: 0; 35 | border: none; 36 | background-color: #3f454b; 37 | color: #f3f3f3; 38 | font-size: 11pt; 39 | text-transform: uppercase; 40 | letter-spacing: 2px; 41 | font-weight: 600; 42 | cursor: pointer; 43 | } 44 | 45 | button:hover { 46 | text-decoration: underline; 47 | } 48 | 49 | select { 50 | padding: 6px 0; 51 | padding-left: 6px; 52 | width: 250px; 53 | font-weight: 600; 54 | background-color: white; 55 | background-size: 16px; 56 | } 57 | 58 | legend { 59 | font-size: 16pt; 60 | margin-bottom: 12px; 61 | color: #252525; 62 | } 63 | 64 | label { 65 | font-size: 14pt; 66 | color: #252525; 67 | } 68 | 69 | form { 70 | background-color: #f3f3f3; 71 | padding: 16px; 72 | margin: 12px 0; 73 | } 74 | 75 | form label { 76 | display: block; 77 | margin-bottom: 10px; 78 | } 79 | 80 | form button, form select { 81 | display: inline-block; 82 | } 83 | 84 | form select { 85 | margin-right: 12px; 86 | } 87 | 88 | #report { 89 | margin: 0; 90 | padding: 0; 91 | font-size: 16pt; 92 | } 93 | 94 | #report pre { 95 | overflow-x: scroll; 96 | padding: 1em; 97 | background-color: #f6f6f6; 98 | } 99 | 100 | #report pre code { 101 | font-size: 14pt; 102 | } 103 | 104 | .shiny-html-output .container-fluid, 105 | .shiny-html-output .main-container { 106 | padding: 0; 107 | margin: 0; 108 | } 109 | 110 | @media screen and (min-width: 972px) { 111 | main { 112 | width: 100%; 113 | max-width: 912px; 114 | } 115 | } -------------------------------------------------------------------------------- /sass-in-shiny/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fsass-in-shiny%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fsass-in-shiny%2Fpackage.json) 4 | 5 | 6 | ## Using SASS in Shiny 7 | 8 | In this example, learn how to integrate [SASS](https://sass-lang.com) into your Shiny development workflow. To get this example working, you will need to install [node.js](https://nodejs.org/en/) and SASS on your machine. See the [Install SASS guide](https://sass-lang.com/install) for additional instructions. There are two build scripts: one for development and one for production. When you have node installed, you can run these scripts in the command line. 9 | 10 | ```shell 11 | npm run start # for development 12 | npm run build # for production 13 | ``` 14 | 15 | Future applications will demonstrate how to integrate application bundlers and additional plugins, but we will keep things simple for now. For more information, checkout out the accompanying blog post [Shiny and CSS preprocessors: Getting started with SASS in Shiny](https://davidruvolo51.github.io/shinytutorials/tutorials/sass-in-shiny/). 16 | 17 | ## Getting Started 18 | 19 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 20 | 21 | ### Running in your R environment 22 | 23 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 24 | 25 | ```r 26 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "sass-in-shiny") 27 | ``` 28 | 29 | ### Cloning the subdirectory 30 | 31 | You can clone the data editor subdirectory using `git sparse-checkout`. 32 | 33 | ```bash 34 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 35 | cd shinyAppTutorials 36 | git sparse-checkout init --cone 37 | git sparse-checkout set sass-in-shiny 38 | ``` 39 | 40 | Then you can run the shiny app in your preferred R environment. 41 | -------------------------------------------------------------------------------- /sass-in-shiny/app.R: -------------------------------------------------------------------------------- 1 | #' //////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-01-15 5 | #' MODIFIED: 2021-05-01 6 | #' PURPOSE: example shiny app using sass 7 | #' STATUS: working 8 | #' PACKAGES: shiny, dplyr, stringr 9 | #' COMMENTS: NA 10 | #' //////////////////////////////////////////////////////////////////////////// 11 | 12 | # pkgs 13 | suppressPackageStartupMessages(library(shiny)) 14 | suppressPackageStartupMessages(library(dplyr)) 15 | birds <- readRDS("data/birds_summary.RDS") 16 | 17 | 18 | ui <- tagList( 19 | tags$head( 20 | tags$link( 21 | type = "text/css", 22 | rel = "stylesheet", 23 | href = "styles.css" 24 | ), 25 | tags$title("SASS in Shiny | shinyAppTutorials") 26 | ), 27 | tags$header( 28 | class = "header", 29 | tags$h1("shinyTutorials") 30 | ), 31 | tags$main( 32 | class = "main", 33 | tags$section( 34 | class = "section", 35 | tags$h2("Using SASS in shiny"), 36 | tags$p( 37 | "This shiny app recreates the earlier tutorial", 38 | tags$a( 39 | href = "https://github.com/davidruvolo51/shinyAppTutorials", 40 | "Responsive Datatables in Shiny" 41 | ), 42 | "using SASS. The UI is the same (minus the content).", 43 | "Take a look at the other application and then this one", 44 | "to compare css and sass outputs. The benefits of writing", 45 | "applications with sass is for css specificty, ease of", 46 | "writing, and optimizing css for all browsers. This app", 47 | "uses the following npm packages: sass and post-css.", 48 | "Visit the tutorial for more information." 49 | ), 50 | uiOutput("table") 51 | ) 52 | ) 53 | ) 54 | 55 | 56 | # server 57 | server <- function(input, output) { 58 | 59 | # summarize data and build table 60 | birds2018 <- birds %>% 61 | filter(Year == "2018") %>% 62 | arrange(-Count) %>% 63 | slice(1:10) %>% 64 | mutate( 65 | link = paste0( 66 | "", 74 | "View on birdlife", 75 | "" 76 | ) 77 | ) 78 | 79 | # output table 80 | output$table <- renderUI({ 81 | datatable( 82 | data = birds2018, 83 | id = "birds-summary", 84 | caption = "Top 10 Birds in 2018", 85 | options = list( 86 | asHTML = TRUE 87 | ) 88 | ) 89 | }) 90 | } 91 | 92 | # app 93 | shinyApp(ui, server) 94 | -------------------------------------------------------------------------------- /sass-in-shiny/data/birds_summary.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/sass-in-shiny/data/birds_summary.RDS -------------------------------------------------------------------------------- /sass-in-shiny/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sass-in-shiny", 3 | "version": "1.1.0", 4 | "description": "integrating SASS into your Shiny development workflow", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "scripts": { 8 | "start": "sass --watch src/index.scss:www/styles.css --style expanded --no-source-map", 9 | "build": "sass src/index.scss:www/styles.css --style expanded --no-source-map", 10 | "shiny": "Rscript -e 'shiny::runApp(launch.browser=FALSE, port=1234)'" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 15 | }, 16 | "keywords": ["r", "shiny", "tutorials"], 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 20 | }, 21 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 22 | } 23 | -------------------------------------------------------------------------------- /sass-in-shiny/sass-in-shiny.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | RnwWeave: Sweave 12 | LaTeX: pdfLaTeX 13 | -------------------------------------------------------------------------------- /sass-in-shiny/src/_base.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | padding: 0; 4 | margin: 0; 5 | font-size: 16pt; 6 | } 7 | 8 | header, main, section { 9 | padding: 0; 10 | margin: 0; 11 | } 12 | 13 | h1, h2, caption { 14 | color: $dark; 15 | line-height: 1.4; 16 | margin: 0; 17 | } 18 | 19 | p { 20 | color: $medium; 21 | line-height: 1.7; 22 | margin: 0; 23 | margin: 12px 0; 24 | } 25 | 26 | a { 27 | color: $blue; 28 | text-decoration: none; 29 | box-shadow: inset 0 -2px 0 0 $blue; 30 | } 31 | 32 | main { 33 | padding: 2em 0; 34 | 35 | section { 36 | width: 90%; 37 | padding: 3em 0; 38 | margin: 0 auto; 39 | 40 | h1 { 41 | font-size: 32pt; 42 | } 43 | 44 | h2 { 45 | font-size: 21pt; 46 | } 47 | } 48 | } 49 | 50 | @media (min-width: 912px) { 51 | main { 52 | section { 53 | max-width: 912px; 54 | margin: 0 auto; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /sass-in-shiny/src/_datatable.scss: -------------------------------------------------------------------------------- 1 | .datatable { 2 | width: 100%; 3 | border-spacing: 0; 4 | text-align: left; 5 | font-size: 13pt; 6 | 7 | caption { 8 | text-align: left; 9 | font-size: 16pt; 10 | margin: 12px 0; 11 | color: $dark; 12 | font-weight: 600; 13 | } 14 | 15 | thead { 16 | tr { 17 | th { 18 | font-weight: 600; 19 | padding: 4px 12px; 20 | text-transform: uppercase; 21 | letter-spacing: 2px; 22 | border-bottom: 1px solid $dark; 23 | color: $dark; 24 | } 25 | } 26 | } 27 | 28 | tbody { 29 | tr { 30 | th, 31 | td { 32 | font-weight: 400; 33 | padding: 24px 12px; 34 | 35 | .hidden-colname { 36 | display: inline-block; 37 | clip: rect(1px 1px 1px 1px); 38 | clip: rect(1px, 1px, 1px, 1px); 39 | width: 1px; 40 | height: 1px; 41 | overflow: hidden; 42 | } 43 | } 44 | 45 | &:nth-child(even) { 46 | background-color: $light; 47 | } 48 | } 49 | } 50 | } 51 | 52 | @media (max-width: 892px) { 53 | .datatable { 54 | thead { 55 | position: absolute; 56 | clip: rect(1px 1px 1px 1px); 57 | clip: rect(1px, 1px, 1px, 1px); 58 | width: 1px; 59 | height: 1px; 60 | overflow: hidden; 61 | } 62 | tbody { 63 | tr { 64 | th, 65 | td { 66 | display: block; 67 | padding: 5px 0 5px 12px; 68 | 69 | .hidden-colname { 70 | display: inline-block; 71 | clip: auto; 72 | width: 150px; 73 | height: auto; 74 | line-height: 1; 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /sass-in-shiny/src/_header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | background-color: $dark; 7 | box-shadow: $boxshadow; 8 | 9 | h1 { 10 | padding: 12px 0; 11 | font-size: 14pt; 12 | text-transform: uppercase; 13 | font-weight: bold; 14 | letter-spacing: 2px; 15 | text-align: center; 16 | color: $light; 17 | } 18 | } -------------------------------------------------------------------------------- /sass-in-shiny/src/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | //COLORS 3 | $light: #f6f6f6; 4 | $medium: #3f454b; 5 | $dark: #2b2b2b; 6 | $blue: #5177b8; 7 | $shadow: hsla(0, 0%, 0%, 0.18); 8 | //END COLORS 9 | 10 | $boxshadow: 0 0 6px 4px $shadow; -------------------------------------------------------------------------------- /sass-in-shiny/src/index.scss: -------------------------------------------------------------------------------- 1 | 2 | // import scss files -- load _variables first 3 | @import "_variables"; 4 | @import "_base","_header", "_datatable"; 5 | 6 | // all base and default styles are separated into 7 | // smaller files. Use the rest of this file to 8 | // modify specific elements. 9 | 10 | // align table columns 11 | #birds-summary { 12 | 13 | thead { 14 | tr { 15 | th { 16 | &:nth-child(2), 17 | &:nth-child(3), 18 | &:nth-child(4) { 19 | text-align: center; 20 | } 21 | } 22 | } 23 | } 24 | 25 | tbody { 26 | tr { 27 | td { 28 | &:nth-child(2), 29 | &:nth-child(4) { 30 | text-align: center; 31 | } 32 | 33 | &:nth-child(3){ 34 | text-align: right; 35 | } 36 | } 37 | 38 | &:hover { 39 | background-color: lighten($color: $blue, $amount: 18%); 40 | color: $dark; 41 | 42 | td { 43 | &:last-of-type { 44 | a { 45 | color: $dark; 46 | box-shadow: inset 0 -2px 0 0 $dark; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /sass-in-shiny/www/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | padding: 0; 4 | margin: 0; 5 | font-size: 16pt; 6 | } 7 | 8 | header, main, section { 9 | padding: 0; 10 | margin: 0; 11 | } 12 | 13 | h1, h2, caption { 14 | color: #2b2b2b; 15 | line-height: 1.4; 16 | margin: 0; 17 | } 18 | 19 | p { 20 | color: #3f454b; 21 | line-height: 1.7; 22 | margin: 0; 23 | margin: 12px 0; 24 | } 25 | 26 | a { 27 | color: #5177b8; 28 | text-decoration: none; 29 | box-shadow: inset 0 -2px 0 0 #5177b8; 30 | } 31 | 32 | main { 33 | padding: 2em 0; 34 | } 35 | main section { 36 | width: 90%; 37 | padding: 3em 0; 38 | margin: 0 auto; 39 | } 40 | main section h1 { 41 | font-size: 32pt; 42 | } 43 | main section h2 { 44 | font-size: 21pt; 45 | } 46 | 47 | @media (min-width: 912px) { 48 | main section { 49 | max-width: 912px; 50 | margin: 0 auto; 51 | } 52 | } 53 | .header { 54 | position: fixed; 55 | top: 0; 56 | left: 0; 57 | width: 100%; 58 | background-color: #2b2b2b; 59 | box-shadow: 0 0 6px 4px rgba(0, 0, 0, 0.18); 60 | } 61 | .header h1 { 62 | padding: 12px 0; 63 | font-size: 14pt; 64 | text-transform: uppercase; 65 | font-weight: bold; 66 | letter-spacing: 2px; 67 | text-align: center; 68 | color: #f6f6f6; 69 | } 70 | 71 | .datatable { 72 | width: 100%; 73 | border-spacing: 0; 74 | text-align: left; 75 | font-size: 13pt; 76 | } 77 | .datatable caption { 78 | text-align: left; 79 | font-size: 16pt; 80 | margin: 12px 0; 81 | color: #2b2b2b; 82 | font-weight: 600; 83 | } 84 | .datatable thead tr th { 85 | font-weight: 600; 86 | padding: 4px 12px; 87 | text-transform: uppercase; 88 | letter-spacing: 2px; 89 | border-bottom: 1px solid #2b2b2b; 90 | color: #2b2b2b; 91 | } 92 | .datatable tbody tr th, 93 | .datatable tbody tr td { 94 | font-weight: 400; 95 | padding: 24px 12px; 96 | } 97 | .datatable tbody tr th .hidden-colname, 98 | .datatable tbody tr td .hidden-colname { 99 | display: inline-block; 100 | clip: rect(1px 1px 1px 1px); 101 | clip: rect(1px, 1px, 1px, 1px); 102 | width: 1px; 103 | height: 1px; 104 | overflow: hidden; 105 | } 106 | .datatable tbody tr:nth-child(even) { 107 | background-color: #f6f6f6; 108 | } 109 | 110 | @media (max-width: 892px) { 111 | .datatable thead { 112 | position: absolute; 113 | clip: rect(1px 1px 1px 1px); 114 | clip: rect(1px, 1px, 1px, 1px); 115 | width: 1px; 116 | height: 1px; 117 | overflow: hidden; 118 | } 119 | .datatable tbody tr th, 120 | .datatable tbody tr td { 121 | display: block; 122 | padding: 5px 0 5px 12px; 123 | } 124 | .datatable tbody tr th .hidden-colname, 125 | .datatable tbody tr td .hidden-colname { 126 | display: inline-block; 127 | clip: auto; 128 | width: 150px; 129 | height: auto; 130 | line-height: 1; 131 | } 132 | } 133 | #birds-summary thead tr th:nth-child(2), #birds-summary thead tr th:nth-child(3), #birds-summary thead tr th:nth-child(4) { 134 | text-align: center; 135 | } 136 | #birds-summary tbody tr td:nth-child(2), #birds-summary tbody tr td:nth-child(4) { 137 | text-align: center; 138 | } 139 | #birds-summary tbody tr td:nth-child(3) { 140 | text-align: right; 141 | } 142 | #birds-summary tbody tr:hover { 143 | background-color: #92aad3; 144 | color: #2b2b2b; 145 | } 146 | #birds-summary tbody tr:hover td:last-of-type a { 147 | color: #2b2b2b; 148 | box-shadow: inset 0 -2px 0 0 #2b2b2b; 149 | } 150 | -------------------------------------------------------------------------------- /select-input-styling/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fselect-input-styling%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=critical&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fselect-input-styling%2Fpackage.json) 4 | 5 | 6 | # Select Input Styling 7 | 8 | Since the last update of this demo application, I have created the [Shiny listbox example](https://github.com/davidruvolo51/shinyAppTutorials/tree/prod/shiny-listbox) as a replacement/alternative to this demo. This demo still works, but the methods in this app are outdated and will no longer be maintained. (Updates as of 23 October 2020) 9 | 10 | Questions or comments are always welcome. Feel free to open a new issue. 11 | 12 | \- David 13 | -------------------------------------------------------------------------------- /select-input-styling/app.R: -------------------------------------------------------------------------------- 1 | #'////////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' CREATED: 2018-08-16 4 | #' MODIFIED: 2021-05-29 5 | #' PURPOSE: demo for custom styling of element 45 | tags$span( 46 | id = "shiny__html_attribs", 47 | style = "display: none;", 48 | `data-html-lang` = lang, 49 | `data-html-dir` = dir 50 | ) 51 | } -------------------------------------------------------------------------------- /setting-html-attributes/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fsetting-html-attributes%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fsetting-html-attributes%2Fpackage.json) 4 | 5 | 6 | # Setting Html Attributes 7 | 8 | When evaluating Shiny applications in terms of web accessibility, assessment tools will always throw an error: 'Document language missing'. Shiny does not set this attribute and others like it by default. In this tutorial, we will learn how to fix this. For more information, see the accompanying blog post [Setting Html Attributes](https://davidruvolo51.github.io/shinytutorials/tutorials/setting-html-attributes/). 9 | 10 | ## Getting Started 11 | 12 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 13 | 14 | ### Running in your R environment 15 | 16 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 17 | 18 | ```r 19 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "setting-html-attributes") 20 | ``` 21 | 22 | ### Cloning the subdirectory 23 | 24 | You can clone the data editor subdirectory using `git sparse-checkout`. 25 | 26 | ```bash 27 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 28 | cd shinyAppTutorials 29 | git sparse-checkout init --cone 30 | git sparse-checkout set setting-html-attributes 31 | ``` 32 | 33 | Then you can run the shiny app in your preferred R environment. 34 | -------------------------------------------------------------------------------- /setting-html-attributes/app.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-07-15 5 | #' MODIFIED: 2021-05-01 6 | #' PURPOSE: setting modifying global attributes 7 | #' STATUS: working 8 | #' PACKAGES: Shiny 9 | #' COMMENTS: This example provides a way of setting the document attributes, 10 | #' for example language and direction. Use this function to add other 11 | #' important `` elements. 12 | #'//////////////////////////////////////////////////////////////////////////// 13 | 14 | # pkgs 15 | suppressPackageStartupMessages(library(shiny)) 16 | 17 | # ui 18 | ui <- tagList( 19 | set_html_attribs(lang = "en", dir = "ltr"), 20 | tags$h2("Setting HTML Attributes"), 21 | tags$p( 22 | "Use 'view page source' to see where the html attributes were added." 23 | ), 24 | tags$script(src = "set_html_attribs.js") 25 | ) 26 | 27 | 28 | # server 29 | server <- function(input, output) { 30 | 31 | } 32 | 33 | 34 | # app 35 | shinyApp(ui, server) -------------------------------------------------------------------------------- /setting-html-attributes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "setting-html-attributes", 3 | "version": "1.0.1", 4 | "description": "set document attributes", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": ["r", "shiny", "tutorials"], 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 15 | }, 16 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 17 | } 18 | -------------------------------------------------------------------------------- /setting-html-attributes/setting-html-attributes.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | RnwWeave: Sweave 12 | LaTeX: pdfLaTeX 13 | Version: 1.0 14 | 15 | RestoreWorkspace: Default 16 | SaveWorkspace: Default 17 | AlwaysSaveHistory: Default 18 | 19 | EnableCodeIndexing: Yes 20 | UseSpacesForTab: Yes 21 | NumSpacesForTab: 4 22 | Encoding: UTF-8 23 | RnwWeave: Sweave 24 | LaTeX: pdfLaTeX 25 | -------------------------------------------------------------------------------- /setting-html-attributes/www/set_html_attribs.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // FILE: set_html_attribs.js 3 | // AUTHOR: David Ruvolo 4 | // CREATED: 2020-07-15 5 | // MODIFIED: 2020-07-15 6 | // PURPOSE: find and update attributes 7 | // DEPENDENCIES: NA 8 | // STATUS: working; complete 9 | // COMMENTS: NA 10 | //////////////////////////////////////////////////////////////////////////////// 11 | 12 | // set_html_attributes 13 | // Find hidden html element and extract data attributes, and add then append 14 | // them to the tag. 15 | function set_html_attribs() { 16 | const targetElem = document.getElementsByTagName("html")[0]; 17 | const refElem = document.getElementById("shiny__html_attribs"); 18 | targetElem.lang = refElem.getAttribute("data-html-lang"); 19 | targetElem.dir = refElem.getAttribute("data-html-dir"); 20 | } 21 | 22 | // run set_html_attribs only when DOMContentLoaded, and then remove 23 | window.addEventListener("DOMContentLoaded", function(e) { 24 | set_html_attribs(); 25 | }, { once: true }); 26 | -------------------------------------------------------------------------------- /shiny-accordion/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fshiny-accordion%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fshiny-accordion%2Fpackage.json) 4 | 5 | 6 | # Shiny Accordion 7 | 8 | A shiny component for collapsing HTML elements. 9 | 10 | ## Getting Started 11 | 12 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 13 | 14 | ### Running in your R environment 15 | 16 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 17 | 18 | ```r 19 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "shiny-accordion") 20 | ``` 21 | 22 | ### Cloning the subdirectory 23 | 24 | You can clone the data editor subdirectory using `git sparse-checkout`. 25 | 26 | ```bash 27 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/ 28 | shinyAppTutorials 29 | cd shinyAppTutorials 30 | git sparse-checkout init --cone 31 | git sparse-checkout set shiny-accordion 32 | ``` 33 | 34 | Then you can run the shiny app in your preferred R environment. 35 | -------------------------------------------------------------------------------- /shiny-accordion/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shiny-accordion", 3 | "version": "1.0.0", 4 | "description": "A component for collapsing HTML elements", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } -------------------------------------------------------------------------------- /shiny-accordion/www/accordion.css: -------------------------------------------------------------------------------- 1 | .accordion { 2 | --accordion-primary: #47a9a1; 3 | --accordion-background: #ffffff; 4 | --accordion-border: transparent; 5 | --accordion-color: inherit; 6 | --border-radius: 6px; 7 | --box-shadow: 0 0 6px 4px hsla(0, 0%, 0%, 0.2); 8 | --left-offest: 21px; 9 | 10 | font-family: inherit; 11 | background-color: var(--accordion-background); 12 | box-sizing: border-box; 13 | color: var(--accordion-color); 14 | margin: 16px 0; 15 | padding: 16px 12px; 16 | border: 2px solid var(--accordion-border); 17 | border-radius: var(--border-radius); 18 | -webkit-box-shadow: var(--box-shadow); 19 | box-shadow: var(--box-shadow); 20 | } 21 | 22 | /* accordion heading styles (includes button) */ 23 | .accordion .accordion__heading { 24 | display: -webkit-flex; 25 | display: -ms-flex; 26 | display: flex; 27 | -webkit-box-pack: start; 28 | -ms-flex-pack: start; 29 | justify-content: flex-start; 30 | -webkit-box-align: center; 31 | -ms-flex-align: center; 32 | align-items: center; 33 | margin: 0; 34 | padding: 0; 35 | font-family: inherit; 36 | font-size: 100%; 37 | word-break: break-word; 38 | } 39 | 40 | /* reset button styles */ 41 | .accordion .accordion__toggle { 42 | display: -webkit-flex; 43 | display: -ms-flex; 44 | display: flex; 45 | -webkit-box-pack: start; 46 | -ms-flex-pack: start; 47 | justify-content: flex-start; 48 | -webkit-box-align: center; 49 | -ms-flex-align: center; 50 | align-items: center; 51 | width: 100%; 52 | border: none; 53 | position: relative; 54 | background: none; 55 | background-color: none; 56 | outline: none; 57 | margin: 0; 58 | padding: 0; 59 | cursor: pointer; 60 | font-size: inherit; 61 | text-align: left; 62 | color: currentColor; 63 | } 64 | 65 | /* add spacing to button label (internal text) */ 66 | .accordion .accordion__toggle .toggle__label { 67 | display: inline-block; 68 | width: calc(100% - 24px); 69 | margin-left: var(--left-offest); 70 | word-break: break-word; 71 | } 72 | 73 | /* element */ 74 | .accordion .accordion__toggle .toggle__icon { 75 | --icon-size: 26px; 76 | width: var(--icon-size); 77 | height: var(--icon-size); 78 | transform: rotate(0); 79 | transform-origin: center center; 80 | transition: transform 0.3s ease-in-out; 81 | } 82 | 83 | /* rotate (will be added to element via JS) */ 84 | .accordion .accordion__toggle .toggle__icon.rotated { 85 | transform: rotate(180deg); 86 | } 87 | 88 | 89 | /* inner accordion content */ 90 | .accordion .accordion__content { 91 | margin: 0; 92 | padding: 0; 93 | margin-left: var(--left-offest); 94 | margin-right: var(--left-offest); 95 | } 96 | 97 | /* focus event */ 98 | .accordion.accordion__focused { 99 | border: 2px solid var(--accordion-primary); 100 | } 101 | 102 | /* eliminate transition for accessibility support */ 103 | @media (prefers-reduced-motion) { 104 | /* enforce instant rotation for button icon */ 105 | .accordion .accordion__toggle .toggle__icon { 106 | transition: all 1ms ease-in-out; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /shiny-accordion/www/accordion.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // FILE: accordion.js 3 | // AUTHOR: David Ruvolo 4 | // CREATED: 2021-11-11 5 | // MODIFIED: 2021-11-11 6 | // PURPOSE: accordion button input binding 7 | // DEPENDENCIES: Shiny assets 8 | // STATUS: working 9 | // COMMENTS: This input binding toggles all instances of the accordion 10 | // component. This script will create a new constructor that extends the 11 | // Shiny.InputBinding() and adjust the state of the component when the 12 | // toggle is clicked. ARIA attributes will also be adjusted. Return current 13 | // state for use in the server (if needed by the user). 14 | //////////////////////////////////////////////////////////////////////////////// 15 | 16 | // create new binding 17 | var Accordion = new Shiny.InputBinding(); 18 | $.extend(Accordion, { 19 | 20 | // find: locate component within scope 21 | find: function(scope) { 22 | return $(scope).find(".accordion"); 23 | }, 24 | 25 | // initialize: return state if needed in the server (i.e., isOpen) 26 | initialize: function(el) { 27 | return $(el).find("button.accordion__toggle").attr("aria-expanded") === true; 28 | }, 29 | 30 | // getValue: return state if needed (i.e., isOpen) 31 | getValue: function(el) { 32 | return $(el).find("button.accordion__toggle").attr("aria-expanded") === true; 33 | }, 34 | 35 | // subscribe: create an register DOM events 36 | subscribe: function(el, callback) { 37 | 38 | // define function that handles state 39 | function toggleAccordion() { 40 | 41 | // find elements 42 | var btn = $(el).find(".accordion__toggle"); 43 | var icon = $(el).find(".toggle__icon"); 44 | var section = $(el).find(".accordion__content"); 45 | 46 | // toggle: component state + ARIA attributes 47 | if (btn.attr("aria-expanded") === "false") { 48 | btn.attr("aria-expanded", "true"); 49 | section.removeAttr("hidden"); 50 | icon.addClass("rotated"); 51 | } else { 52 | btn.attr("aria-expanded", "false"); 53 | section.attr("hidden", ""); 54 | icon.removeClass("rotated"); 55 | } 56 | 57 | // return state (if needed in the server) 58 | callback(); 59 | } 60 | 61 | // onFocus 62 | $(el).on("focusin", function(e) { 63 | $(el).addClass("accordion__focused"); 64 | }); 65 | 66 | // onBlur 67 | $(el).on("focusout", function(e) { 68 | $(el).removeClass("accordion__focused"); 69 | }); 70 | 71 | 72 | // onClick 73 | $(el).on("click", "button.accordion__toggle", function(e) { 74 | toggleAccordion(); 75 | }); 76 | }, 77 | 78 | // receiveMessage: triggered by server-side functions 79 | receiveMessage: function(el, message) { 80 | 81 | // reset accordion to it's default closed state 82 | if (message === "reset") { 83 | $(el).find(".accordion__toggle").attr("aria-expanded", "false"); 84 | $(el).find(".accordion__content").attr("hidden", ""); 85 | $(el).find(".toggle__icon").removeClass("rotated"); 86 | } 87 | }, 88 | 89 | // unsubscribe: clean up 90 | unsubscribe: function(el) { 91 | $(el).off(".Accordion"); 92 | } 93 | }) 94 | 95 | // bind 96 | Shiny.inputBindings.register(Accordion) 97 | -------------------------------------------------------------------------------- /shiny-accordion/www/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: Helvetica, Arial, sans-serif; 3 | font-size: 14pt; 4 | padding: 0; 5 | margin: 0; 6 | background-color: #f6f6f6; 7 | } 8 | 9 | h1, h2, h3 { 10 | margin: 0; 11 | margin-bottom: 8px; 12 | line-height: 1.4; 13 | color: #252525 14 | } 15 | 16 | p, li { 17 | color: #3f454b; 18 | } 19 | 20 | p { 21 | line-height: 1.5; 22 | } 23 | 24 | main { 25 | width: 90%; 26 | padding: 2em 0; 27 | margin: 0 auto; 28 | } 29 | 30 | header, section { 31 | padding: 0.5em 0; 32 | box-sizing: content-box; 33 | } 34 | 35 | header { 36 | border-bottom: 2px solid #c4c4c4; 37 | } 38 | 39 | header .title { 40 | font-size: 13pt; 41 | text-transform: uppercase; 42 | letter-spacing: 2px; 43 | margin-bottom: 4px; 44 | } 45 | 46 | header .subtitle { 47 | font-weight: 300; 48 | font-size: 24pt; 49 | } 50 | 51 | section h2 { 52 | margin-top: 6px; 53 | } 54 | 55 | #section-faq h2 { 56 | text-align: center; 57 | } 58 | 59 | #section-faq .accordion { 60 | width: 100%; 61 | margin: 24px auto; 62 | } 63 | 64 | @media screen and (min-width: 972px) { 65 | main { 66 | max-width: 1024px; 67 | } 68 | 69 | #section-faq .accordion { 70 | max-width: 724px; 71 | } 72 | } -------------------------------------------------------------------------------- /shiny-landing-page/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fshiny-landing-page%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fshiny-landing-page%2Fpackage.json) 4 | 5 | 6 | ## Creating a landing page 7 | 8 | ![landing page demo](preview.png) 9 | 10 | The purpose of this shiny app is to demonstrate how you could design a landing page in a shiny app where content is placed over a series of images arranged in a `2 x 2` layout. The motivation for this app was to answer this post on the rstudio community: [https://community.rstudio.com/t/background-images-in-shiny/12261](https://community.rstudio.com/t/background-images-in-shiny/12261). Check out longer write up here: [https://davidruvolo51.github.io/pages/shinytutorials/tutorials/landing-page.html](https://davidruvolo51.github.io/pages/shinytutorials/tutorials/landing-page.html). 11 | 12 | ## Getting Started 13 | 14 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 15 | 16 | ### Running in your R environment 17 | 18 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 19 | 20 | ```r 21 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "shiny-landing-page") 22 | ``` 23 | 24 | ### Cloning the subdirectory 25 | 26 | You can clone the data editor subdirectory using `git sparse-checkout`. 27 | 28 | ```bash 29 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 30 | cd shinyAppTutorials 31 | git sparse-checkout init --cone 32 | git sparse-checkout set shiny-landing-page 33 | ``` 34 | 35 | Then you can run the shiny app in your preferred R environment. 36 | -------------------------------------------------------------------------------- /shiny-landing-page/app.R: -------------------------------------------------------------------------------- 1 | #'////////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2019-08-09 5 | #' MODIFIED: 2021-02-19 6 | #' PURPOSE: single file shiny app 7 | #' PACKAGES: shiny 8 | #' COMMENTS: 9 | #' - app was built in response to this question: 10 | #' https://community.rstudio.com/t/background-images-in-shiny/12261/ 11 | #'////////////////////////////////////////////////////////////////////////////// 12 | 13 | #' imgs can be downloaded from my unsplash collection: 14 | #' - https://unsplash.com/collections/98323154/shiny-app-demonstrations 15 | #' 16 | #' I strongly recommend using some sort of image optimization. For example, 17 | #' the ImageOptim tool is really nice! 18 | #' 19 | #' In this demo, I will build an object that contains all of the image files 20 | #' located in `www/imgs/`. 21 | imgs <- c( 22 | "imgs/colton-jones-_ZX2WYM3_BM-unsplash.jpg", 23 | "imgs/david-tostado-woMvsY6KHac-unsplash.jpg", 24 | "imgs/hilary-bird-F_aYxIFPnfk-unsplash.jpg", 25 | "imgs/lloyd-blunk-Sv0xUKiu6ek-unsplash.jpg" 26 | ) 27 | 28 | 29 | # pkgs 30 | suppressPackageStartupMessages(library(shiny)) 31 | 32 | # ui 33 | ui <- tagList( 34 | tags$head( 35 | tags$meta(charset = "utf-8"), 36 | tags$meta( 37 | `http-quiv` = "x-ua-compatible", 38 | content = "ie=edge" 39 | ), 40 | tags$meta( 41 | name = "viewport", 42 | content = "width=device-width, initial-scale=1" 43 | ), 44 | tags$link( 45 | type = "text/css", 46 | rel = "stylesheet", 47 | href = "css/styles.css" 48 | ) 49 | ), 50 | shinyUI( 51 | navbarPage( 52 | title = "National Park", 53 | tabPanel( 54 | title = "Home", 55 | tags$div( 56 | class = "landing-wrapper", 57 | # child element 1: images 58 | tags$div( 59 | class = "landing-block background-content", 60 | # images: top -> bottom, left -> right 61 | tags$div(tags$img(src = imgs[1])), 62 | tags$div(tags$img(src = imgs[2])), 63 | tags$div(tags$img(src = imgs[3])), 64 | tags$div(tags$img(src = imgs[4])) 65 | ), 66 | # child element 2: content 67 | tags$div( 68 | class = "landing-block foreground-content", 69 | tags$div( 70 | class = "foreground-text", 71 | tags$h1("Welcome"), 72 | tags$p( 73 | "This shiny app demonstrates how to create ", 74 | "a 2 x 2 layout using css grid and ", 75 | "overlaying content." 76 | ), 77 | tags$p("Isn't this cool?"), 78 | tags$p("Yes it is!") 79 | ) 80 | ) 81 | ) 82 | ) 83 | ) 84 | ) 85 | ) 86 | 87 | 88 | # server 89 | server <- function(input, output) { 90 | 91 | } 92 | 93 | # app 94 | shinyApp(ui, server) 95 | -------------------------------------------------------------------------------- /shiny-landing-page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shiny-landing-page", 3 | "version": "1.1.0", 4 | "description": "create a landing page", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": ["r", "shiny", "tutorials"], 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 15 | }, 16 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 17 | } 18 | -------------------------------------------------------------------------------- /shiny-landing-page/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/shiny-landing-page/preview.png -------------------------------------------------------------------------------- /shiny-landing-page/shiny-landing-page.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: WINDOWS-1252 11 | 12 | RnwWeave: knitr 13 | LaTeX: XeLaTeX 14 | Version: 1.0 15 | 16 | RestoreWorkspace: Default 17 | SaveWorkspace: Default 18 | AlwaysSaveHistory: Default 19 | 20 | EnableCodeIndexing: Yes 21 | UseSpacesForTab: Yes 22 | NumSpacesForTab: 2 23 | Encoding: UTF-8 24 | RnwWeave: Sweave 25 | LaTeX: pdfLaTeX 26 | -------------------------------------------------------------------------------- /shiny-landing-page/www/css/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | padding: 0; 4 | margin: 0; 5 | font-size: 16pt; 6 | } 7 | 8 | /* override bootstrap */ 9 | body > .container-fluid { 10 | margin-top: -20px !important; 11 | margin-bottom: 0 !important 12 | } 13 | 14 | .container-fluid { 15 | padding: 0 !important; 16 | margin: 0 !important; 17 | } 18 | 19 | /* layout css */ 20 | .landing-wrapper { 21 | position: relative; 22 | } 23 | 24 | .landing-wrapper .landing-block { 25 | width: 100%; 26 | height: 93vh; 27 | } 28 | 29 | .landing-wrapper .background-content { 30 | display: grid; 31 | grid-template-columns: repeat(2, 1fr); 32 | grid-template-rows: repeat(2, 1fr); 33 | grid-row-gap: 0; 34 | overflow: hidden; 35 | } 36 | 37 | .landing-wrapper .background-content div { 38 | overflow: hidden; 39 | } 40 | 41 | .landing-wrapper .background-content img { 42 | object-fit: cover; 43 | object-position: 50% 10%; 44 | width: 100%; 45 | max-height: 100%; 46 | } 47 | 48 | .landing-wrapper .foreground-content { 49 | position: absolute; 50 | display: flex; 51 | justify-content: center; 52 | align-items: center; 53 | top: 0; 54 | z-index: 1; 55 | } 56 | 57 | .landing-wrapper .foreground-content .foreground-text { 58 | width: 50%; 59 | box-sizing: content-box; 60 | padding: 1em; 61 | background-color: white; 62 | text-align: center; 63 | border-radius: 6px; 64 | } -------------------------------------------------------------------------------- /shiny-landing-page/www/imgs/colton-jones-_ZX2WYM3_BM-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/shiny-landing-page/www/imgs/colton-jones-_ZX2WYM3_BM-unsplash.jpg -------------------------------------------------------------------------------- /shiny-landing-page/www/imgs/david-tostado-woMvsY6KHac-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/shiny-landing-page/www/imgs/david-tostado-woMvsY6KHac-unsplash.jpg -------------------------------------------------------------------------------- /shiny-landing-page/www/imgs/hilary-bird-F_aYxIFPnfk-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/shiny-landing-page/www/imgs/hilary-bird-F_aYxIFPnfk-unsplash.jpg -------------------------------------------------------------------------------- /shiny-landing-page/www/imgs/lloyd-blunk-Sv0xUKiu6ek-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/shiny-landing-page/www/imgs/lloyd-blunk-Sv0xUKiu6ek-unsplash.jpg -------------------------------------------------------------------------------- /shiny-links/R/shinyLink.R: -------------------------------------------------------------------------------- 1 | #' Shiny Link 2 | #' 3 | #' Create a link to an internal tab panel 4 | #' 5 | #' @param to page to navigate to 6 | #' @param label text that describes the link 7 | #' 8 | #' @importFrom shiny tags 9 | #' @export 10 | shinyLink <- function(to, label) { 11 | tags$a( 12 | class = "shiny__link", 13 | href = to, 14 | label 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /shiny-links/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fshiny-links%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Fshiny-links%2Fpackage.json) 4 | 5 | 6 | # shinyLink 7 | 8 | The `shinyLink` app replaces previous examples on creating links to tabs in shiny apps. This example uses a custom Shiny UI component and input binding to handle the navigation. For more information, see the longer post in the Shiny Tutorials site: [Creating Internal Links](https://davidruvolo51.github.io/shinytutorials/tutorials/shiny-link). 9 | 10 | ## Getting Started 11 | 12 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 13 | 14 | ### Running in your R environment 15 | 16 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 17 | 18 | ```r 19 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "shiny-links") 20 | ``` 21 | 22 | ### Cloning the subdirectory 23 | 24 | You can clone the data editor subdirectory using `git sparse-checkout`. 25 | 26 | ```bash 27 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 28 | cd shinyAppTutorials 29 | git sparse-checkout init --cone 30 | git sparse-checkout set shiny-links 31 | ``` 32 | 33 | Then you can run the shiny app in your preferred R environment. 34 | -------------------------------------------------------------------------------- /shiny-links/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shiny-links", 3 | "version": "1.1.0", 4 | "description": "a component for within app navigation", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": ["r", "shiny", "tutorials"], 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 15 | }, 16 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 17 | } 18 | -------------------------------------------------------------------------------- /shiny-links/shiny-links.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | RnwWeave: Sweave 12 | LaTeX: pdfLaTeX 13 | -------------------------------------------------------------------------------- /shiny-links/www/chain-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/shiny-links/www/chain-image.jpg -------------------------------------------------------------------------------- /shiny-links/www/shinyLink.js: -------------------------------------------------------------------------------- 1 | // create new input binding 2 | var shinyLink = new Shiny.InputBinding(); 3 | 4 | // define methods 5 | $.extend(shinyLink, { 6 | find: function(scope) { 7 | return $(scope).find("a.shiny__link"); 8 | }, 9 | initialize: function(el) { 10 | $(el).on("click", function(e) { 11 | e.preventDefault(); 12 | 13 | // extract destination and find matching link 14 | var to = $(el).attr("href"); 15 | var target = $(`a[data-value=${to}]`); 16 | 17 | // does the link exist? 18 | if (target.length) { 19 | 20 | // if the parent ul of the matching link has the class `.navbar-nav`, 21 | // then click the link. Links with these classes are 22 | // located in the navbar element. 23 | if ($(target).parent().parent().hasClass("navbar-nav")) { 24 | $(target).click(); 25 | window.scrollTo(0, 0,); 26 | } 27 | 28 | // if the parent ul of the matching link has the class `.nav-tabs`, 29 | // then this indicates that the link is part of of a tabSetPanel 30 | // inside a tabPanel. This means, that parent tabPanel must 31 | // be found and activated before activating the tabPanel. 32 | if ($(target).parent().parent().hasClass("nav-tabs")) { 33 | 34 | // find the nearest .tab-pane and extract `data-value` 35 | var val = $(target).closest("div.tab-pane").data("value"); 36 | var parentLink = $(`a[data-toggle="tab"][data-value="${val}"]`); 37 | 38 | // activate parent (if not already active) 39 | if (!parentLink.parent("li").hasClass("active")) { 40 | parentLink.click(); 41 | } 42 | 43 | // activate destination tab 44 | target.click(); 45 | window.scrollTo(0, 0,); 46 | 47 | } 48 | 49 | } else { 50 | console.error("No matching link found. Is the destination correct?"); 51 | } 52 | }); 53 | } 54 | }); 55 | 56 | 57 | // register 58 | Shiny.inputBindings.register(shinyLink); 59 | -------------------------------------------------------------------------------- /shiny-links/www/styles.css: -------------------------------------------------------------------------------- 1 | 2 | html, body { 3 | font-size: 15pt; 4 | } 5 | 6 | p { 7 | line-height: 1.6; 8 | margin-top: 12px; 9 | } 10 | 11 | img { 12 | display: block; 13 | margin: 0 auto; 14 | width: 100%; 15 | } 16 | 17 | .tab-content { 18 | width: 90%; 19 | margin: 0 auto; 20 | } 21 | 22 | .navbar-static-top + div.container-fluid { 23 | padding-bottom: 72px; 24 | } 25 | 26 | @media (min-width: 972px) { 27 | .tab-content { 28 | max-width: 972px; 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /shiny-listbox/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "env": { 4 | "production": { 5 | "presets": ["minify"] 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /shiny-listbox/.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | node_modules/ 3 | .sass-cache/ 4 | .pnpm-lock.yaml -------------------------------------------------------------------------------- /shiny-listbox/.postcssrc: -------------------------------------------------------------------------------- 1 | { 2 | "modules": true, 3 | "plugins": { 4 | "postcss-modules": { 5 | "generateScopedName": "[local]" 6 | }, 7 | "autoprefixer": { 8 | "overrideBrowserslist": [ 9 | ">1%", 10 | "last 4 versions", 11 | "Firefox ESR", 12 | "not ie < 9" 13 | ], 14 | "flexbox": "no-2009" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /shiny-listbox/app.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-06-29 5 | #' MODIFIED: 2020-09-07 6 | #' PURPOSE: custom select input component example 7 | #' STATUS: working 8 | #' PACKAGES: shiny; rheroicons; 9 | #' COMMENTS: NA 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | suppressPackageStartupMessages(library(shiny)) 13 | 14 | # ui 15 | ui <- tagList( 16 | tags$head( 17 | tags$meta(charset = "utf-8"), 18 | tags$meta(`http-quiv` = "x-ua-compatible", content = "ie=edge"), 19 | tags$meta( 20 | name = "viewport", 21 | content = "width=device-width, initial-scale=1" 22 | ), 23 | tags$link(rel = "stylesheet", href = "css/index.css"), 24 | tags$link(rel = "stylesheet", href = "css/listbox.css"), 25 | # tags$style( 26 | # "#popularTech .listbox-toggle { 27 | # background-color: #2d7ddd; 28 | # color: #f1f1f1; 29 | # }", 30 | # "#popularTech .listbox-option[aria-selected='true'] { 31 | # background-color: #2d7ddd; 32 | # color: #f1f1f1; 33 | # } 34 | # " 35 | # ), 36 | tags$title("shinyAppTutorials | Listbox Example") 37 | ), 38 | tags$main( 39 | class = "main", 40 | tags$header( 41 | class = "header", 42 | `aria-labelledby` = "header-title", 43 | tags$h2( 44 | id = "header-title", 45 | "Building a Shiny Listbox Component" 46 | ), 47 | tags$p( 48 | "This example shiny app demonstrates how to create a custom", 49 | "listbox widget. Listboxes are a good alternative to select", 50 | "inputs as select inputs have limited modifiable CSS", 51 | "properties. To get listbox components working in shiny, you", 52 | "will need a function that generates the HTML markup from", 53 | "a data object, a CSS file that defines the base appearance", 54 | "of the listbox element, and a javascript input binding that", 55 | "registers our component with shiny. The following example", 56 | "uses the Top 10 Technologies from the", 57 | tags$a( 58 | href = "https://insights.stackoverflow.com/survey/2020#most-popular-technologies", 59 | "StackOverflow 2020 Survey" 60 | ), ". Use the mouse to make a selection or use keyboard to", 61 | "navigation the listbox (up, down, escape, enter)." 62 | ) 63 | ), 64 | tags$form( 65 | class = "form", 66 | `aria-labelledby` = "popularTech__title", 67 | listbox( 68 | inputId = "popularTech", 69 | title = "Most Popular Technologies", 70 | label = "Select a technology", 71 | options = c( 72 | "JavaScript", 73 | "HTML/CSS", 74 | "SQL", 75 | "Python", 76 | "Java", 77 | "Bash/Shell/Powershell", 78 | "C#", 79 | "PHP", 80 | "Typescript", 81 | "C++" 82 | ) 83 | ) 84 | ), 85 | verbatimTextOutput("result") 86 | ), 87 | tags$script(src = "js/listbox.js") 88 | ) 89 | 90 | 91 | # server 92 | server <- function(input, output) { 93 | output$result <- renderText( 94 | paste0("You selected: ", input$popularTech) 95 | ) 96 | } 97 | 98 | 99 | # app 100 | shinyApp(ui, server) -------------------------------------------------------------------------------- /shiny-listbox/dev/dev.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: dev.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2021-02-16 5 | #' MODIFIED: 2021-02-16 6 | #' PURPOSE: subdir management 7 | #' STATUS: ongoing 8 | #' PACKAGES: see below 9 | #' COMMENTS: NA 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | #' pkgs 13 | install.packages("shiny") 14 | 15 | # use remotes until this package is on cran 16 | remotes::install_github("davidruvolo51/rheroicons@*release") -------------------------------------------------------------------------------- /shiny-listbox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shiny-listbox", 3 | "version": "1.1.0", 4 | "description": "create listbox components in shiny", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "scripts": { 8 | "shiny": "Rscript -e \"shiny::runApp(launch.browser = FALSE, port = 8000)\"", 9 | "dev": "parcel src/index.scss src/listbox.scss -d www/css/ --no-source-maps --no-cache --no-minify", 10 | "build": "parcel build src/index.scss src/listbox.scss -d www/css/ --no-source-maps --no-cache", 11 | "clean": "rm -rf www/css/*", 12 | "start": "concurrently \"npm run shiny\" \"npm run dev\"" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 17 | }, 18 | "keywords": [ 19 | "r", 20 | "shiny", 21 | "tutorials" 22 | ], 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 26 | }, 27 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials", 28 | "devDependencies": { 29 | "@babel/cli": "^7.12.16", 30 | "@babel/core": "^7.12.16", 31 | "@babel/preset-env": "^7.12.16", 32 | "autoprefixer": "^10.2.4", 33 | "babel-preset-minify": "^0.5.1", 34 | "concurrently": "^5.3.0", 35 | "parcel": "^1.12.4", 36 | "postcss-modules": "^4.0.0", 37 | "sass": "^1.32.7" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /shiny-listbox/shiny-listbox.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | RnwWeave: Sweave 12 | LaTeX: pdfLaTeX 13 | -------------------------------------------------------------------------------- /shiny-listbox/src/index.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0; 3 | margin: 0; 4 | font-size: 16pt; 5 | font-family: Arial, Helvetica, sans-serif; 6 | } 7 | 8 | p { 9 | line-height: 1.5; 10 | } 11 | 12 | .main { 13 | width: 90%; 14 | margin: 0 auto; 15 | padding: 72px 0; 16 | 17 | .form, .shiny-text-output { 18 | margin: 0; 19 | padding: 24px; 20 | } 21 | 22 | .form { 23 | background-color: #f6f6f6; 24 | } 25 | 26 | .shiny-text-output { 27 | background-color: #3f454b; 28 | color: #f1f1f1; 29 | word-break: break-all; 30 | } 31 | } 32 | 33 | @media (min-width: 712px) { 34 | 35 | .main { 36 | display: grid; 37 | grid-template-columns: 1fr 2fr; 38 | grid-template-rows: auto; 39 | grid-template-areas: 40 | "header header" 41 | "form output"; 42 | column-gap: 2em; 43 | 44 | .header { 45 | grid-area: header; 46 | } 47 | 48 | .form, .shiny-text-output { 49 | height: 300px; 50 | } 51 | 52 | .form { 53 | grid-area: form; 54 | } 55 | 56 | .shiny-text-output { 57 | grid-area: output; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /shiny-listbox/www/css/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0; 3 | margin: 0; 4 | font-size: 16pt; 5 | font-family: Arial, Helvetica, sans-serif; 6 | } 7 | 8 | p { 9 | line-height: 1.5; 10 | } 11 | 12 | .main { 13 | width: 90%; 14 | margin: 0 auto; 15 | padding: 72px 0; 16 | } 17 | .main .form, .main .shiny-text-output { 18 | margin: 0; 19 | padding: 24px; 20 | } 21 | .main .form { 22 | background-color: #f6f6f6; 23 | } 24 | .main .shiny-text-output { 25 | background-color: #3f454b; 26 | color: #f1f1f1; 27 | word-break: break-all; 28 | } 29 | 30 | @media (min-width: 712px) { 31 | .main { 32 | display: grid; 33 | grid-template-columns: 1fr 2fr; 34 | grid-template-rows: auto; 35 | grid-template-areas: "header header" "form output"; 36 | -webkit-column-gap: 2em; 37 | -moz-column-gap: 2em; 38 | column-gap: 2em; 39 | } 40 | .main .header { 41 | grid-area: header; 42 | } 43 | .main .form, .main .shiny-text-output { 44 | height: 300px; 45 | } 46 | .main .form { 47 | grid-area: form; 48 | } 49 | .main .shiny-text-output { 50 | grid-area: output; 51 | } 52 | } -------------------------------------------------------------------------------- /shiny-listbox/www/css/listbox.css: -------------------------------------------------------------------------------- 1 | .listbox-group { 2 | padding: 0; 3 | margin: 0; 4 | border: 0; 5 | width: 300px; 6 | position: relative; 7 | } 8 | .listbox-group button { 9 | display: block; 10 | margin: 0; 11 | padding: 6px 0; 12 | background: none; 13 | border: none; 14 | font-family: inherit; 15 | font-size: inherit; 16 | cursor: pointer; 17 | } 18 | .listbox-group .listbox-title { 19 | margin: 0; 20 | padding: 0; 21 | } 22 | .listbox-group .listbox-label { 23 | display: block; 24 | padding: 0; 25 | margin: 6px 0; 26 | font-size: 13pt; 27 | } 28 | .listbox-group .listbox-toggle { 29 | width: 100%; 30 | display: -ms-flexbox; 31 | display: flex; 32 | -ms-flex-pack: start; 33 | justify-content: flex-start; 34 | -ms-flex-align: center; 35 | align-items: center; 36 | background-color: #ffffff; 37 | border: 1px solid #3f454b; 38 | } 39 | .listbox-group .listbox-toggle .toggle-text { 40 | width: 75%; 41 | max-width: 450px; 42 | padding: 6px; 43 | padding-left: 16px; 44 | overflow: hidden; 45 | white-space: nowrap; 46 | -o-text-overflow: ellipsis; 47 | text-overflow: ellipsis; 48 | } 49 | .listbox-group .listbox-toggle .toggle-icon { 50 | width: 25%; 51 | max-width: 50px; 52 | -webkit-transform: rotate(0deg); 53 | -ms-transform: rotate(0deg); 54 | transform: rotate(0deg); 55 | -webkit-transform-origin: center; 56 | -ms-transform-origin: center; 57 | transform-origin: center; 58 | -webkit-transition: all 0.3s ease-in-out; 59 | -o-transition: all 0.3s ease-in-out; 60 | transition: all 0.3s ease-in-out; 61 | } 62 | .listbox-group .listbox-toggle .toggle-icon.rotated { 63 | -webkit-transform: rotate(180deg); 64 | -ms-transform: rotate(180deg); 65 | transform: rotate(180deg); 66 | } 67 | .listbox-group .listbox-list { 68 | list-style: none; 69 | position: absolute; 70 | width: 100%; 71 | max-height: 200px; 72 | overflow-y: scroll; 73 | padding: 0; 74 | margin: 0; 75 | top: 100%; 76 | left: 0; 77 | border: 1px solid #dad6d6; 78 | z-index: 2; 79 | -webkit-box-shadow: 0 12px 9px 3px rgba(0, 0, 0, 0.18); 80 | box-shadow: 0 12px 9px 3px rgba(0, 0, 0, 0.18); 81 | } 82 | .listbox-group .listbox-list .listbox-option { 83 | border-bottom: 1px solid #dad6d6; 84 | width: 100%; 85 | display: -ms-flexbox; 86 | display: flex; 87 | -ms-flex-pack: start; 88 | justify-content: flex-start; 89 | -ms-flex-align: center; 90 | align-items: center; 91 | text-align: left; 92 | padding: 8px 0; 93 | background-color: #ffffff; 94 | cursor: pointer; 95 | } 96 | .listbox-group .listbox-list .listbox-option .option-text { 97 | width: 80%; 98 | overflow: hidden; 99 | white-space: nowrap; 100 | -o-text-overflow: ellipsis; 101 | text-overflow: ellipsis; 102 | } 103 | .listbox-group .listbox-list .listbox-option .option-icon { 104 | margin: 0; 105 | margin-left: 12px; 106 | margin-right: 12px; 107 | opacity: 0; 108 | } 109 | .listbox-group .listbox-list .listbox-option:hover, .listbox-group .listbox-list .listbox-option:focus { 110 | background-color: #dad6d6; 111 | color: #252525; 112 | } 113 | .listbox-group .listbox-list .listbox-option[aria-selected=true] { 114 | background-color: #252525; 115 | color: #f6f6f6; 116 | } 117 | .listbox-group .listbox-list .listbox-option[aria-selected=true] .option-icon { 118 | opacity: 1; 119 | } 120 | .listbox-group .listbox-list .listbox-option:first-child { 121 | border-top: 1px solid #dad6d6; 122 | } 123 | .listbox-group .listbox-list .listbox-option:last-child { 124 | border-bottom: none; 125 | } 126 | .listbox-group .listbox-list.hidden { 127 | display: none; 128 | } 129 | 130 | @media screen and (prefers-reduced-motion: reduce) { 131 | .listbox-group .listbox-toggle .toggle-icon { 132 | -webkit-transition: none; 133 | -o-transition: none; 134 | transition: none; 135 | } 136 | } -------------------------------------------------------------------------------- /shinyAppTutorials.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders":[ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | 9 | // markdown lint config 10 | "markdownlint.config": { 11 | "MD007": { 12 | "indent": 4 13 | }, 14 | "MD041": false 15 | } 16 | }, 17 | // vscode extension recommendations 18 | "extensions": { 19 | "recommendations": [ 20 | "davidanson.vscode-markdownlint", 21 | "shd101wyy.markdown-preview-enhanced", 22 | "fcrespo82.markdown-table-formatter", 23 | "ikuyadeu.r", 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /shinytutorials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidruvolo51/shinyAppTutorials/2b34b5d2bda43605f17b729f24f842a6913c92d0/shinytutorials.png -------------------------------------------------------------------------------- /time-input/R/time_input.R: -------------------------------------------------------------------------------- 1 | #'//////////////////////////////////////////////////////////////////////////// 2 | #' FILE: time_input.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2020-07-31 5 | #' MODIFIED: 2020-08-01 6 | #' PURPOSE: Shiny UI Component for Time Inputs 7 | #' STATUS: in.progress 8 | #' PACKAGES: Shiny 9 | #' COMMENTS: requires JavaScript input binding 10 | #'//////////////////////////////////////////////////////////////////////////// 11 | 12 | #' time_input 13 | #' 14 | #' Create a time input component for use in Shiny apps. This component 15 | #' generates the markup for input[type=time]. The value for time is always in 16 | #' 24-hour format (hh:mm). 17 | #' 18 | #' @param inputId an unique identifier for an instance of the component 19 | #' @param label text that describes the input element 20 | #' @param value a default value for the time input (hh:mm) 21 | #' @param min the earliest time allowed (hh:mm; deafult: "07:00") 22 | #' @param max the latest time allowed (hh:mm; default: "10:00") 23 | #' @param caption text that provides additional infomration about the input 24 | #' 25 | time_input <- function( 26 | inputId, 27 | label, 28 | value = "13:00", 29 | min = "07:00", 30 | max = "10:00", 31 | caption = NULL 32 | ) { 33 | 34 | # generate HTML for label 35 | lab <- shiny::tags$label( 36 | class = "time__label", 37 | `for` = inputId, 38 | label 39 | ) 40 | 41 | # if caption 42 | if (!is.null(caption)) { 43 | lab$children <- shiny::tagList( 44 | lab$children, 45 | shiny::tags$span( 46 | class = "time__caption", 47 | caption 48 | ), 49 | ) 50 | } 51 | 52 | # generate HTML for input 53 | input <- shiny::tags$input( 54 | id = inputId, 55 | name = inputId, 56 | class = "time__input", 57 | type = "time", 58 | min = min, 59 | max = max, 60 | value = value 61 | ) 62 | 63 | # return 64 | shiny::tagList(lab, input) 65 | } -------------------------------------------------------------------------------- /time-input/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![version](https://img.shields.io/badge/dynamic/json?color=2d7ddd&label=version&query=version&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Ftime-input%2Fpackage.json) 3 | ![status](https://img.shields.io/badge/dynamic/json?color=success&label=status&query=status&url=https%3A%2F%2Fraw.githubusercontent.com%2Fdavidruvolo51%2FshinyAppTutorials%2Fmain%2Ftime-input%2Fpackage.json) 4 | 5 | 6 | # Time Input 7 | 8 | A time input element does not exist for shiny applications. There are a number of packages and widgets available; however, we can easily create one using `type="time"`. This shiny application demonstrates how to structure the input and register it with shiny for use in your server code. You can find more infomation in the blog post [creating a custom time input](https://davidruvolo51.github.io/shinytutorials/tutorials/time-input/). 9 | 10 | ## Getting Started 11 | 12 | You can run this app locally using the following methods: running within your R environment or cloning this subdirectory. 13 | 14 | ### Running in your R environment 15 | 16 | You can run this app within your R environment using the `runGithub` function. Enter the following command in the console. 17 | 18 | ```r 19 | shiny::runGitHub(username = "davidruvolo51", repo = "shinyAppTutorials", subdir = "time-input") 20 | ``` 21 | 22 | ### Cloning the subdirectory 23 | 24 | You can clone the data editor subdirectory using `git sparse-checkout`. 25 | 26 | ```bash 27 | git clone --filter=blob:none --sparse https://github.com/davidruvolo51/shinyAppTutorials 28 | cd shinyAppTutorials 29 | git sparse-checkout init --cone 30 | git sparse-checkout set time-input 31 | ``` 32 | 33 | Then you can run the shiny app in your preferred R environment. 34 | -------------------------------------------------------------------------------- /time-input/app.R: -------------------------------------------------------------------------------- 1 | #'////////////////////////////////////////////////////////////////////////////// 2 | #' FILE: app.R 3 | #' AUTHOR: David Ruvolo 4 | #' CREATED: 2019-11-17 5 | #' MODIFIED: 2021-02-22 6 | #' PURPOSE: single file app for time-input shiny application 7 | #' STATUS: working 8 | #' PACKAGES: shiny 9 | #' COMMENTS: NA 10 | #'////////////////////////////////////////////////////////////////////////////// 11 | 12 | # pkgs 13 | suppressPackageStartupMessages(library(shiny)) 14 | 15 | # app 16 | ui <- tagList( 17 | tags$head( 18 | tags$link( 19 | rel = "stylesheet", 20 | type = "text/css", 21 | href = "styles.css" 22 | ), 23 | tags$link( 24 | type = "text/css", 25 | rel = "stylesheet", 26 | href = "time_input.css" 27 | ), 28 | tags$style( 29 | "#timeOutput { 30 | background-color: #353535; 31 | color: #f1f1f1; 32 | box-sizing: content-box; 33 | padding: 12px; 34 | }" 35 | ), 36 | tags$title("Time Input Example | shinyAppTutorials ") 37 | ), 38 | tags$main( 39 | tags$h1("Time Input Example"), 40 | tags$form( 41 | time_input( 42 | inputId = "take_away", 43 | label = "What time would you like to pick up your order?", 44 | caption = "We are open everyday from 11:00 to 18:00", 45 | value = "12:00", 46 | min = "11:00", 47 | max = "18:00" 48 | ), 49 | tags$h2("Time Selected"), 50 | verbatimTextOutput("timeOutput") 51 | ) 52 | ), 53 | tags$script(src = "time_input.js") 54 | ) 55 | 56 | # server 57 | server <- function(input, output, session) { 58 | output$timeOutput <- renderPrint({ 59 | input$take_away 60 | }) 61 | } 62 | 63 | # app 64 | shinyApp(ui, server) -------------------------------------------------------------------------------- /time-input/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "time-input", 3 | "version": "1.1.0", 4 | "description": "creating a custom time input for use in shiny", 5 | "author": "@dcruvolo", 6 | "status": "active", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/davidruvolo51/shinyAppTutorials.git" 10 | }, 11 | "keywords": [ 12 | "r", 13 | "shiny", 14 | "tutorials" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/davidruvolo51/shinyAppTutorials/issues" 19 | }, 20 | "homepage": "https://github.com/davidruvolo51/shinyAppTutorials" 21 | } 22 | -------------------------------------------------------------------------------- /time-input/time-input.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | RnwWeave: Sweave 12 | LaTeX: pdfLaTeX 13 | -------------------------------------------------------------------------------- /time-input/www/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: Helvetica; 3 | font-size: 16pt; 4 | } 5 | 6 | 7 | main { 8 | width: 90%; 9 | max-width: 912px; 10 | margin: 0 auto; 11 | padding: 32px; 12 | } -------------------------------------------------------------------------------- /time-input/www/time_input.css: -------------------------------------------------------------------------------- 1 | .time__label { 2 | font-family: inherit; 3 | color: #353535; 4 | font-size: 16pt; 5 | line-height: 1.4; 6 | } 7 | 8 | 9 | .time__caption { 10 | font-family: inherit; 11 | color: #53595f; 12 | display: block; 13 | margin-bottom: 8px; 14 | line-height: 1.4; 15 | font-size: 12pt; 16 | } 17 | 18 | 19 | .time__input { 20 | display: block; 21 | width: 125px; 22 | border: 1px solid #53595f; 23 | padding: 12px; 24 | font-size: 12pt; 25 | text-align: center; 26 | box-shadow: inset 0 0 4px 0 #c5c5c5; 27 | } -------------------------------------------------------------------------------- /time-input/www/time_input.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // FILE: time_input.js 3 | // AUTHOR: David Ruvolo 4 | // CREATED: 2020-07-31 5 | // MODIFIED: 2021-02-22 6 | // PURPOSE: input binding for time inputs 7 | // DEPENDENCIES: NA 8 | // STATUS: working 9 | // COMMENTS: NA 10 | //////////////////////////////////////////////////////////////////////////////// 11 | 12 | // init new binding 13 | var timeInput = new Shiny.InputBinding(); 14 | 15 | // extend class 16 | $.extend(timeInput, { 17 | 18 | // locate all instances of input[type='time'] 19 | find: function (scope) { 20 | return $(scope).find(".time__input"); 21 | }, 22 | 23 | // return default value defined by the attribute `value` 24 | // this will also reset the input to it's default value 25 | // on page refresh 26 | initialize: function(el) { 27 | return el.value = $(el).attr("value"); 28 | }, 29 | 30 | // callback function: when called, return the current input value 31 | getValue: function(el) { 32 | return el.value; 33 | }, 34 | 35 | // events: when input is changed, return the value 36 | subscribe: function (el, callback) { 37 | $(el).on("change", function (e) { 38 | 39 | // callback; i.e., run `getValue` 40 | callback(); 41 | }); 42 | } 43 | }); 44 | 45 | // register 46 | Shiny.inputBindings.register(timeInput) 47 | 48 | 49 | // Alternatives for initialize and getvalue to return value in 12 hour format 50 | // intialize: function (el) { 51 | // el.value = $(el).attr("value"); 52 | // this.getValue(); 53 | // }, 54 | 55 | // // method: returns time in 12-hour format 56 | // getValue: function (el) { 57 | // var val = el.valueAsDate; 58 | // var time = val.toLocaleString("en-us", { hour: "numeric", minute: "numeric" }); 59 | // return time; 60 | // }, --------------------------------------------------------------------------------