---
title: "Defaults and Hiding Controls"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Defaults and Hiding Controls}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
    collapse = TRUE,
    comment = "#>",
    eval = FALSE
)
```

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.

```{r defaults-basic}
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:

```{r defaults-reactive}
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:

```{r defaults-app}
plotthis_BoxPlotApp(
    defaults = list(
        x.by     = "Species",
        y.by     = "Sepal.Length",
        pt.size  = 0
    )
)
```

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

```{r defaults-createModuleApp}
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.

```{r hide-inputs}
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:

```{r hide-fixed}
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.

```{r hide-tabs}
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:

```{r combined}
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:

```{r hide-in-app}
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`:

```{r dynamic-hide}
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:

```{r dynamic-hide-reflow}
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)
}
```
