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.

Building Custom Modules

Introduction

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.

The Pattern

When building a custom module, you need to handle Shiny’s namespacing correctly. The key insight is:

  1. Your module’s custom inputs need to be namespaced with NS(id).
  2. The base module’s UI and server functions should receive the bare id, not a namespaced version.
  3. Data processing that requires access to your module’s inputs must happen inside a moduleServer() block.
  4. The base module’s server should be called outside that moduleServer() block to avoid double-namespacing issues.

A Minimal Example

Let’s build a custom module that adds a simple filtering checkbox to the scatterPlot module.

The UI

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.

The Server

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?

Putting It Together

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)

Hiding Base Module Inputs

If your module pre-sets certain parameters, you can hide those inputs from the user to keep them from being changed:

focusedModuleUI <- function(id) {
    ns <- NS(id)
    tagList(
        h4("Simplified Scatter Plot"),
        # Hide a few parameters
        dittoViz_scatterPlotInputsUI(id, iris,
            hide.inputs = c("shape.by", "color.by")
        )
    )
}

Best Practices

  1. Keep wrapper logic focused: Each wrapper should add a cohesive set of related functionality.

  2. Document the data requirements: If your wrapper expects certain columns or data types, document this clearly.

  3. Use reactive expressions: Use reactive data inputs.

  4. 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.

  5. Consider composability: Design your wrappers so they could potentially be wrapped by even higher-level modules.

See Also

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.