---
title: "VizModules Custom Shiny Inputs"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{VizModules Custom Shiny Inputs}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

VizModules ships two custom Shiny inputs that go beyond standard `shiny::*Input` widgets: **multiColorPicker** and **multiDynamicInput**. `multiColorPicker` maps colors to the discrete levels of a variable using palettes or per-group hex pickers, while `multiDynamicInput` is a general-purpose widget that lets users dynamically add and remove rows of heterogeneous inputs.

# multiColorPicker

`multiColorPicker` assigns a color to each level of a discrete variable. Users can apply a named palette to every group at once, or fine-tune individual groups with a native color picker and an editable hex field. The value reported to `input[[inputId]]` is a named character vector of hex colors keyed by group. It is used throughout the package wherever a plot maps a discrete variable to color for more granular control than just a palette selection.

## Overview

The widget renders a palette selector with **Apply** and **Reset** buttons, a preview row of swatches, and one row per group. Selecting a palette and clicking Apply recolors every group in order; the per-group color picker and hex input let users override any single group afterwards.

## Basic Usage

```{r}
library(shiny)
library(VizModules)

ui <- fluidPage(
    br(),
    splitLayout(
        multiColorPicker(
            "dept_cols",
            label = "Plot colors",
            groups = c("Engineering", "Finance", "HR",
                       "Marketing", "Operations", "Sales"),
            selected_palette = "dittoColors"
        ),
        verbatimTextOutput("value")
    )
)

server <- function(input, output, session) {
    output$value <- renderPrint(input$dept_cols)
}

shinyApp(ui, server)
```

```{r, echo = FALSE, eval = TRUE, out.width = "50%"}
knitr::include_graphics("../man/figures/multiColorPicker.png")
```

## Key Arguments

| Argument           | Description                                                                             |
| ------------------ | --------------------------------------------------------------------------------------- |
| `inputId`          | Shiny input id. The value is accessed via `input[[inputId]]`.                           |
| `label`            | Optional label displayed above the control.                                            |
| `groups`           | Character or factor vector of group names — one color row is created per unique value.  |
| `palette_options`  | Named list of palettes (each a character vector of colors). Defaults to `default_palettes()`. |
| `selected_palette` | Name of the palette to preselect.                                                      |
| `colors`           | Optional named vector of starting colors, matched to `groups` by name.                 |
| `show_text`        | If `TRUE` (default), show editable hex text inputs beside the color pickers.           |
| `compact`          | If `TRUE`, render a tighter layout with smaller controls.                              |
| `panel`            | If `FALSE`, remove the surrounding panel/well styling.                                 |

## Reading the Value

The value in `input[[inputId]]` is a named character vector of hex colors keyed by group:

```{r}
# With groups = c("Engineering", "Finance", "HR", ...)
input$dept_cols
#> c(
#>   Engineering = "#E69F00",
#>   Finance     = "#56B4E9",
#>   HR          = "#009E73",
#>   Marketing   = "#F0E442",
#>   Operations  = "#0072B2",
#>   Sales       = "#D55E00"
#> )
```

## Updating from the Server

Use `updateMultiColorPicker()` to set specific colors, apply a palette by name, or reset the widget to its initial state:

```{r}
# Set specific group colors (only named groups change)
updateMultiColorPicker(session, "dept_cols",
    colors = c(Engineering = "#1B9E77", Finance = "#D95F02"))

# Apply a named palette to all groups
updateMultiColorPicker(session, "dept_cols", palette = "ggplot2")

# Reset colors and palette back to the initial state
updateMultiColorPicker(session, "dept_cols", reset = TRUE)
```

Exactly one of `colors`, `palette`, or `reset` should be supplied per call; `reset` takes precedence, followed by `palette`, then `colors`.

## Summary

- `multiColorPicker()` — place in your UI, one color row per group
- `input[[inputId]]` — read the named character vector of hex colors in your server
- `updateMultiColorPicker()` — set colors, apply a palette, or reset from the server

# multiDynamicInput

## Overview

`multiDynamicInput` renders a "+ Add" button and a container. Each time the user clicks Add, a new row appears with whatever fields you define. An "×" button on each row deletes it. The collected value is reported to `input[[inputId]]` as a named list of rows.

Unlike `insertUI`/`removeUI`, rows are managed entirely client-side via DOM template cloning — no server round-trips for add/delete.

## Basic Usage

```{r}
library(shiny)
library(VizModules)

ui <- fluidPage(
    br(),
    splitLayout(
        multiDynamicInput(
            "layers",
            label = "Layers",
            row_spec = list(
                colour = list(
                    type = "colour",
                    args = list(value = "#000000")
                ),
                size   = list(
                    type = "numeric",
                    args = list(value = 2, min = 0.5, max = 10)
                ),
                style  = list(type = "select",
                args = list(choices = c("solid", "dashed", "dotted")))
            )
        ),
        verbatimTextOutput("value")
    )
)

server <- function(input, output, session) {
    output$value <- renderPrint(input$layers)
}

shinyApp(ui, server)
```

```{r, echo = FALSE, eval = TRUE, out.width = "100%"}
knitr::include_graphics("../man/figures/multiDynamicInput.png")
```

## Key Arguments

| Argument      | Description                                                                       |
| ------------- | --------------------------------------------------------------------------------- |
| `inputId`     | Shiny input id. The value is accessed via `input[[inputId]]`.                     |
| `label`       | Label shown next to the + Add button. Also determines row naming (lowercased).    |
| `row_spec`    | Named list defining one row's fields. Each entry has `type` (or `fn`) and `args`. |
| `elements`    | Optional list of pre-filled rows to show on startup.                              |
| `max_per_row` | Number of fields per visual line before wrapping (default 4).                     |
| `panel`       | If `FALSE`, removes the border/background styling.                                |

## Defining Fields with `row_spec`

Each field in `row_spec` is a named list with:

- **`type`** — a shorthand alias: `"select"`, `"text"`, `"numeric"`, `"slider"`, `"checkbox"`, `"colour"` / `"color"`.
- **`fn`** — alternatively, any input constructor function (e.g. `shiny::dateInput`). Use this for inputs without a built-in alias.
- **`args`** — a list of arguments passed to the constructor (everything except `inputId`, which is auto-generated per row).

```{r}
# Using type aliases
row_spec = list(
    name = list(type = "text", args = list(placeholder = "Enter name")),
    age  = list(type = "numeric", args = list(value = 25, min = 0, max = 120))
)

# Using fn for a custom input
row_spec = list(
    date = list(fn = shiny::dateInput, args = list(label = "Date", value = Sys.Date()))
)
```

## Pre-filled Rows with `elements`

To show rows on startup with specific values:

```{r}
multiDynamicInput(
    "models",
    label = "Models",
    row_spec = list(
        model_type = list(type = "select", args = list(choices = c("lm", "glm"))),
        formula    = list(type = "text", args = list(placeholder = "y ~ x"))
    ),
    elements = list(
        models1 = list(model_type = "lm", formula = "revenue ~ units"),
        models2 = list(model_type = "glm", formula = "revenue ~ poly(units, 2)")
    )
)
```

The names (`models1`, `models2`) don't matter for input — they're auto-generated from the label at runtime. The values inside each element must match the `row_spec` field names.

## Reading the Value

The value in `input[[inputId]]` is a named list of rows:

```{r}
# With label = "Models" and two rows filled in:
input$models
#> list(
#>   models1 = list(model_type = "lm", formula = "revenue ~ units"),
#>   models2 = list(model_type = "glm", formula = "revenue ~ poly(units, 2)")
#> )
```

Row names are derived from the `label` argument (lowercased) plus an index: `models1`, `models2`, etc.

## Updating from the Server

Use `updateMultiDynamicInput()` to replace all rows or clear them:

```{r}
# Replace all rows
updateMultiDynamicInput(session, "models", elements = list(
    models1 = list(model_type = "lm", formula = "y ~ x")
))

# Clear all rows
updateMultiDynamicInput(session, "models", clear = TRUE)
```

## Backend-Specific Fields

When used with the model backend system (see `vignette("custom-model-lines")`), fields can be tagged with a `backend` attribute. These fields are hidden by default and only appear when the matching model type is selected:

```{r}
row_spec = list(
    model_type = list(type = "select", args = list(choices = c("lm", "drm"))),
    formula    = list(type = "text", args = list(placeholder = "y ~ x")),
    # Only visible when model_type == "drm"
    drc_fct    = list(type = "select", backend = "drm",
                      args = list(choices = c("LL.4", "LL.3")))
)
```

In practice you don't build this manually — `build_model_row_spec()` does it for you by collecting `fields` from all registered backends.

## Summary

- `multiDynamicInput()` — place in your UI
- `input[[inputId]]` — read the named list of rows in your server
- `updateMultiDynamicInput()` — set or clear rows from the server
- `build_model_row_spec()` — auto-build a row_spec from registered model backends
