I want to create an app that has the following flow:
- The user selects some data groups
- Those groups become dynamic tabs, with each of those tabs containing a subset editable
DT
with the respective group - Each tab contains an additional reactive
DT
that reacts to changes in editable DataTable created in #2 (in the example below, simply multiplying numeric columns by two)
Here is an example that does #1 and #2. However, #3 does not work because the information that is normally exposed with an editable DT
does not appear in my input
, likely due to some scoping or order of rendering issue.
library(shiny)
library(DT)
library(dplyr)
ui <- fluidPage(
sidebarLayout(
sidebarPanel =
sidebarPanel(
selectInput("cars", "Pick a vehicle", rownames(mtcars), multiple = T),
actionButton("add", "Create Tabs")
),
mainPanel =
mainPanel(
tabsetPanel(
id = "panel"
)
)
)
)
server <- function(input, output, session) {
df <- tibble::rownames_to_column(mtcars, "car")
data <- reactiveVal()
observe({
req(df, input$cars)
# Step 1) split data by user input groups
df |>
filter(car %in% input$cars) |>
split(~ car) |>
data()
})
observeEvent(input$add, {
req(input$cars, data())
# Step 2) Editable DT with respective group
# Creates output$<car name>
lapply(input$cars, \(x) { output[[x]] <- renderDT(data()[[x]],
rownames = F,
editable = "cell",
selection = "none")
})
# Step 3) Reactive DT that responds to user changes
# Creates output$<car name>tbl
lapply(input$cars, \(x) { output[[paste0(x, "tbl")]] <- renderDT({
mutate(data()[[x]], across(where(is.numeric), ~ . * 2))
})
})
# insert dynamic tabs with data
lapply(input$cars, \(x) {
insertTab("panel", tabPanel(x,
DTOutput(x), # access output$<car name>
br(),
DTOutput(paste0(x, "tbl")) # access output$<car name>
)
)
})
# input does not contain input$<vehicle selection>_cell_edit
print(names(input)) # [1] "cars" "add" "panel"
})
}
shinyApp(ui, server)
You can see in this example that upon changing mpg
to 10, the second table does not reactively show 10*2 = 20.
Normally when you create a DT
on the server side like output$table <- renderDT(iris , editable = "cell")
you gain access to information stored in the input
object (see 2.2 DataTables Information). One of those being input$table_cell_edit
(input$table_
bc the assignment is output$table <-
) that you can use to create a reactive event.
Since I need to do this dynamically, I cannot hardcode assignments in this manner. lapply
does work to the extent that I can reference dynamically created items (see DTOutput(...)
). However, you can see from the print
statement that the DataTable information is not created to capture user interactions when output
assignment is done via lapply
.
This SO question had a similar issue, but no response. Same with this DT GitHub issue that also was closed due to no response.
Question
So, my problem is how do I dynamically create editable DT
in my output
object so that I can access input
object information about edits to create a chain of reactions?
Answer
In any response it would be great to see code that accomplishes 1-3 above, but also:
- Adjusts the data underlying the first table when the user edits
- Adjusts the data underlying the second table when the user edits the first table
- Provide more detail about why my code does not work (how can I access DataTables
output$<car name>
andoutput$<car name>tbl
, but noinput
information is accessible?)
observeEvent(input[[edit_input_id]]...)
that reacts when the first table is edited and it reacts even though the parentobserveEvent
event expression has not changed. This nested observer is also within anlapply
so does everything within thelapply
rerun upon an edit? Or does justobserveEvent(input[[edit_input_id]]...)
run which in turn causesDT::renderDT
to react and reassign tooutput[[NS(car)("transformed")]]
? – Footloose