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
golem::create_golem("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
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():
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
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
- delete
R/app_server.R, and keep onlygolem_add_external_resources()fromR/app_ui.R - turn each screen into a file exposing a
page(), the waynew_page()lays it out - replace
shinyApp()withbrochureApp()inrun_app(), passinggolem_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.
