The code posted at the bottom allows the user to dynamically add and delete tables. You'll see when adding tables that their column headers are automatically sequentially numbered "Col 1", "Col 2", etc. Remaining tables are automatically renumbered after any table is deleted.
How would I capture, in a vector, the nested names of all of these tables ("Col 1", "Col 2", for example)? As shown in the illustration below, a screenshot of the R studio console when running the code and clicking the "Add table" button once. I use print(tables_list) to see the contents of the list. I just don't know how to move around that dynamic list.
I'm having trouble understanding how to subset a dynamic list like this one. I also wonder if I'll be able to reference other values in the list by referring to these element names of Col 1, Col 2, etc.
Illustration:
Code:
library(shiny)
library(rhandsontable)
data1 <- data.frame(row.names = c("A","B","C","Sum"),"Col 1"=c(1,1,0,2),check.names=FALSE)
ui <- fluidPage(
br(),
actionButton("addTbl","Add table"),
br(),br(),
tags$div(id = "placeholder",
tags$div(
style = "display: inline-block",
rHandsontableOutput("hottable1")
)
)
)
server <- function(input, output, session) {
uiTbl <- reactiveValues(div_01_tbl = data1)
rv <- reactiveValues()
observeEvent(input$hottable1, {uiTbl$div_01_tbl <- hot_to_r(input$hottable1)})
observe({
divID <- paste0("div_", sprintf("%02d", input$addTbl+1))
dtID <- paste0(divID, "_DT")
btnID <- paste0(divID, "_rmv")
uiTbl[[paste0(divID,"_tbl")]] <- data1 # captures initial dataframe values
insertUI(
selector = "#placeholder",
ui = tags$div(
id = divID,
style = "display:inline-block;",
rHandsontableOutput(dtID),
actionButton(btnID, "Delete", class = "pull-left btn btn-danger"),
)
)
output[[dtID]] <- renderRHandsontable({
req(uiTbl[[paste0(divID,"_tbl")]])
rhandsontable(uiTbl[[paste0(divID,"_tbl")]], useTypes = TRUE)
})
observeEvent(input[[dtID]], {uiTbl[[paste0(divID,"_tbl")]] <- hot_to_r(input[[dtID]])})
observeEvent(input[[btnID]],{
removeUI(selector = paste0("#", divID))
rv[[divID]] <- NULL
uiTbl[[paste0(divID,"_tbl")]] <- NULL
},
ignoreInit = TRUE,
once = TRUE
)
})
observe({
tables_list <- reactiveValuesToList(uiTbl)
tables_list <- tables_list[order(names(tables_list))]
table_lengths <- lengths(tables_list)
cumsum_table_lengths <- cumsum(table_lengths)[table_lengths != 0L]
for(i in seq_along(cumsum_table_lengths)){
names(uiTbl[[names(cumsum_table_lengths[i])]]) <- paste("Col", cumsum_table_lengths[i])
}
print(tables_list) ### PRINT ###
})
}
shinyApp(ui, server)
We can create the needed vector in the observe() call and pass it to updateSelectizeInput if you need it somewhere else you could pass it to a reactiveVal instead:
library(shiny)
library(rhandsontable)
data1 <- data.frame(row.names = c("A","B","C","Sum"),"Col 1"=c(1,1,0,2),check.names=FALSE)
ui <- fluidPage(
br(),
actionButton("addTbl","Add table"),
br(), br(),
tags$div(id = "placeholder",
tags$div(
style = "display: inline-block",
rHandsontableOutput("hottable1")
)
),
br(),
selectizeInput(inputId = "select_deletion",
label = "Select deletion",
choices = NULL,
selected = NULL,
multiple = TRUE)
)
server <- function(input, output, session) {
uiTbl <- reactiveValues(div_01_tbl = data1)
rv <- reactiveValues()
observeEvent(input$hottable1, {uiTbl$div_01_tbl <- hot_to_r(input$hottable1)})
observe({
divID <- paste0("div_", sprintf("%02d", input$addTbl+1))
dtID <- paste0(divID, "_DT")
btnID <- paste0(divID, "_rmv")
uiTbl[[paste0(divID,"_tbl")]] <- data1 # captures initial dataframe values
insertUI(
selector = "#placeholder",
ui = tags$div(
id = divID,
style = "display:inline-block;",
rHandsontableOutput(dtID),
actionButton(btnID, "Delete", class = "pull-left btn btn-danger"),
)
)
output[[dtID]] <- renderRHandsontable({
req(uiTbl[[paste0(divID,"_tbl")]])
rhandsontable(uiTbl[[paste0(divID,"_tbl")]], useTypes = TRUE)
})
observeEvent(input[[dtID]], {uiTbl[[paste0(divID,"_tbl")]] <- hot_to_r(input[[dtID]])})
observeEvent(input[[btnID]],{
removeUI(selector = paste0("#", divID))
rv[[divID]] <- NULL
uiTbl[[paste0(divID,"_tbl")]] <- NULL
},
ignoreInit = TRUE,
once = TRUE
)
})
observe({
tables_list <- reactiveValuesToList(uiTbl)
tables_list <- tables_list[order(names(tables_list))]
table_lengths <- lengths(tables_list)
cumsum_table_lengths <- cumsum(table_lengths)[table_lengths != 0L]
table_names <- paste("Col", cumsum_table_lengths)
for(i in seq_along(cumsum_table_lengths)){
names(uiTbl[[names(cumsum_table_lengths[i])]]) <- table_names[i]
}
# print(tables_list) ### PRINT ###
# browser() ### use browser() to analyse your observer
freezeReactiveValue(input, "select_deletion")
updateSelectizeInput(session, inputId = "select_deletion", choices = table_names, selected = NULL)
})
}
shinyApp(ui, server)
PS: Please remember to avoid <<- and renderUI wherever you can.
Below is one long-winded way of doing this (also using dplyr for a mutate()), by reverting back to my familiarity with data frames. See the additions of "tmp" objects in the below which replaces the last observe() in the OP. Note that rather than using print() to see the vector as I did in my OP, I send it to the global environment via "tmp.R" for reviewing more complicated input sequences. I hope better solutions to this are posted! I'd like to learn how to easily navigate nested lists. Also, I leave in, but comment-out, object "test1" which is a good way to view the contents of the list neatly organized as a dataframe.
observe({
tables_list <- reactiveValuesToList(uiTbl)
tables_list <- tables_list[order(names(tables_list))]
table_lengths <- lengths(tables_list)
cumsum_table_lengths <- cumsum(table_lengths)[table_lengths != 0L]
for(i in seq_along(cumsum_table_lengths)){
names(uiTbl[[names(cumsum_table_lengths[i])]]) <- paste("Col", cumsum_table_lengths[i])
}
tmp <- data.frame(cumsum_table_lengths)
tmp <- data.frame(origTbl = rownames(tmp), tblCnt = tmp[,1])
tmp <- tmp %>% mutate(tblCode = paste("Col",tblCnt))
tmp <- tmp[,3]
tmp.R <<- tmp
# test1 <- as.data.frame(do.call(cbind, tables_list)) ## this is also useful
})
Related
I'm trying to figure out how to navigate through lists in R (I've mostly worked with vectors to-date in R). The Shiny code posted at the bottom allows the user to dynamically add/delete tables, and I'm trying to capture in a separate list or vector the column names of the added tables. (I'm trying to capture the column names so I can populate a pending selectizeInput() function for choosing which tables to delete). Any recommendations for how to do this?
As you can see in my print() function in the code below, I am only extracting a high-level name, but instead would like to drill deeper to the column names of the individual tables. The following illustrations explain better.
In this illustration the user has added 2 tables, in addition to the first default table:
And in this illustration, the print() function produces the following list names in R Studio Console when I would like to instead only show "Col 1", "Col 2", and "Col 3", in this example of 2 clicks of "Add table":
Code:
library(shiny)
library(rhandsontable)
data1 <- data.frame(row.names = c("A","B","C","Sum"),"Col 1"=c(1,1,0,2),check.names=FALSE)
ui <- fluidPage(
br(),
actionButton("addTbl","Add table"),
br(),br(),
tags$div(id = "placeholder",
tags$div(
style = "display: inline-block",
rHandsontableOutput("hottable1")
)
)
)
server <- function(input, output, session) {
uiTbl <- reactiveValues(div_01_tbl = data1)
rv <- reactiveValues()
observeEvent(input$hottable1, {uiTbl$div_01_tbl <- hot_to_r(input$hottable1)})
observe({
divID <- paste0("div_", sprintf("%02d", input$addTbl+1))
dtID <- paste0(divID, "_DT")
btnID <- paste0(divID, "_rmv")
uiTbl[[paste0(divID,"_tbl")]] <- data1 # captures initial dataframe values
insertUI(
selector = "#placeholder",
ui = tags$div(
id = divID,
style = "display:inline-block;",
rHandsontableOutput(dtID),
actionButton(btnID, "Delete", class = "pull-left btn btn-danger"),
)
)
output[[dtID]] <- renderRHandsontable({
req(uiTbl[[paste0(divID,"_tbl")]])
rhandsontable(uiTbl[[paste0(divID,"_tbl")]], useTypes = TRUE)
})
observeEvent(input[[dtID]], {uiTbl[[paste0(divID,"_tbl")]] <- hot_to_r(input[[dtID]])})
observeEvent(input[[btnID]],{
removeUI(selector = paste0("#", divID))
rv[[divID]] <- NULL
uiTbl[[paste0(divID,"_tbl")]] <- NULL
},
ignoreInit = TRUE,
once = TRUE
)
})
observe({
tables_list <- reactiveValuesToList(uiTbl)
tables_list <- tables_list[order(names(tables_list))]
table_lengths <- lengths(tables_list)
cumsum_table_lengths <- cumsum(table_lengths)[table_lengths != 0L]
for(i in seq_along(cumsum_table_lengths)){
names(uiTbl[[names(cumsum_table_lengths[i])]]) <- paste("Col", cumsum_table_lengths[i])
}
print(names(uiTbl))
})
}
shinyApp(ui, server)
observe({
print(paste0(lapply(
Filter(
\(x)!is.null(x),
reactiveValuesToList(uiTbl)
),
names
), collapse = "; "))
})
Here is one albeit long-winded solution, resorting to my familiarity with data frames. I am sure there are cleaner approaches. See the inclusion of "tmp" objects in the last observe() for the core of my solution; I send the reactive values list into a dataframe and manipulate from there. I also include the selectizeInput() using renderUI() also embedded in the last observe() so you can see the point of my question. Rather than sending the desired vector to R Studio console via print() as in the OP, I send it to global environment as "tmp.R" so I can review more intricate input sequences.
library(dplyr)
library(rhandsontable)
library(shiny)
data1 <- data.frame(row.names = c("A","B","C","Sum"),"Col 1"=c(1,1,0,2),check.names=FALSE)
ui <- fluidPage(
br(),
actionButton("addTbl","Add table"),
br(),br(),
uiOutput("delSection"),
tags$div(id = "placeholder",
tags$div(
style = "display: inline-block",
rHandsontableOutput("hottable1")
)
)
)
server <- function(input, output, session) {
uiTbl <- reactiveValues(div_01_tbl = data1)
rv <- reactiveValues()
observeEvent(input$hottable1, {uiTbl$div_01_tbl <- hot_to_r(input$hottable1)})
observe({
divID <- paste0("div_", sprintf("%02d", input$addTbl+1))
dtID <- paste0(divID, "_DT")
btnID <- paste0(divID, "_rmv")
uiTbl[[paste0(divID,"_tbl")]] <- data1 # captures initial dataframe values
insertUI(
selector = "#placeholder",
ui = tags$div(
id = divID,
style = "display:inline-block;",
rHandsontableOutput(dtID),
actionButton(btnID, "Delete", class = "pull-left btn btn-danger"),
)
)
output[[dtID]] <- renderRHandsontable({
req(uiTbl[[paste0(divID,"_tbl")]])
rhandsontable(uiTbl[[paste0(divID,"_tbl")]], useTypes = TRUE)
})
observeEvent(input[[dtID]], {uiTbl[[paste0(divID,"_tbl")]] <- hot_to_r(input[[dtID]])})
observeEvent(input[[btnID]],{
removeUI(selector = paste0("#", divID))
rv[[divID]] <- NULL
uiTbl[[paste0(divID,"_tbl")]] <- NULL
},
ignoreInit = TRUE,
once = TRUE
)
})
observe({
tables_list <- reactiveValuesToList(uiTbl)
tables_list <- tables_list[order(names(tables_list))]
table_lengths <- lengths(tables_list)
cumsum_table_lengths <- cumsum(table_lengths)[table_lengths != 0L]
for(i in seq_along(cumsum_table_lengths)){
names(uiTbl[[names(cumsum_table_lengths[i])]]) <- paste("Col", cumsum_table_lengths[i])
}
tmp <- data.frame(cumsum_table_lengths)
tmp <- data.frame(origTbl = rownames(tmp), tblCnt = tmp[,1])
tmp <- tmp %>% mutate(tblCode = paste("Col",tblCnt))
tmp.R <<- tmp
output$delSection <-
renderUI(
tagList(
selectizeInput(
'delSelector',
'Select table for deletion:',
choices = tmp[,3],
multiple = FALSE,
options = list(placeholder = 'Choose table')
),
p(actionButton('delTbl', 'Delete'))
)
)
})
}
shinyApp(ui, server)
In the below code, I'm trying to understand what the input[[...]] in the two commented observeEvent() functions do. Could someone please explain?
I'm trying to adapt this code so the function of the individual delete action buttons rendered under each added table can be consolidated into a single selectInput() or selectizeInput() box, showing all tables available for deletion, and then I can remove these individual table delete buttons. I need to understand this input[[...]] part in the below code first, so perhaps I can do the equivalent with selectInput() or selectizeInput(). I've only used input$... before in referring to action buttons or other user input widgets for an observeEvent().
Code:
library(shiny)
library(rhandsontable)
data1 <- data.frame(row.names = c("A","B","C","Sum"),"Col 1"=c(1,1,0,2),check.names=FALSE)
ui <- fluidPage(
br(),
actionButton("addTbl","Add table"),
br(),br(),
tags$div(id = "placeholder",
tags$div(
style = "display: inline-block",
rHandsontableOutput("hottable1")
)
)
)
server <- function(input, output, session) {
uiTbl <- reactiveValues(div_01_tbl = data1) # mod
rv <- reactiveValues()
observeEvent(input$hottable1, {uiTbl$div_01_tbl <- hot_to_r(input$hottable1)}) # mod
output$hottable1 <- renderRHandsontable({rhandsontable(uiTbl$div_01_tbl, useTypes = TRUE)}) # mod
observeEvent(input$addTbl, {
divID <- paste0("div_", if(input$addTbl+1 < 10){"0"},input$addTbl+1) # mod
dtID <- paste0(divID, "_DT")
btnID <- paste0(divID, "_rmv")
uiTbl[[paste0(divID,"_tbl")]] <- data1 # captures initial dataframe values
insertUI(
selector = "#placeholder",
ui = tags$div(
id = divID,
style = "display:inline-block;",
rHandsontableOutput(dtID),
actionButton(btnID, "Delete", class = "pull-left btn btn-danger"),
)
)
output[[dtID]] <- renderRHandsontable({
req(uiTbl[[paste0(divID,"_tbl")]])
rhandsontable(uiTbl[[paste0(divID,"_tbl")]], useTypes = TRUE)
})
# print(btnID)
# what does input[[dtID]] below do?
observeEvent(input[[dtID]], {uiTbl[[paste0(divID,"_tbl")]] <- hot_to_r(input[[dtID]])})
# what does input[[btnID]] below do?
observeEvent(input[[btnID]],{
removeUI(selector = paste0("#", divID))
rv[[divID]] <- NULL
uiTbl[[paste0(divID,"_tbl")]] <- NULL
},
ignoreInit = TRUE,
once = TRUE
)
})
observe({
tables_list <- reactiveValuesToList(uiTbl)
tables_list <- tables_list[order(names(tables_list))]
table_lengths <- lengths(tables_list)
cumsum_table_lengths <- cumsum(table_lengths)[table_lengths != 0L]
for(i in seq_along(cumsum_table_lengths)){
names(uiTbl[[names(cumsum_table_lengths[i])]]) <- paste("Col", cumsum_table_lengths[i])
}
})
}
shinyApp(ui, server)
The code at the bottom of this post works as intended, using observeEvent(input$choices...) in the server section. The use of input$choices is a simplification for sake of example ease. In the fuller code this excerpt derives from, the equivalent of "choices" is molded by many different inputs (call it a "floating reactive"), and unless I misunderstand observeEvent(), it won't be feasible to use observeEvent() in the fuller code because I would have to list the myriad inputs that can alter it. So, is there a way to genericize this code where it instantly captures any change to "choices" (again, "choices" is a simplified analogy for my more complex floating reactive) and outputs it to the 2nd row of the table, including added rows?
Also in the below image, I show how "choices" is a always parachuted into the 2nd position of the dataframe in all circumstances (maybe there's a simpler way to do this too):
Code:
library(rhandsontable)
library(shiny)
mydata <- data.frame('Series 1' = c(1,1,0,1), check.names = FALSE)
rownames(mydata) <- c('Term A','Floating reactive','Term C','Term D')
ui <- fluidPage(br(),
useShinyjs(),
uiOutput("choices"),br(),
rHandsontableOutput('hottable'),br(),
fluidRow(
column(1,actionButton("addSeries", "Add",width = '70px')),
column(3,hidden(uiOutput("delSeries2")))
)
)
server <- function(input, output) {
uiTable <- reactiveVal(mydata)
observeEvent(input$hottable, {uiTable(hot_to_r(input$hottable))})
output$hottable <- renderRHandsontable({
rhandsontable(uiTable(),rowHeaderWidth = 100, useTypes = TRUE)
})
observeEvent(input$choices,{
tmpTable <- uiTable()
tmpTable[2,]<- as.numeric(input$choices)
uiTable(tmpTable)
})
output$choices <-
renderUI({
selectInput(
"choices",
label = "User selects value to reflect in row 2 of table below:",
choices = c(1,2,3)
)
})
observeEvent(input$addSeries, {
newCol <- data.frame(c(1,1,0,1))
newCol[2,] <- as.numeric(input$choices)
names(newCol) <- paste("Series", ncol(hot_to_r(input$hottable)) + 1)
uiTable(cbind(uiTable(), newCol))
})
output$delSeries2 <-
renderUI(
selectInput(
"delSeries3",
label = NULL,
choices = colnames(hot_to_r(input$hottable))
)
)
}
shinyApp(ui,server)
Not sure if I get the point here, but you might want to use observe instead of observeEvent to avoid managing the reactive dependencies (eventExpr) yourself:
library(rhandsontable)
library(shiny)
library(shinyjs)
mydata <- data.frame('Series 1' = c(1,1,0,1), check.names = FALSE)
rownames(mydata) <- c('Term A','Floating reactive','Term C','Term D')
ui <- fluidPage(br(),
useShinyjs(),
uiOutput("choices"),br(),
rHandsontableOutput('hottable'),br(),
fluidRow(
column(1,actionButton("addSeries", "Add",width = '70px')),
column(3,hidden(uiOutput("delSeries2")))
)
)
server <- function(input, output) {
uiTable <- reactiveVal(mydata)
observeEvent(input$hottable, {uiTable(hot_to_r(input$hottable))})
output$hottable <- renderRHandsontable({
rhandsontable(uiTable(),rowHeaderWidth = 100, useTypes = TRUE)
})
observe({
req(input$choices)
tmpTable <- uiTable()
tmpTable[2,] <- as.numeric(input$choices)
uiTable(tmpTable)
})
output$choices <-
renderUI({
selectInput(
"choices",
label = "User selects value to reflect in row 2 of table below:",
choices = c(1,2,3)
)
})
observeEvent(input$addSeries, {
newCol <- data.frame(c(1,1,0,1))
newCol[2,] <- as.numeric(input$choices)
names(newCol) <- paste("Series", ncol(hot_to_r(input$hottable)) + 1)
uiTable(cbind(uiTable(), newCol))
})
output$delSeries2 <-
renderUI(
selectInput(
"delSeries3",
label = NULL,
choices = colnames(hot_to_r(input$hottable))
)
)
}
shinyApp(ui,server)
I have a running example: I am updating a data.table depending on users input via checkboxes. So far Iam filtering the data explicitly, but I would like to do that with the help of a loop using a for loop or a function of the apply-family. Unfortunately I cannot get either to work.
library(shiny)
library(data.table)
library(DT)
tdata <- data.table(fruit = c(rep("Apple",4),rep( "Ban",4)),
bug1 = c(rep(c("+","+", "-","-"),2)),
bug2 = c(rep(c("+","-"),4)),
value = c(rep(c(0.25),4), 0.6,0.4,0,0))
ui <- (fluidPage(tagList(
sidebarLayout(
sidebarPanel(uiOutput("file_input")),
mainPanel(dataTableOutput('fruit_table'))
))))
server <- function(input, output) {
fileData <- reactive(
return(tdata)
)
colname_list <- reactive(
colnames(fileData())
)
output$file_input <- renderUI ({
if(is.null(fileData())){
return()
}else{
tagList(
lapply(1:(length(fileData())-1), function(i){
choice_list = unique(fileData()[,get(colnames(fileData()[,i, with = FALSE]))])
checkboxGroupInput(inputId = colnames(fileData()[,i, with = FALSE]),
label = colnames(fileData()[,i, with = FALSE]),
choices = choice_list,
inline = TRUE,
selected = fileData()[1, i, with = FALSE])
})
)
}
})
# works fine, but usually the number of columns changes so I want to keep it flexible
fruitFilter <- reactive({
fileData()[[paste0(colname_list()[1])]] %in% input[[paste0(colname_list()[1])]] &
fileData()[[paste0(colname_list()[2])]] %in% input[[paste0(colname_list()[2])]] &
fileData()[[paste0(colname_list()[3])]] %in% input[[paste0(colname_list()[3])]]
})
# fruitFilter <- reactive({
# for(i in 1: ((length(fileData()))-1)){
# fileData()[[paste0(colname_list()[i])]] %in% input[[paste0(colname_list()[i])]]
# }
# })
output$fruit_table <- renderDataTable({
datatable(fileData()[fruitFilter(),])
})
}
shinyApp(ui = ui, server = server)
I still consider myself a newby to Shiny. I appreciate any help! Thanks.
In the loop approach, we could initialize a list and then Reduce the output to a single logical vector
fruitFilter <- reactive({
i1 <- head(seq_along(fileData()), -1)
l1 <- vector('list', length(i1))
for(i in i1){
l1[[i]] <- fileData()[[colname_list()[i]]] %in% input[[colname_list()[i]]]
}
Reduce(`&`, l1)
})
With the full code
library(shiny)
library(data.table)
library(DT)
tdata <- data.table(fruit = c(rep("Apple",4),rep( "Ban",4)),
bug1 = c(rep(c("+","+", "-","-"),2)),
bug2 = c(rep(c("+","-"),4)),
value = c(rep(c(0.25),4), 0.6,0.4,0,0))
ui <- (fluidPage(tagList(
sidebarLayout(
sidebarPanel(uiOutput("file_input")),
mainPanel(dataTableOutput('fruit_table'))
))))
server <- function(input, output) {
fileData <- reactive(
return(tdata)
)
colname_list <- reactive(
colnames(fileData())
)
output$file_input <- renderUI ({
if(is.null(fileData())){
return()
}else{
tagList(
lapply(1:(length(fileData())-1), function(i){
choice_list = unique(fileData()[,get(colnames(fileData()[,i, with = FALSE]))])
checkboxGroupInput(inputId = colnames(fileData()[,i, with = FALSE]),
label = colnames(fileData()[,i, with = FALSE]),
choices = choice_list,
inline = TRUE,
selected = fileData()[1, i, with = FALSE])
})
)
}
})
fruitFilter <- reactive({
i1 <- head(seq_along(fileData()), -1)
l1 <- vector('list', length(i1))
for(i in i1){
l1[[i]] <- fileData()[[colname_list()[i]]] %in% input[[colname_list()[i]]]
}
Reduce(`&`, l1)
})
output$fruit_table <- renderDataTable({
datatable(fileData()[fruitFilter()])
})
}
shinyApp(ui = ui, server = server)
--output
I am trying to build a dynamic form where the user can add some criteria (via an actionButton) and select values for those criteria. When he's done selecting he may launch some computation.
Every criterion may be removed via a 'delete' button.
It works quite fine for all except the last inserted component that does not react to the related remove button.
The last component is removed only when the "Add criteria" button is clicked.
Is it a bug or could you point my mistake ?
I'm using an observeEvent with a renderUI to build components:
In server.R
observeEvent(input$go, {
output$ui <- renderUI({
rows <- lapply(names(components),buildComponent)
res = do.call(fluidRow, rows)
})
makeObservers()
})
makeObservers creates an observeEvent closure for every component :
makeObservers <- eventReactive(input$go, {
IDs <- names(components)
new_ind <- !(IDs %in% vals$y)
res <- lapply(IDs[new_ind], function (x) {
observeEvent(input[[paste0("rmv", x)]], {
if(components[[x]] == "Main1") removeComponent(x)
})
})
} ,
ignoreNULL = F, ignoreInit = F)
Please find a working example.
library(shiny)
library(shinythemes)
criterias <- c("Criteria 1", "Criteria 2", "Criteria 3", "Criteria 4")
components <<- list()
counter <<- 0
buildComponent <- function(val) {
idselect = paste0("select", val)
idremove <- paste0("rmv", val)
div(
selectInput(idselect, "criteria :", criterias, criterias[0]),
actionButton(idremove, paste0("X", val),icon = icon("remove"), size = "small")
)
}
removeComponent <- function(x) {
print(paste0("Removing" ,x))
xpath1 = paste0("div:has(> #select", x ,")" )
xpath2 = paste0("div:has(> #rmv", x ,")" )
removeUI(
selector = xpath1, multiple = T#, immediate=T
)
removeUI(
selector = xpath2, multiple = T#, immediate=T
)
components[[as.character(x)]] <<- NULL
}
ui <- shinyUI(fluidPage(
sidebarPanel(
actionButton("go", "Criteria", icon = icon("plus-circle"),
size = "small"),
uiOutput("ui")
),
mainPanel(
actionButton("activate", "show cpts"),
textOutput('show_components')
)
) )
server <- shinyServer(function(input, output, session) {
# Keep track of which observer has been already created
vals <- reactiveValues(y = NULL)
makeObservers <- eventReactive(input$go, {
IDs <- names(components)
new_ind <- !(IDs %in% vals$y)
print("new_ind")
print(IDs[new_ind])
# update reactive values
vals$y <- names(components)
res <- lapply(IDs[new_ind], function (x) {
observeEvent(input[[paste0("rmv", x)]], {
print(paste0("rmv", x))
print(components[[x]])
if(components[[x]] == "Main1") removeComponent(x)
})
})
} , ignoreNULL = F, ignoreInit = F)
observeEvent(input$go, {
output$ui <- renderUI({
print(counter)
counter <<- counter + 1
components[[as.character(counter)]] <<- "Main1"
print("adding component : ")
print(paste0(names(components),collapse = ";"))
rows <- lapply(names(components),buildComponent)
res = do.call(fluidRow, rows)
})
makeObservers()
})
observeEvent(input$activate, {
output$show_components <- renderPrint({
components
})
})
})
shinyApp(ui, server)
Thanks to great remarks from Mike Wise, i ve been able to spot the precise problem: (see comment in Mike answer). Here is some code :
library(shiny)
library(shinythemes)
criterias <- c("Criteria 1", "Criteria 2", "Criteria 3", "Criteria 4")
components <<- list()
counter <<- 0
buildComponent <- function(val) {
idselect = paste0("select", val)
idremove <- paste0("rmv", val)
div(
selectInput(idselect, "criteria :", criterias, criterias[0]),
actionButton(idremove, paste0("X", val),icon = icon("remove"), size = "small")
)
}
removeComponent <- function(x) {
print(paste0("Removing" ,x))
xpath1 = paste0("div:has(> #select", x ,")" )
xpath2 = paste0("div:has(> #rmv", x ,")" )
removeUI(
selector = xpath1, multiple = T#, immediate=T
)
removeUI(
selector = xpath2, multiple = T#, immediate=T
)
components[[as.character(x)]] <<- NULL
}
ui <- shinyUI(fluidPage(
sidebarPanel(
actionButton("go", "Criteria", icon = icon("plus-circle"),
size = "small"),
uiOutput("ui")
),
mainPanel(
actionButton("activate", "show cpts"),
textOutput('show_components')
)
) )
server <- shinyServer(function(input, output, session) {
# Keep track of which observer has been already created
vals <- reactiveValues(y = NULL)
makeObservers <- eventReactive(input$go, {
IDs <- names(components)
new_ind <- !(IDs %in% vals$y)
print("new_ind")
print(IDs[new_ind])
# update reactive values
vals$y <- names(components)
res <- lapply(IDs[new_ind], function (x) {
observeEvent(input[[paste0("rmv", x)]], {
print(paste0("rmv", x))
print(components[[x]])
if(components[[x]] == "Main1") removeComponent(x)
})
})
} , ignoreNULL = F, ignoreInit = F)
observeEvent(input$go, {
counter <<- counter + 1
components[[as.character(counter)]] <<- "Main1"
output$ui <- renderUI({
print(counter)
print("adding component : ")
print(paste0(names(components),collapse = ";"))
rows <- lapply(names(components),buildComponent)
res = do.call(fluidRow, rows)
})
makeObservers()
})
observeEvent(input$activate, {
output$show_components <- renderPrint({
components
})
})
})
shinyApp(ui, server)
Ok, there were some problems in the code, and I had to make some small but important changes to understand it, and then get it to work as intended. However it is essentially the same code.
Changes:
Changed rv$y to rv$prev_components.
Put your components and counter variable into the reactiveValues to get rid of the <<-, seeing as you were using reactiveValues already which obviates the need for <<-
Added a setdiff to find the new addition to your names (this was key).
Changed makeObervables into a simple function (it was not being used as an eventReactive at all anyway).
Probably a few other small things that are forgotten.
This is the code:
library(shiny)
library(shinythemes)
criterias <- c("Criteria 1", "Criteria 2", "Criteria 3", "Criteria 4")
vals <- reactiveValues(prev_components=list(),components=list(),counter=0)
buildComponent <- function(val) {
idselect = paste0("select", val)
idremove <- paste0("rmv", val)
div(
selectInput(idselect, "criteria :", criterias, criterias[0]),
actionButton(idremove, paste0("X", val),icon = icon("remove"), size = "small")
)
}
removeComponent <- function(x) {
print(paste0("Removing" ,x))
xpath1 = paste0("div:has(> #select", x ,")" )
xpath2 = paste0("div:has(> #rmv", x ,")" )
removeUI(
selector = xpath1, multiple = T#, immediate=T
)
removeUI(
selector = xpath2, multiple = T#, immediate=T
)
vals$components[[as.character(x)]] <<- NULL
}
ui <- shinyUI(fluidPage(
sidebarPanel(
actionButton("go", "Criteria", icon = icon("plus-circle"),
size = "small"),
uiOutput("uii")
),
mainPanel(
actionButton("activate", "show cpts"),
textOutput('show_components')
)
) )
server <- shinyServer(function(input, output, session) {
makeObservers <- function() {
IDs <- names(vals$components)
new_ind <- setdiff(IDs,vals$prev_components)
vals$prev_components <- names(vals$components)
res <- lapply(new_ind, function (x) {
observeEvent(input[[paste0("rmv", x)]], {
print(paste0("rmv", x))
print(vals$components[[x]])
if(vals$components[[x]] == "Main1") removeComponent(x)
})
})
}
observeEvent(input$go, {
print(vals$counter)
vals$counter <- vals$counter + 1
vals$components[[as.character(vals$counter)]] <- "Main1"
output$uii <- renderUI({
print("adding component : ")
print(paste0(names(vals$components),collapse = ";"))
rows <- lapply(names(vals$components),buildComponent)
res = do.call(fluidRow, rows)
})
makeObservers()
})
observeEvent(input$activate, {
output$show_components <- renderPrint({
vals$components
})
})
})
shinyApp(ui, server)
And a screen shot: