The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
The modules in VizModules are designed to be composed and extended. You can build higher-level modules that add custom logic such as data filtering, transformations, or additional UI controls while reusing the full functionality of the base modules.
This vignette demonstrates how to create a custom module by building
on top of the scatterPlot module.
When building a custom module, you need to handle Shiny’s namespacing correctly. The key insight is:
NS(id).id, not a namespaced version.moduleServer()
block.moduleServer() block to avoid
double-namespacing issues.Let’s build a custom module that adds a simple filtering checkbox to
the scatterPlot module.
library(VizModules)
minimalModuleUI <- function(id) {
ns <- NS(id)
tagList(
h4("Minimal Module Controls"),
# Custom input - uses the module's namespace
checkboxInput(ns("filter_setosa"), "Start with Setosa Only", value = FALSE),
hr(),
# Base module UI - pass the bare 'id', not ns(id)
dittoViz_scatterPlotInputsUI(id, iris)
)
}
minimalModuleOutput <- function(id) {
# Simply delegate to the base module's output UI
dittoViz_scatterPlotOutputUI(id)
}Notice that checkboxInput() uses
ns("filter_setosa") to namespace the custom input, while
dittoViz_scatterPlotInputsUI() receives the bare
id. This ensures the base module creates its inputs in the
correct namespace.
minimalModuleServer <- function(id, data_reactive) {
# Step 1: Process data inside a moduleServer block
# This gives us access to inputs namespaced to 'id' (our module's inputs)
filtered_data <- moduleServer(id, function(input, output, session) {
reactive({
req(data_reactive())
df <- data_reactive()
# Input specific to this custom module
if (isTRUE(input$filter_setosa)) {
if ("Species" %in% names(df)) {
df <- df[df$Species == "setosa", ]
}
}
df
})
})
# Step 2: Call the base module server OUTSIDE the moduleServer block
# This is critical! If we called this inside the moduleServer above,
# dittoViz_scatterPlotServer would look for inputs at id-id-inputName instead of id-inputName
dittoViz_scatterPlotServer(id, filtered_data)
}Why this pattern?
moduleServer(id, ...) gives us access to
input$filter_setosa, which is namespaced to our wrapper’s
id.dittoViz_scatterPlotServer(id, filtered_data)
outside the moduleServer() closure, the base
module attaches to the same namespace as our UI, not a nested one.dittoViz_scatterPlotServer() inside the
moduleServer() block, it would create nested namespaces
like id-id-x_axis, which wouldn’t match the actual input
IDs in the UI.ui <- fluidPage(
titlePanel("Minimal Module Example"),
sidebarLayout(
sidebarPanel(
minimalModuleUI("demo")
),
mainPanel(
minimalModuleOutput("demo")
)
)
)
server <- function(input, output, session) {
# Pass a reactive data source to the module
minimalModuleServer("demo", reactive({
iris
}))
}
shinyApp(ui, server)If your module pre-sets certain parameters, you can hide those inputs from the user to keep them from being changed:
Keep wrapper logic focused: Each wrapper should add a cohesive set of related functionality.
Document the data requirements: If your wrapper expects certain columns or data types, document this clearly.
Use reactive expressions: Use reactive data inputs.
Test the namespace: If inputs aren’t working, check that you’re handling namespaces correctly. A common symptom of namespace issues is that inputs seem to have no effect.
Consider composability: Design your wrappers so they could potentially be wrapped by even higher-level modules.
dittoViz_scatterPlotInputsUI,
dittoViz_scatterPlotOutputUI,
dittoViz_scatterPlotServer) are documented in the package
reference.These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.