generated from posit-conf-2023/workshop-template
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
40 changed files
with
667 additions
and
384 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
_freeze/materials/d1-01-welcome/index/execute-results/html.json
Large diffs are not rendered by default.
Oops, something went wrong.
21 changes: 21 additions & 0 deletions
21
_freeze/materials/d1-02-structure/index/execute-results/html.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"hash": "df5024907ac0a3253be0f2e64a57973a", | ||
"result": { | ||
"markdown": "---\ntitle: \"Application Structure\"\ntitle-slide-attributes:\n data-background-image: assets/img/lego_city.jpg\n data-background-size: contain\n data-background-opacity: \"0.3\"\nsubtitle: \"posit::conf(2023) <br> Shiny in Production: Tools & Techniques\"\nfooter: \"[{{< var workshop_short_url >}}]({{< var workshop_full_url >}})\"\nformat:\n revealjs:\n theme: [default, ../slides.scss] # moon= teal bg | dark\n scrollable: true\n incremental: false\n slide-number: c/t # c/t | c | h/v | h.v\n slide-tone: false #true\n code-line-numbers: false\n history: false\nfrom: markdown+emoji\nrevealjs-plugins:\n - codewindow\n---\n\n\n## It's Never Just Shiny\n\n... at least for production-quality apps!\n\n* External data sources\n* Connections to other execution backends\n* Additional R packages!\n\n\n# Application Structure Options\n\n## A Single Point: `app.R`\n\nPrototype apps can coast by with a single `app.R`\n\n* More inputs, visualizations, modules, tabs ...\n* Eventually the `app.R` almost explodes\n* Difficult to collaborate without conflicts\n\n## `R` Directory\n\n* Shiny supports auto-loading scripts in an `R` directory\n* Nested directories not supported\n* More information on the [App Formats](https://shiny.rstudio.com/articles/app-formats.html ) article\n\n## Enter the [`{golem}`](https://thinkr-open.github.io/golem/)\n\n> Opinionated framework for building production-grade Shiny applications as **R packages**\n\n* Scripts guide you with first steps akin to `{usethis}` & `{devtools}`\n* Encourages Shiny best practices (especially **modules**)\n* Streamlines deployment on multiple platforms\n\n::: {.notes}\n* Developed by Colin Fay & the ThinkR team in France\n* The moral of the stoary is that Shiny apps can get really big, really fast\n + We need to provide a lot of structure and guardrails; similar to what we do when developing *R packages*\n + This is why {golem} apps *are* R packages\n* Once you go {golem}, you never go back\n:::\n\n## {golem} project structure\n\n```\n├── DESCRIPTION\n├── NAMESPACE\n├── R\n│ ├── app_config.R\n│ ├── app_server.R\n│ ├── app_ui.R\n│ └── run_app.R\n├── dev\n│ ├── 01_start.R\n│ ├── 02_dev.R\n│ ├── 03_deploy.R\n│ └── run_dev.R\n├── inst\n│ ├── app\n│ │ └── www\n│ │ └── favicon.ico\n│ └── golem-config.yml\n└── man\n └── run_app.Rd\n```\n\n::: {.notes}\n* Looks a LOT like an R package's structure (because it is one!)\n* I want to just highlight the two directories `dev` and `R`\n + The scripts in `dev` have functions that safely do other things within the package (create scripts, add dependencies, etc.)\n + Some scripts in this directory will create other scripts in the `R` directory\n + The `R` directory is where all of the logic lives (your modules, custom functions, and UI/Server scripts)\n:::\n\n## Getting Started with {golem}\n\nUse helper functions in `dev/01_start.R` to...\n\n* Create `DESCRIPTION`, `README`, `LICENSE` (etc.) files\n* Use git, tests\n* Add your own favicon 🚀\n\n::: {.notes}\n- This file and the functions it includes help you manage things at the *project*-level\n:::\n\n## Developing with {golem}\n\nUse the helper functions in `dev/02_dev.R` to...\n\n* Add R package dependencies\n* Create custom functions\n* Add Shiny modules\n\n. . .\n\nAnd use `dev/run_dev.R` to run your app\n\n::: {.notes}\n- As you run these functions and watch in awe as your `DESCRIPTION` file changes automatically, as new .R files magically appear out of thin air that have already handled all of the tricky parts for you, you too will realize that {golem} is the ChatGPT of Shiny development\n:::\n\n## Deploying with {golem}\n\nUse the helper functions in `dev/03_deploy.R` to...\n\n* Check your R package ( `devtools::check()` )\n* Build your R package ( `R CMD build mypackage` )\n* Generate deployment files for\n + RStudio Connect\n + Shinyapps.io\n + Your own Shiny Server\n + Dockerized deployments (e.g., ShinyProxy)\n\n# All About Modules\n\n## What are Modules?\n\n. . .\n\n### Building blocks to compose any Shiny app out of smaller, more understandable pieces\n\n* Avoids namespace collisions when using same widget across different areas of your app\n* Allow you to encapsulate distinct app interfaces\n* Organize code into logical and easy-to-understand components\n* Facilitate collaboration\n\n## Sound familiar?\n\n* R functions also help avoid collisions in variable names with general R code\n* Essential for creating non-trivial and extensive workflows\n\n# Module Code Example\n\n## Anatomy of a Function (UI) {auto-animate=true}\n\n:::: {.columns}\n\n::: {.column width=\"60%\"}\n::: {.codewindow .r}\npicker.R\n```r\nset_picker_ui <- function() {\n tagList(\n selectInput(\n inputId = \"set_num\",\n label = \"Select a set\"\n choices = c(\"set1\", \"set2\"),\n selected = \"set1\",\n multiple = FALSE\n )\n )\n}\n```\n:::\n:::\n\n::: {.column width=\"40%\"}\n\n:::\n\n::::\n\n## Anatomy of a Module (UI) {auto-animate=true}\n\n:::: {.columns}\n\n::: {.column width=\"60%\"}\n\n::: {.codewindow .r}\nmod_picker.R\n```r\nset_picker_ui <- function(id) {\n ns <- NS(id)\n tagList(\n selectInput(\n inputId = ns(\"set_num\"),\n label = \"Select a set\"\n choices = c(),\n multiple = FALSE\n )\n )\n}\n```\n:::\n:::\n\n::: {.column width=\"40%\"}\n\n:::\n\n::::\n\n## Anatomy of a Module (UI)\n\n:::: {.columns}\n\n::: {.column width=\"60%\"}\n\n::: {.codewindow .r}\nmod_picker.R\n```{.r code-line-numbers=\"1,2,5\"}\nset_picker_ui <- function(id) {\n ns <- NS(id)\n tagList(\n selectInput(\n inputId = ns(\"set_num\"),\n label = \"Select a set\"\n choices = c(),\n multiple = FALSE\n )\n )\n}\n```\n:::\n:::\n\n::: {.column width=\"40%\"}\n\n* `id`: String to use for namespace\n* `ns <- NS(id)`: Create proper namespace function\n\n:::\n\n::::\n\n## Anatomy of a Module (Server) {auto-animate=true}\n\n::: {.codewindow .r}\nmod_picker.R\n```r\nset_picker_server <- function(input, output, session, sets_rv) {\n set_choices <- reactive({\n # do something with sets_rv\n })\n\n observeEvent(set_choices(), {\n req(set_choices())\n updateSelectInput(\n \"set_num\",\n choices = set_choices()\n )\n })\n}\n```\n:::\n\n## Anatomy of a Module (Server) {auto-animate=true}\n\n::: {.codewindow .r}\nmod_picker.R\n```r\nset_picker_server <- function(id, sets_rv) {\n moduleServer(\n id,\n function(input, output, session) {\n set_choices <- reactive({\n # do something with sets_rv\n })\n\n observeEvent(set_choices(), {\n req(set_choices())\n updateSelectInput(\n \"set_num\",\n choices = set_choices()\n )\n })\n }\n )\n}\n```\n:::\n\n\nMinimal changes necessary\n\n## Anatomy of a Module (Server) {auto-animate=true}\n\n:::: {.columns}\n\n::: {.column width=\"70%\"}\n\n```{.r code-line-numbers=\"1,2\"}\nset_picker_server <- function(id, sets_rv) {\n moduleServer(\n id,\n function(input, output, session) {\n set_choices <- reactive({\n # do something with sets_rv\n })\n\n observeEvent(set_choices(), {\n req(set_choices())\n updateSelectInput(\n \"set_num\",\n choices = set_choices()\n )\n })\n }\n )\n}\n```\n\n:::\n\n::: {.column width=\"30%\"}\n\n:thinking: `id`\n\n:::\n\n::::\n\n::: {style=\"font-size: 70%\"}\n`moduleServer()`: Encapsulate server-side logic with namespace applied.\n:::\n\n## Invoking Modules\n\n::: {.codewindow .r}\napp.R\n```{.r}\nlibrary(shiny)\nlibrary(bslib)\nui <- page_fluid(\n set_picker_ui(\"mod1\")\n)\n\nserver <- function(input, output, session) {\n sets_rv <- reactive({\n # processing\n })\n\n set_picker_server(\"mod1\", sets_rv)\n}\n\nshinyApp(ui, server)\n```\n:::\n\n## Giving and Receiving\n\n::: {.codewindow .r}\nmod_picker.R\n```r\nset_picker_ui <- function(id, label = \"Select a set\") {\n ns <- NS(id)\n tagList(\n selectInput(\n inputId = ns(\"set_num\"),\n label = label,\n choices = c(),\n multiple = FALSE\n )\n )\n}\n```\n:::\n\n::: {style=\"font-size: 80%\"}\n* Reasonable inputs: static values, vectors, flags\n* Avoid **reactive** parameters\n* Return value: `tagList()` of inputs, output placeholders, and other UI elements\n:::\n\n## Giving and Receiving\n\n::: {.codewindow .r}\nmod_picker.R\n```{.r}\nset_picker_server <- function(id, sets_rv) {\n moduleServer(\n id,\n function(input, output, session) {\n set_choices <- reactive({\n # do something with sets_rv\n })\n\n observeEvent(set_choices(), {\n req(set_choices())\n updateSelectInput(\n \"set_num\",\n choices = set_choices()\n )\n })\n }\n )\n}\n```\n:::\n\n::: {style=\"font-size: 80%\"}\nInput & return values can be a mix of static and **reactive** objects\n:::\n\n\n## To () or not to ()\n\n:::: {.columns}\n\n::: {.column width=\"45%\"}\n\n::: {.codewindow .r}\napp_server.R\n```{.r}\n# app server\nsets_rv <- reactive({\n # processing\n})\n\nset_picker_server(\"mod1\", sets_rv)\n```\n:::\n\n:::\n\n::: {.column width=\"55%\"}\n\n::: {.codewindow .r}\nmod_picker.R\n```{.r}\nset_picker_server <- function(id, sets_rv) {\n moduleServer(\n id,\n function(input, output, session) {\n # ...\n\n set_selection <- reactive({\n input$set_num\n })\n\n set_selection\n }\n )\n}\n```\n:::\n:::\n\n::::\n\n::: {style=\"font-size: 70%;\"}\n* Reactive parameters reference by **name**: `sets_rv`\n* Inside module, **invoke** reactive parameter as you would any other reactive in Shiny: `sets_rv()`\n* Any reactive(s) returned by module should also be reference by **name**: `set_selection`, ~~`set_selection()`~~\n:::\n\n::: footer\n:::\n\n# Code-Along {background-color=\"#17395c\"}\n\nAdd a new Shiny module to pick LEGO set themes\n\n* [Details](codealong-1.html){target=\"_blank\"}\n* Posit Cloud project: **Application Structure Code-along 1**\n\n## Your Turn: [Exercise 1](ex-1.html){target=\"_blank\"}\n\nCreate a new Shiny module with LEGO data metrics!\n\n* [Details](ex-1.html){target=\"_blank\"}\n* Posit Cloud project: **Application Structure Exercise 1**\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n<div class=\"countdown\" id=\"timer_41152904\" data-update-every=\"1\" tabindex=\"0\" style=\"right:0;bottom:0;\">\n<div class=\"countdown-controls\"><button class=\"countdown-bump-down\">−</button><button class=\"countdown-bump-up\">+</button></div>\n<code class=\"countdown-time\"><span class=\"countdown-digits minutes\">10</span><span class=\"countdown-digits colon\">:</span><span class=\"countdown-digits seconds\">00</span></code>\n</div>\n```\n\n:::\n:::\n\n\n\n# Dependency Management\n\n## Turned Upside-Down\n\nImagine your application is working great!\n\n<br>\n\n. . .\n\n:::: {.columns .v-center-container}\n\n::: {.column width=\"50%\"}\n\n```r\nupdate.packages(ask = FALSE)\nremotes::install_github(\"pkg\")\n```\n\n:::\n\n::: {.column width=\"50%}\n\n![](https://rfortherestofus.com/wp-content/uploads/2020/09/update-packages-prompt.png)\n\n:::\n\n::::\n\n## Turned Upside-Down\n\n:::: {.columns .v-center-container}\n\n::: {.column width=\"50%\"}\n\n`ggplot2` version `0.9.3`\n\n![](assets/img/computer_user_happy.png){width=\"60%\"}\n\n:::\n\n::: {.column width=\"50%\"}\n\n`ggplot2` version `1.0.0`\n\n![](http://i.giphy.com/RhEvCHIeZAZ6E.gif)\n\n:::\n\n::::\n\n## Take Control with [`{renv}`](https://rstudio.github.io/renv/)\n\n> Create **r**eproducible **env**ironments for your R projects.\n\n* Next generation of `{packrat}`\n* Isolated package library from rest of your system\n* Transfer projects to different collaborators / platforms\n* **Reproducible** package installation\n* Easily create new projects or convert existing projects with RStudio or built-in functions.\n\n## Under the Hood\n\nUpon initializing a project:\n\n1. Project-level `.Rprofile` to activate custom package library on startup\n1. Lockfile `renv.lock` to describe state of project library\n1. `renv/library` to hold private project library\n1. `renv/activate.R` performs activation\n\n## Develop a Routine\n\nSticking with `{renv}` will pay off (trust me)\n\n* Fair play to mix packages from CRAN, GitHub, and proprietary sources\n* Roll back when a package upgrade doesn't play nicely\n* **You** make the call when to update your library!\n", | ||
"supporting": [], | ||
"filters": [ | ||
"rmarkdown/pagebreak.lua" | ||
], | ||
"includes": { | ||
"include-in-header": [ | ||
"<link href=\"../../site_libs/countdown-0.4.0/countdown.css\" rel=\"stylesheet\" />\n<script src=\"../../site_libs/countdown-0.4.0/countdown.js\"></script>\n" | ||
], | ||
"include-after-body": [ | ||
"\n<script>\n // htmlwidgets need to know to resize themselves when slides are shown/hidden.\n // Fire the \"slideenter\" event (handled by htmlwidgets.js) when the current\n // slide changes (different for each slide format).\n (function () {\n // dispatch for htmlwidgets\n function fireSlideEnter() {\n const event = window.document.createEvent(\"Event\");\n event.initEvent(\"slideenter\", true, true);\n window.document.dispatchEvent(event);\n }\n\n function fireSlideChanged(previousSlide, currentSlide) {\n fireSlideEnter();\n\n // dispatch for shiny\n if (window.jQuery) {\n if (previousSlide) {\n window.jQuery(previousSlide).trigger(\"hidden\");\n }\n if (currentSlide) {\n window.jQuery(currentSlide).trigger(\"shown\");\n }\n }\n }\n\n // hookup for slidy\n if (window.w3c_slidy) {\n window.w3c_slidy.add_observer(function (slide_num) {\n // slide_num starts at position 1\n fireSlideChanged(null, w3c_slidy.slides[slide_num - 1]);\n });\n }\n\n })();\n</script>\n\n" | ||
] | ||
}, | ||
"engineDependencies": {}, | ||
"preserve": {}, | ||
"postProcess": true | ||
} | ||
} |
Oops, something went wrong.