Reactive argument to render functions

2019-05-15 22:07发布

问题:

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.)

回答1:

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)


回答2:

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.