--- title: "Using brochure with golem" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Using brochure with golem} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE) ``` `{golem}` organises a Shiny application as a package with one UI and one server, assembled in `app_ui.R` and `app_server.R` and launched by `run_app()`. A brochure app has no single UI and no single server: it has one of each *per page*. The two fit together well, but the shape of the app changes, and this article is about that shape rather than about the two functions brochure exports — those are in `?golem_hook` and `?new_page`. ## What changes | golem, as usual | golem with brochure | | --- | --- | | `app_ui.R` assembles the whole interface | gone; each page brings its own UI | | `app_server.R` holds the whole server | gone; each page brings its own server | | modules are pieces of one page | a module *is* a page, plus a `page()` around it | | `run_app()` calls `shinyApp()` | `run_app()` calls `brochureApp()` | `golem_add_external_resources()` stays, and matters more than before: it is the one place declaring your css, javascript and favicon, and it has to reach every page. ## A fresh app ```{r} golem::create_golem(file.path(tempdir(), "myapp"), project_hook = brochure::golem_hook) ``` The hook removes `app_ui.R` and `app_server.R`, writes a `run_app()` built on `brochureApp()`, and drops in a first page, `R/mod_home.R`. It also rewrites the `golem::add_module()` calls in `dev/02_dev.R` so they use the brochure module template — the file you work from already does the right thing. ## Adding a page ```{r} golem::add_module(name = "contact", module_template = brochure::new_page) ``` You get a file holding three things: a module UI, a module server, and a function wrapping both into a `page()`: ```{r} mod_contact_ui <- function(id) { ns <- NS(id) tagList( h1("Hello {brochure}!") ) } mod_contact_server <- function(id) { moduleServer(id, function(input, output, session) { ns <- session$ns }) } contact <- function(id = "contact", href = "/contact") { page( href = href, ui = mod_contact_ui(id = id), server = function(input, output, session) { mod_contact_server(id = id) } ) } ``` That last function is what you add to `run_app()`. The module keeps its namespace, so two pages may reuse the same module under different ids and hrefs — a `mod_profile` mounted once at `/me` and once at `/user/:id`. ## Wiring it up ```{r} run_app <- function( onStart = NULL, options = list(), enableBookmarking = NULL, ... ) { with_golem_options( app = brochureApp( # Injected into every page: css, javascript, favicon golem_add_external_resources(), # One entry per page home(), contact(), onStart = onStart, options = options, enableBookmarking = enableBookmarking ), golem_opts = list(...) ) } ``` `golem_add_external_resources()` goes in `...`, among the pages, because `brochureApp()` injects everything there that is not a page or a redirection into all of them. Put it inside a single `page()` and only that page gets your stylesheet. ## Adapting an existing golem app 1. delete `R/app_server.R`, and keep only `golem_add_external_resources()` from `R/app_ui.R` 2. turn each screen into a file exposing a `page()`, the way `new_page()` lays it out 3. replace `shinyApp()` with `brochureApp()` in `run_app()`, passing `golem_add_external_resources()` and then the pages Step 2 is where the work is, and it is rarely mechanical. Anything the old `app_server.R` held to pass state between screens has no equivalent: a page cannot read another page's reactive values, because they never exist at the same time. That state has to move to a cookie plus a store — see `vignette("cookies")`. ## What stays golem `golem::get_golem_options()`, the config file, `app_sys()`, the `dev/` scripts, `golem::add_dockerfile()`: all of it works unchanged. `run_app()` is still the entry point, and the app is still a package. Testing changes a little: there is no single `app_server()` to drive with `shiny::testServer()`. Test each page's module server as you would any module, and drive the app's routing through its `httpHandler` with a request built by hand. `vignette("testing")` covers both.