ui <- function(request) {
shiny::tagList(
shiny::actionButton("show", "Click"),
shiny::uiOutput("message")
)
}
server <- function(input, output, session) {
shiny::observeEvent(input$show, {
shiny::showNotification("You pushed that.")
output$message <- shiny::renderUI({
shiny::tags$h1("You have clicked me!")
})
})
}
shiny::shinyApp(ui, server)9 Shiny
Shiny is a framework for building web applications in R. It connects a browser-based UI to server-side R code through reactive programming.
9.1 How Shiny works
A Shiny app has two parts:
- UI function: Defines the HTML elements rendered in the browser
- Server function: Contains the R logic that responds to user interactions
Before the click, the browser has:
<button id="show" class="btn btn-default action-button">Click</button>
<div id="message" class="shiny-html-output"></div>After the click, the server populates the message div:
<div id="message" class="shiny-html-output">
<h1>You have clicked me!</h1>
</div>The key insight: Shiny uses id attributes to create a communication channel between the browser and the R server. The id on a UI element corresponds to input$id (for inputs) or output$id (for outputs) on the server side.
9.2 Modules
Modules encapsulate a piece of UI + server logic into a reusable unit. Every ndexr component is a module.
ui_button <- function(id = "button") {
ns <- shiny::NS(id)
shiny::tagList(
shiny::actionButton(ns("show"), "Click"),
shiny::uiOutput(ns("message"))
)
}
server_button <- function(id = "button") {
shiny::moduleServer(id, function(input, output, session) {
shiny::observeEvent(input$show, {
output$message <- shiny::renderUI({
shiny::tags$h1("You have clicked me!")
})
})
})
}
# Use in your app:
ui <- function(request) { ui_button("my_button") }
server <- function(input, output, session) { server_button("my_button") }
shiny::shinyApp(ui, server)The NS() function namespaces all IDs. The button’s id becomes my_button-show in the browser, not just show. This prevents collisions when you use multiple instances of the same module.
On the server side, moduleServer handles the namespacing automatically — you write input$show, not input$my_button-show.
9.3 ID and class conventions
In ndexr:
idis for action — creating a channel between UI and serverclassis for styling — Bootstrap CSS classes control appearance
IDs must be unique. Classes can be shared across elements. Shiny also uses classes internally for reactivity (e.g., action-button on actionButton).
9.4 Nested modules
Modules can contain other modules. This is how ndexr builds complex interfaces from simple pieces:
ui_table <- function(id = "table") {
ns <- shiny::NS(id)
shiny::tags$table(
class = "table",
shiny::tags$thead(
shiny::tags$tr(
lapply(colnames(datasets::mtcars), function(x) {
shiny::tags$th(scope = "col", x)
})
)
),
shiny::uiOutput(ns("rows"), container = shiny::tags$tbody)
)
}
server_table <- function(id = "table") {
shiny::moduleServer(id, function(input, output, session) {
ns <- session$ns
output$rows <- shiny::renderUI({
lapply(split(datasets::mtcars, seq_len(nrow(datasets::mtcars))), function(row) {
row_id <- paste0("row_", rownames(row))
server_row(row_id, row)
ui_row(ns(row_id))
})
})
})
}Each row is its own module with its own namespace. The table module creates and manages them dynamically.
9.5 The ndexr project template
The default Shiny project for ndexr servers lives in ~/public/services/console/:
~/public/
├── docker-compose.yaml
├── Makefile
└── services/
└── console/
├── app.r
├── Dockerfile
├── renv.lock
└── src/
└── (your application code)
docker-compose.yamldefines the container and port mappingMakefileprovides build/scale/stop commandsDockerfilespecifies the system and R dependenciesrenv.lockpins R package versions for reproducibilitysrc/contains your Shiny application
Run make up profile=ndexrio scale=10 from ~/public/ to build and launch.