shinypal turns a Shiny app into a point-and-click
workflow builder: the user assembles a pipeline step by step, and
shinypal shows (and exports) the equivalent reproducible R code via
{shinymeta}. This vignette walks through building your
own.
A shinypal app has two parts:
The host calls shinypal_ui() in its UI and
shinypal_setup() at the top of its server function. Both
take a vector of paths to your module directories.
modules <- list.dirs("modules", recursive = FALSE)
ui <- bslib::page_navbar(
title = "My workflow builder",
bslib::nav_panel("Build", shinypal_ui(modules))
)
server <- function(input, output, session) {
shinypal_setup(
input, output, session,
modules = modules,
download_template = "modules/report.qmd"
)
}
shiny::shinyApp(ui, server)shinypal_ui() renders two cards (the menu of
possible steps on the left and the draggable workflow
on the right) plus a sidebar holding the live report and a download
button. shinypal_setup() initializes the shared reactive
state, wires the report and download handler, and sources each module’s
ui-aux.R and server.R.
Each module is a folder containing at least three files:
| File | Sourced by | Purpose |
|---|---|---|
ui-main.R |
shinypal_ui() |
the panel advertising the possible steps included in the module |
ui-aux.R |
shinypal_setup() |
defines the fun_workflow and fun_report
helpers for the steps |
server.R |
shinypal_setup() |
defines the server logic for each step to the workflow |
ui-aux.R and server.R are sourced with
local = TRUE, so they share the host’s
input/output/session and can see
each other’s definitions.
ui-main.RThis file is evaluated and its value becomes a panel in the menu, so it should return a single [bslib::accordion_panel()]. Give it an action button the server will listen for.
ui-aux.RDefines two functions for each step with index ind: one
that draws the step’s panel in the workflow accordion, and one that
draws its block in the report. Use the shinypal UI helpers so the panel
gets a remove button, a dataset selector, and a copy-able code block for
free.
filter_workflow <- function(ind) {
accordion_panel_remove_button(
ind, "Filter rows",
select_dataset_input(ind),
select_column_input(ind, "Column to filter on:"),
shiny::numericInput(paste0("min_", ind), "Minimum value", value = 0),
df_modal_button(ind)
)
}
filter_report <- function(ind) {
verbatimTextOutput_copy(ind)
}server.RWhen the Add button is clicked, grab a fresh index with
next_step_index(), build a [shinymeta::metaReactive2()]
that both computes the result and records the code
that produced it, then register everything with
add_shinypal_data_step().
shiny::observeEvent(input$add_filter, {
ind <- next_step_index()
# metaReactive2 returns the data when called, and its code when expanded.
# Name it occs_<ind> so shinypal can find it as an intermediate dataset.
data <- shinymeta::metaReactive2(varname = paste0("occs_", ind), {
df <- get_int_data(input[[paste0("dataset_", ind)]])()
col <- input[[paste0("column_", ind)]]
shiny::req(col)
shinymeta::metaExpr(
dplyr::filter(df, !!col >= !!input[[paste0("min_", ind)]])
)
})
add_shinypal_data_step(
ind,
data = data,
fun_workflow = filter_workflow,
fun_report = filter_report,
libs = "dplyr",
select_dataset = TRUE,
column_ids = paste0("column_", ind)
)
})add_shinypal_data_step() stores the result under
occs_<ind>, renders its generated code into the
report, wires the “view data” modal, and (because
select_dataset = TRUE) keeps the dataset dropdown in sync
as other steps come and go. A later step can then consume this step’s
output by calling get_int_data().
A step that produces a figure instead of a dataset uses
add_shinypal_plot_step() with a [shinymeta::metaRender2()]
render. Point output_prefix at the
plotOutput() id used in the report.
plot_render <- shinymeta::metaRender2(shiny::renderPlot, {
df <- get_int_data(input[[paste0("dataset_", ind)]])()
shinymeta::metaExpr(plot(df))
})
add_shinypal_plot_step(
ind,
plot = plot_render,
fun_workflow = plot_workflow,
fun_report = function(ind) shiny::plotOutput(paste0("plot_", ind)),
output_prefix = "plot_"
)shinypal_setup(download_template = ...) points at a
Quarto/R Markdown template with two knitr-expandable variables,
{{libraries}} and {{code}}, which shinypal
fills with the assembled library() calls and step code.
Files a user uploads through a [shiny::fileInput()] can be bundled into
the download by registering them with file_observe().
shinypal apps can be exported with shinylive so they
run entirely in the browser via webR. Because some things behave
differently there (e.g., no system zip, and packages
without a WebAssembly build can’t load), use is_shinylive()
to gate that behavior: