4

I have one code in which I add and remove objects by using insertUI and removeUI as it can be seen below:

library(shiny)

# Define the UI
ui <- fluidPage(
  actionButton("adder", "Add"),
  tags$div(id = 'placeholder')
)


# Define the server code
server <- function(input, output) {
  rv <- reactiveValues()

  rv$counter <- 0

  observeEvent(input$adder,{
    rv$counter <- rv$counter + 1
    add <- sprintf("%03d",rv$counter)

    filterId <- paste0('adder_', add)
    divId <- paste0('adder_div_', add)
    elementFilterId <- paste0('adder_object_', add)
    removeFilterId <- paste0('remover_', add)

    insertUI(
      selector = '#placeholder',
      ui = tags$div(
        id = divId,
        actionButton(removeFilterId, label = "Remove filter", style = "float: right;"),
        textInput(elementFilterId, label = "Introduce text", value = "")
      )
    )

    # Observer that removes a filter
    observeEvent(input[[removeFilterId]],{
      rv$counter <- rv$counter - 1
      removeUI(selector = paste0("#", divId))
    })
  })
}

# Return a Shiny app object
shinyApp(ui = ui, server = server, options = list(launch.browser = T))

The problem that I'm experiencing is that if I add one UI (click on Add), then remove it (click on Remove filter) and then add a new one (click on Add again), the first time I click on it, doesn't work.

I know that it's due to the fact that I'm using an ID I've used previously but, theoretically, I've completely removed it with the removeUI instruction.

What am I missing in here?

asuka
  • 2,249
  • 3
  • 22
  • 25

3 Answers3

2

FYI there is a way to clear the Server of input IDS so you don't run into the same problem. I found the solution here.

https://roh.engineering/post/shiny-add-removing-modules-dynamically/

The article goes into a lot of detail, but you can use the following function to remove inputs from the server.

remove_shiny_inputs <- function(id, .input) {
  invisible(
    lapply(grep(id, names(.input), value = TRUE), function(i) {
      .subset2(.input, "impl")$.values$remove(i)
    })
  )
}
  • Pls give an answer to the question or at least quote the relevant parts of the blog post. The link might helpful now but invalid in future – Thomas Apr 08 '20 at 15:35
1

Though I consider that there should be a more elegant way of solving this problem, I have found a workaround that solves it.

In summary, I've added to the ID of the object that I create a random string that I've created; thus, the id will never be repeated.

library(shiny)

generateRandomString <- function(n = 10, m = 10) {
  elements <- c()

  chars <- c(LETTERS, letters)

  for(idx in 1:n) {
    element <- c()

    for(entry in 1:m) {
      val <- sample(c("pair","odd"),1)

      switch(val,
        pair = {
          # Add a letter
          element <- c(element, sample(chars,1))
        },
        odd = {
          # Add a number
          element <- c(element, as.character(sample(0:9,1)))
        }
      )
    }

    elements <- c(elements, paste0(element,collapse=""))
  }

  elements
}

# Define the UI
ui <- fluidPage(
  actionButton("adder", "Add"),
  tags$div(id = 'placeholder')
)


# Define the server code
server <- function(input, output) {
  rv <- reactiveValues()

  rv$counter <- 0

  observeEvent(input$adder,{
    rv$counter <- rv$counter + 1

    add <- sprintf("%03d",rv$counter)

    prefix <- generateRandomString(1,20)
    filterId <- paste0(prefix,'_adder_', add)
    divId <- paste0(prefix,'_adder_div_', add)
    elementFilterId <- paste0(prefix,'_adder_object_', add)
    removeFilterId <- paste0(prefix,'_remover_', add)

    insertUI(
      selector = '#placeholder',
      ui = tags$div(
        id = divId,
        actionButton(removeFilterId, label = "Remove filter", style = "float: right;"),
        textInput(elementFilterId, label = "Introduce text", value = "")
      )
    )

    # Observer that removes a filter
    observeEvent(input[[removeFilterId]],{
      rv$counter <- rv$counter - 1
      removeUI(selector = paste0("#", divId))
    })
  })
}

# Return a Shiny app object
shinyApp(ui = ui, server = server, options = list(launch.browser = T))

There are better ways of creating such a random string but the point is that the issue is solved.

asuka
  • 2,249
  • 3
  • 22
  • 25
0

I was trying the same thing and this is my solution:

I keep track of all the ids that are created, I remove the last one created, and I reuse the ids of the deleted ones. I have a counter for that.
I start with an initial box (there is no real need for that, but I guess in a real work scenario you would expect at least 1 textBox to appear and increase thereafter). It is straightforward to start without the initial box.

Also, I keep track of the values of the textInput boxes that are currently active, in a reactive List. You would definitely need this

Hope it helps!



library(shiny)

ui <- fluidPage(
  
  actionButton("insertBtn", "Insert"),
  actionButton("deleteBtn", "Delete"),
  h4("My boxes"),
  # Initial box here to start with. Not needed but it is nice to have one :)
  div(id = "box-1", textInput(inputId =  "box-1", label = "box-1")),
  div(id = "placeholder"),
  h4('Box contents'),
  verbatimTextOutput("box_inputs")
)

server <- function(input, output, session) {
  
  id_prefix <- "box-"
  
  ## keep track of elements inserted and a counter of the elements
  counter <- reactiveVal(1)
  
  observeEvent(input$insertBtn, {
    
    counter(counter()+1)
    
    serial <- counter()
    
    id <- paste0('box-', serial)
    
    insertUI(
      
      selector = '#placeholder',      
      ## wrap element in a div with id for ease of removal
      ui = div(id = id,
               textInput(inputId =  id, label = paste0("box-", serial))
      )
    )
    
  })
  
  observeEvent(input$deleteBtn, { 
    
    req(counter() > 0) # need to have some inputs to remove them
    
    
    id_to_remove <- paste0(id_prefix, counter()) # removes the last one 
    
    removeUI(
      ## pass it in as JQuery selector
      selector = paste0('#', id_to_remove)
    )
    
    counter(counter()-1)
    
  })
  
  box_inputs <- reactive({
    
    req(counter() > 0) # need to have some inputs to remove them
    
    all_inputs <- reactiveValuesToList(input)
    
    boxes <- paste0(id_prefix, seq_len(counter()))
    
    # regex of the `union` of all boxes
    ids_regex <- paste(boxes, collapse = "|")
    
    # extract them from the list of server inpurs
    all_inputs[grepl(ids_regex, names(all_inputs))]
    
  })
  
  output$box_inputs <- renderPrint({
    
    box_inputs()
    
  })
  
  output$all_inputs <- renderPrint({
    
    reactiveValuesToList(input)
  })
  
  
}

shinyApp(ui, server)

Many thanks to

this post and this and these SO posts one, two

enter image description here

Lefkios Paikousis
  • 462
  • 1
  • 6
  • 12