When a {shiny}
app loads, it receives a
request
object, with properties such as
QUERY_STRING
(the ?x=1&y=2
part of a url)
and HTTP_COOKIE
(the names and values of cookies). Use
{scenes}
to switch between alternative {shiny}
UIs depending on properties of that request
object.
It’s possible to process the request
using a UI
function, instead of a standard shiny::tagList()
.
So why not process the request
through such a function?
I created {scenes}
to write a single login process for
the apps I produce for the R4DS
Online Learning Community. The goal was to create each UI without
having to think about login, and then wrap those UIs in the common login
framework. That process became the {shinyslack}
package.
Perhaps you have your own login process. Or perhaps you want to show
completely different UIs to different customer segments visiting the
same URL, depending on a cookie or a query parameter.
{scenes}
exists to enables these workflows.
Here I’ll demonstrate a simple example of changing UIs based on
various request
parameters. You can see a deployed version
of this app at https://r4dscommunity.shinyapps.io/scenes/.
First we’ll create four simple UIs. You can ignore the specifics of
these UIs for now, but you might want to come back to see how they work
once you run the app. In a real {scenes}
app, these should
each be a shiny::tagList()
UI or UI function.
# ui1 loads if none of the requirements are met.
ui1 <- shiny::tagList(
shiny::p("This is UI 1."),
shiny::a("Add '?code' to the URL to see UI 2.", href = "?code")
)
# ui2 allows us to create the cookie requirement for ui3.
ui2 <- cookies::add_cookie_handlers(
shiny::tagList(
shiny::p("This is UI 2."),
shiny::actionButton("cookie_simple", "Store Simple Cookie"),
shiny::p("Press the button to see UI 3.")
)
)
# ui3 allows us to update that cookie to one that will pass validation.
ui3 <- cookies::add_cookie_handlers(
shiny::tagList(
shiny::p("This is UI 3."),
shiny::actionButton("cookie_valid", "Store Valid Cookie"),
shiny::p("Press the button to see UI 4.")
)
)
# ui4 only loads when everything is all set. It has a button to reset things.
ui4 <- cookies::add_cookie_handlers(
shiny::tagList(
shiny::p("This is UI 4."),
shiny::actionButton("reset", "Reset"),
shiny::p("Press the button to go back to UI 2.")
)
)
We’ll use actions to decide which of those UIs to display.
In {scenes}
, a shiny_scene
associates a UI
with one or more scene_actions
that are used to choose it.
In this case, we’ll display our UIs in these four situations:
ui4
when the user has a particular cookie set
and the value of that cookie successfully passes a validation
function.ui3
when the user has that cookie set, but
their value doesn’t validate.ui2
when the user has a particular parameter in
the URL query string.ui1
when none of those cases are true. In a
real app, this final UI would likely be the login screen, or perhaps an
error page.In this toy example, our cookies are “valid” if they have a certain value. That value changes sometimes, so we create a validation function that accepts both the cookie value and the acceptable value.
We wrap ui4
with the req_has_cookie()
action, into a shiny_scene
.
scene4 <- set_scene(
ui4,
req_has_cookie(
cookie_name = "our_cookie",
validation_fn = our_cookie_validator,
acceptable = "good value" # We can pass variables through to our validator.
)
)
The shiny_scene
for ui3
is similar, but we
skip the validation. In other words, they must have the cookie set, but
we don’t care what value it has.
For ui2
, we’re looking for a parameter named “code”. We
don’t care what the value is (if we did, we’d pass a vector of
acceptable values).
Finally, we set up a scene without any actions for our fall-through UI.
We wrap our scenes together with change_scene()
. We list
the scenes in priority order.
We can use this ui
just like any other
{shiny}
UI.
# Any UI that the user sees will use this
# shared server backend.
server <- function(input, output, session) {
# If they press the button in ui2, save a cookie and reload.
shiny::observeEvent(
input$cookie_simple,
{
cookies::set_cookie("our_cookie", "bad value")
session$reload()
}
)
# If they press the button in ui3, save a "valid" cookie and reload.
shiny::observeEvent(
input$cookie_valid,
{
cookies::set_cookie("our_cookie", "good value")
session$reload()
}
)
# If they press the reset button in ui4, delete the cookie and reload.
shiny::observeEvent(
input$reset,
{
cookies::remove_cookie("our_cookie")
session$reload()
}
)
}
shiny::shinyApp(
ui = ui,
server = server
)