# Defaults and Hiding Controls

Two closely related arguments — `defaults` and `hide.inputs`/`hide.tabs` — let you control what users see and what the module starts with. This vignette covers both in depth.

## `defaults`: pre-filling inputs

Pass a named list to the `defaults` argument of any `*InputsUI()` call. Each name is an input ID (matching the underlying plot function argument), and its value is what the control initialises to.

    library(VizModules)

    ui <- fluidPage(
        sidebarLayout(
            sidebarPanel(
                dittoViz_scatterPlotInputsUI(
                    "p", mtcars,
                    defaults = list(
                        x.by        = "wt",
                        y.by        = "mpg",
                        color.by    = "cyl",
                        size        = 3,
                        best.fit    = TRUE
                    )
                )
            ),
            mainPanel(dittoViz_scatterPlotOutputUI("p"))
        )
    )

    server <- function(input, output, session) {
        dittoViz_scatterPlotServer("p", data = reactive(mtcars))
    }

    shinyApp(ui, server)

### Finding valid keys

Defaults keys map directly to the argument names of the underlying plot function. The quickest way to find them is the module’s `*InputsUI()` help page, e.g. `?dittoViz_scatterPlotInputsUI`. The Plot parameters and defaults section lists every wired argument, its UI label, and its built-in default.

For inputs that come from the uniform tab helpers (Axes, Legend, Lines, Plotly), see `?uniform_axes_inputs_ui`, `?uniform_legend_inputs_ui`, `?uniform_lines_inputs_ui`, and `?uniform_plotly_inputs_ui` respectively.

### Validation

`get_default()` (used internally by every module) accepts an optional `validator` predicate. If the value you supply fails validation — e.g. passing a string where a logical is expected — the module silently falls back to its built-in default rather than erroring. This means a typo in a key is silent; double-check key names against the help page if a default appears to have no effect.

### Reactive defaults: driving inputs from app state

Sometimes a parameter needs to follow your app rather than be fixed once. A common case is a colour mapping that should track a column the user has selected elsewhere in the app. For this, any individual entry in `defaults` may be a `reactive()` or a `reactiveVal()` instead of a plain value:

    library(VizModules)

    ui <- fluidPage(
        selectInput("colour_col", "Colour by", choices = c("cyl", "gear", "carb")),
        sidebarLayout(
            sidebarPanel(uiOutput("controls")),
            mainPanel(dittoViz_scatterPlotOutputUI("p"))
        )
    )

    server <- function(input, output, session) {
        plot_defaults <- list(
            x.by = "wt",
            y.by = "mpg",
            color.by = reactive(input$colour_col)
        )

        output$controls <- renderUI({
            dittoViz_scatterPlotInputsUI("p", mtcars, defaults = plot_defaults)
        })

        dittoViz_scatterPlotServer("p", data = reactive(mtcars), defaults = plot_defaults)
    }

    shinyApp(ui, server)

Pass the same list to both `*InputsUI()` and `*Server()`, as you would for static defaults. Because the UI needs access to the reactive, build it inside `renderUI()` (the UI seed is taken with `isolate()`, so this does not cause the controls to re-render whenever the reactive changes).

You get three guarantees:

- The parameter tracks the reactive. Every change flows through to the plot.
- The control stays correct and editable. The Color By selector shows the current column, and the user can still change it.
- The plot renders once per change. The value is resolved server-side, in the same reactive flush as the data, so there is no flicker.

Semantics worth knowing:

- An external change always wins. If the user has picked a different colour column in the module and then changes the app-level selector, the selector’s value replaces their choice.
- Reset restores the reactive’s current value, not the value it held at startup.
- Only `reactive()` and `reactiveVal()` are recognised. A plain function is treated as a literal default value, not as something to call.
- Composite widgets sync cosmetically only. The plot always uses the current value, but a compound control that does not accept a generic value message — `custom.models` in the scatter module is the one to watch — will not re-display it. Reactive defaults are not supported for that input.

#### Why not `update*Input()` from the parent?

The obvious alternative is an `observeEvent()` in the parent calling `updateTextInput(session, "p-main", ...)`. That works, but `update*Input()` is an asynchronous round-trip to the browser, so each change renders the plot twice: once with the stale value, then again when the new value arrives back from the client. Reactive defaults exist to avoid that second render. `dev-notes/reactive-defaults-repro.R` in the package source runs both approaches side by side with render counters if you want to see the difference.

### Passing defaults through `createModuleApp()` and `*App()`

`defaults` is forwarded all the way through the app factory:

    plotthis_BoxPlotApp(
        defaults = list(
            x.by     = "Species",
            y.by     = "Sepal.Length",
            pt.size  = 0
        )
    )

The same `defaults` list is accepted by `createModuleApp()` directly:

    app <- createModuleApp(
        inputs_ui_fn = plotthis_BoxPlotInputsUI,
        output_ui_fn = plotthis_BoxPlotOutputUI,
        server_fn    = plotthis_BoxPlotServer,
        data_list    = list("iris" = iris),
        defaults     = list(x.by = "Species", y.by = "Sepal.Length")
    )
    if (interactive()) runApp(app)

## `hide.inputs`: hiding individual controls

Pass a character vector of input IDs to `hide.inputs` on the server function. Those controls are hidden from the UI while their values are still initialised (from `defaults` if supplied, otherwise the built-in default) and passed to the plot on every render.

    server <- function(input, output, session) {
        dittoViz_scatterPlotServer(
            "p",
            data        = reactive(mtcars),
            hide.inputs = c("shape.by", "plot.order", "opacity")
        )
    }

Hidden inputs reflow: the surrounding controls close the gap rather than leaving an empty space. This is handled automatically by the flexbox grid that `organize_inputs()` creates.

### Use case: app-level fixed parameters

A common pattern is to fix a column mapping or aesthetic so that app users cannot change it, while still applying it to every plot:

    server <- function(input, output, session) {
        dittoViz_scatterPlotServer(
            "p",
            data        = reactive(mtcars),
            defaults    = list(color.by = "cyl"),
            hide.inputs = "color.by"
        )
    }

The colour mapping is always `cyl`, and the control for it never appears.

This composes with reactive defaults: a hidden control whose default is a `reactive()` still drives the plot, because the value is resolved server-side rather than read back from the (now invisible) input. That is the cleanest way to lock a parameter to app state entirely.

## `hide.tabs`: hiding entire tab panels

Modules organise their inputs into named tabs (e.g. `"Data"`, `"Points"`, `"Lines"`, `"Axes"`, `"Legend"`, `"Plotly"`). Pass a character vector of tab names to `hide.tabs` to remove whole groups at once.

    server <- function(input, output, session) {
        dittoViz_scatterPlotServer(
            "p",
            data      = reactive(mtcars),
            hide.tabs = c("Plotly", "Lines", "Trajectory")
        )
    }

All inputs in a hidden tab are still initialised and active — they just aren’t shown. This is useful when you want to keep the defaults for an entire feature group (e.g. plotly export settings) without exposing the controls to users.

### Available tab names

Tab names are module-specific. Inspect the `inputs` list inside the relevant `*InputsUI()` source, or open `?<module>InputsUI` and look for the tab headings described there. Common tabs across most modules include:

Tab | Contents
--- | ---
`"Data"` | Column selectors (x, y, color, split, etc.)
`"Axes"` | Title font, gridlines, tick styling
`"Legend"` | Legend title and text sizes
`"Lines"` | Reference lines (h/v/ablines)
`"Plotly"` | Download format, margins, drawn shape styling
`"Facet"` | Facet rows/columns, scales, subplot spacing

## Combining `defaults`, `hide.inputs`, and `hide.tabs`

These three arguments compose freely. A typical production pattern is to set defaults for everything the user shouldn’t touch, hide the individual controls that need fine-grained locking, and hide any whole tabs that are irrelevant for your use case:

    ui <- fluidPage(
        sidebarLayout(
            sidebarPanel(
                plotthis_ViolinPlotInputsUI(
                    "v", example_rnaseq,
                    defaults = list(
                        x.by     = "condition",
                        y.by     = "expression",
                        color.by = "condition"
                    )
                )
            ),
            mainPanel(plotthis_ViolinPlotOutputUI("v"))
        )
    )

    server <- function(input, output, session) {
        plotthis_ViolinPlotServer(
            "v",
            data        = reactive(example_rnaseq),
            hide.inputs = c("color.by"),
            hide.tabs   = c("Plotly", "Lines")
        )
    }

    shinyApp(ui, server)

Here `color.by` is fixed to `"condition"` and hidden. The entire `"Plotly"` and `"Lines"` tabs are removed because they aren’t relevant to this app.

## Using `hide.inputs` in `createModuleApp()` and `*App()`

Both accept `hide.inputs` and `hide.tabs`, forwarding them to the server if it supports those arguments:

    plotthis_ViolinPlotApp(
        defaults    = list(x.by = "Species", y.by = "Sepal.Length"),
        hide.inputs = "color.by",
        hide.tabs   = "Plotly"
    )

## Hiding inputs dynamically in a custom module

When building a wrapper module (see `vignette("custom-modules", package = "VizModules")`), you can hide and show inputs at runtime in response to other inputs using `shinyjs`:

    library(shinyjs)

    myModuleServer <- function(id, data_reactive) {
        moduleServer(id, function(input, output, session) {
            # Hide the 'size' input whenever a size.by column is chosen
            observe({
                if (nzchar(input$size.by)) {
                    shinyjs::hide(id = "size")
                } else {
                    shinyjs::show(id = "size")
                }
            })
        })

        dittoViz_scatterPlotServer(id, data_reactive)
    }

Note that `shinyjs::hide()` / `shinyjs::show()` act on the input element itself. For the reflow behaviour (no empty gap), use the `hide_input()` / `show_input()` helpers instead, which target the wrapping cell in the flexbox grid:

    myModuleServer <- function(id, data_reactive) {
        moduleServer(id, function(input, output, session) {
            observe({
                if (nzchar(input$size.by)) {
                    VizModules::hide_input(session, "size")
                } else {
                    VizModules::show_input(session, "size")
                }
            })
        })

        dittoViz_scatterPlotServer(id, data_reactive)
    }