--- title: "Designing a multipage app" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Designing a multipage app} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE) ``` ```{r} library(shiny) library(brochure) ``` Everything in brochure follows from one sentence: **a page is a Shiny session, and two sessions never meet.** Opening `/contact` does not hide `/` and show something else. It closes the first session and opens a second one, which loads its own UI and runs its own server. Nothing computed on the first page is there when the second starts. That is not a limitation to work around. It is the trade: you get urls that mean something, and you give up the shared reactive graph that a single page app leans on. This article is about what that costs and how the usual patterns translate. ## Coming from a single page app | Single page Shiny | brochure | | --- | --- | | `tabsetPanel()` / `navbarPage()` | one `page()` per tab | | `conditionalPanel()` on a "screen" input | one `page()` per screen | | `updateTabsetPanel()` to move around | a link, or `server_redirect()` | | `renderUI()` picking the screen | the url picks the page | | a navbar written once in the ui | `wrapped`, or a tag list in `...` | | `reactiveValues()` shared by the screens | a cookie plus a store | | `moduleServer()` per screen | the same modules, one per page | Modules survive the move unchanged, which is most of the work saved. What does not survive is whatever passed values *between* them. ## Navigation is a link ```{r} tags$a(href = "/contact", "Contact") ``` No router to configure, no observer, no `updateTabsetPanel()`. The browser asks the server for that url, and the back button, the middle click and "open in a new tab" all work because nothing was faked. `server_redirect()` is for the other case: navigating as the *result* of something, once a form is submitted or a login accepted. ```{r} page( href = "/login", ui = tagList(passwordInput("pw", "Password"), actionButton("go", "Log in")), server = function(input, output, session) { observeEvent(input$go, { if (identical(input$pw, Sys.getenv("APP_PASSWORD"))) { server_redirect("/") } }) } ) ``` ## What every page shares Three things reach every page, and they are the three places to put anything common — see `vignette("brochure")` for the mechanics: - tags and dependencies passed to `brochureApp()` in `...`: css, favicon, a `