---
title: "Using glasstabs in Shiny modules"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Using glasstabs in Shiny modules}
  %\VignetteEncoding{UTF-8}
  %\VignetteEngine{knitr::rmarkdown}
editor_options:
  markdown:
    wrap: 72
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(eval = FALSE)
library(shiny)
library(glasstabs)
```

Modules are how larger Shiny apps stay maintainable, and glasstabs
widgets follow the same namespacing rule as every other input: apply
`NS(id)` to the widget IDs in the module UI, and pass the module's
session plus bare input IDs everywhere else. The one mistake to avoid
is namespacing twice — never pass an already-namespaced ID to an
update function or a server helper.

## The rule, stated once

```{r run-ns-check}
ns <- shiny::NS("filters")
stopifnot(ns("region") == "filters-region")
stopifnot(ns("status") == "filters-status")
```

`NS("filters")("region")` is `"filters-region"`. The module UI
creates widgets under those namespaced IDs; the module server talks
about `"region"` and `"status"` with its own session, and Shiny
resolves them to the same inputs.

## A complete filter module

Both select controls, their update functions, and a value shared with
the parent:

```{r filter-module}
filterUI <- function(id) {
  ns <- NS(id)

  tagList(
    glassSelect(
      ns("region"),
      c(North = "north", South = "south"),
      label = "Region",
      selected = "north"
    ),

    glassMultiSelect(
      ns("status"),
      c(Active = "active", Closed = "closed"),
      label = "Status",
      selected = "active",
      show_style_switcher = FALSE
    ),

    actionButton(ns("clear"), "Clear filters")
  )
}

filterServer <- function(id) {
  moduleServer(id, function(input, output, session) {

    observeEvent(input$clear, {
      updateGlassSelect(
        session, "region",
        selected = character(0)
      )

      updateGlassMultiSelect(
        session, "status",
        selected = character(0)
      )
    })

    reactive({
      list(
        region = input$region,
        status = input$status
      )
    })
  })
}

ui <- fluidPage(
  useGlassTabs(),
  filterUI("filters"),
  verbatimTextOutput("current")
)

server <- function(input, output, session) {
  filters <- filterServer("filters")

  output$current <- renderPrint(filters())
}

if (interactive()) shinyApp(ui, server)
```

Three things to notice. First, the update calls take the module
session and the bare IDs (`"region"`, `"status"`): passing
`ns("region")` here would address a nonexistent
`"filters-filters-region"` input. Second, `character(0)` clears both
widgets — for the single-select it means "no selection" (the value
becomes `NULL`), for the multi-select it means an empty selection.
Third, the parent never touches `input$filters-region` directly; it
reads the module's returned reactive, which keeps the module's inputs
an implementation detail.

## Server-backed dropdowns in a module

The same rule covers `glassMultiSelectServer()` and
`glassSelectServer()`: register them inside `moduleServer` with the
bare ID and the module session, and the helper searches the module's
input:

```{r filter-module-server}
filterServerWithSearch <- function(id, choices) {
  moduleServer(id, function(input, output, session) {
    glassMultiSelectServer("status", choices, session = session)

    reactive({
      input$status
    })
  })
}
```

The widget itself still takes the namespaced ID in the module UI
(`glassMultiSelect(ns("status"), ..., server = TRUE)`); only the
server side uses the bare ID. The same holds for controls created by
`renderUI()` inside a module: register the server helper once in the
module server for that input ID, and the newly rendered control
receives search results through Shiny.

## Two modules, one filter

Because every ID is namespaced at the UI boundary, the same module can
be instantiated twice with no collision:

```{r two-instances}
ui <- fluidPage(
  useGlassTabs(),
  filterUI("left"),
  filterUI("right")
)

server <- function(input, output, session) {
  left <- filterServer("left")
  right <- filterServer("right")
}
```

Each instance keeps its own selection, its own server registration,
and its own update scope. If an update in one module ever seems to
reach the other, the cause is almost always a hardcoded full ID
somewhere in the UI — check that every widget ID goes through
`ns()`.
