I have a table in a flexdashboard whose number of columns can change. I can compute the alignment of the columns on the fly (default alignment treats $23.45
as a character vector and thus left aligns the value though it is a number and should be right aligned). The problem is I can't pass this alignment back to renderTable
as a value to align
because it's a reactive value.
How can I pass the reactive alignment back to the renderTable
function's align
argument? (or an alternative that let's me render a table with a reactive alignment)
MWE
---
title: "test"
output: flexdashboard::flex_dashboard
runtime: shiny
---
```{r}
library(flexdashboard)
library(shiny)
```
Inputs {.sidebar}
-------------------------------------
```{r}
selectInput(
"ncols",
label = "How many columns?",
choices = 1:5,
selected = 5
)
```
Column
-------------------------------------
### Test
```{r}
nc <- reactive({input$ncols})
aln <- reactive({substring('lllrr', 1, nc())})
renderTable({
x <- CO2[1:5, seq_len(nc()), drop = FALSE]
x[, 1] <- as.character(x[, 1])
x[3, 1] <- '<b>Mc1</b>'
x
}, align = aln(), sanitize.text.function = function(x) x)
```
Results in:
Warning: Error in .getReactiveEnvironment()$currentContext: Operation not
allowed without an active reactive context. (You tried to do something
that can only be done from inside a reactive expression or observer.)
Try wrapping aln()
in renderText(...)
i.e.
renderTable({
x <- CO2[1:5, seq_len(nc()), drop = FALSE]
x[, 1] <- as.character(x[, 1])
x[3, 1] <- '<b>Mc1</b>'
x
}, align = renderText(aln()),
sanitize.text.function = function(x) x)
First things first, the specific error that occurred is quite correct. The function renderTable
can only hold reactive expressions in its first argument renderTable({ ... }, ...)
. All other things must be plain objects. So, putting a reactive value aln()
there really was putting a reactive
in a regular environment. Or, seen from the point of view of the reactive
itself, it was evaluated without an active reactive context like it said in the error message.
A workaround to make those things reactive as well, is a wrapper around the renderTable
, namely renderUI
. Inside the renderUI
we can reactively shape the whole table-rendering which includes the align. (Solution partly adapted from here.)
Here's what you have to replace the renderTable
for:
renderUI({
output$table <- renderTable({
x <- CO2[1:5, seq_len(isolate(nc())), drop = FALSE]
x[, 1] <- as.character(x[, 1])
x[3, 1] <- '<b>Mc1</b>'
x
}, align = aln(), sanitize.text.function = function(x) x)
tableOutput("table")
})
Quick explanation: Using the output
variable is like you would do it in a regular shiny environment, if not in markdown. What will be rendered by renderUI
is then a reference to the renderTable
which we will re-create from scratch every time.
Note: I used an isolate
inside the renderTable
. Here is why: renderTable
is reactive and would update if nc()
changes. But that can happen before the surrounding renderUI
reacts to the change. You see, in this intermediate step, renderTable
tries to plot a data.frame
with a different column count, but the align
is still the one from before. An error is thrown, because they don't match. Isolating the inner nc()
stops the inner renderTable
from updating. But that's okay, because the outer renderUI
does this anyways once it is it's turn to update.