user3757897
user3757897

Reputation: 326

Dynamically add/remove whole UI elements in Shiny

I've been trying to generate a Shiny page where elements can be added or removed by the user - based on this snippet. I tried to adapt it for UI elements rather than buttons, but I've clearly done something wrong.

I've only included the add-button part here - each time the "add" button is clicked, the correct number of UI elements are added, but they seem to all be the same element rather just appending a new one to the existing list (the number in the label is the same, whereas it should be different for each one) and the initial text values are erased (or, potentially, are just the "default" from the final element repeated however many times).

How can I just add a new UI element and keep any text input that the user has entered in the already-existing elements?

Code:

library(shiny)

# function to render UI element

renderUiElement <- function(id, nameDef) {
  renderUI(box(
    actionButton(paste0("rmBtn", id), label = "", icon = icon("times")),

    # text initial value supplied as a variable

    textInput(paste0("nameText", id), label = paste("Item", id), value = nameDef)
  ))
}


server <- function(input, output, session) {

  rv <- reactiveValues(uiComponents = list(), uiIds = list(), 
                       nameVals = list(), lastId = 0)

  # on "Add" button click: 

  observe({
    if(is.null(input$addBtn) || input$addBtn == 0) return()

    isolate({

      # store current text inputs and append new default text

      rv$nameVals <- lapply(rv$uiIds, function(x) input[[paste0("nameText", x)]])
      rv$nameVals <- append(rv$nameVals, "New Text")

      # create (and store) new id number and add it to the list of ids in use

      rv$lastId <- rv$lastId + 1 # get the new code to use for these UI elements
      rv$uiIds <- append(rv$uiIds, rv$lastId)

      # for each id, render the UI element using the appropriate default text

      for(i in seq_along(rv$uiIds)) {
        thisId <- rv$uiIds[[i]]
        output[[paste0("uiEl", thisId)]] <- renderUiElement(
          id = thisId, nameDef = rv$nameVals[[i]]
        )
      }

      # add the new UI element into the reactiveValues list

      rv$uiComponents <- append(
        rv$uiComponents, 
        list(
          list(
            uiOutput(paste0("uiEl", rv$lastId)),
            br(),br()
          )
        )
      )
    })
  })

  # render all the UI elements inside the container

  output$container <- renderUI({
    rv$uiComponents
  })

}

ui <- fluidPage(
  titlePanel("Dynamically Add/Remove UI Elements"),
  hr(),
  actionButton("addBtn", "Add"),
  br(),br(),
  uiOutput("container")
)

shinyApp(ui, server)

Upvotes: 0

Views: 2759

Answers (1)

Bertil Baron
Bertil Baron

Reputation: 5003

Hi to dynamicly add objects shiny has the function insertUI.

Your code would look like something similar to this

library(shiny)
library(shinydashboard)

# function to render UI element

renderUiElement <- function(id, nameDef) {
  box(
    actionButton(paste0("rmBtn", id), label = "", icon = icon("times")),

    # text initial value supplied as a variable

    textInput(paste0("nameText", id), label = paste("Item", id), value = nameDef)
  )
}


server <- function(input, output, session) {


  # on "Add" button click: 
  counter <- 0
  observeEvent(
    input$addBtn,
    {
      counter <<- counter +1
      insertUI(
        selector = '#container',
        where = "beforeEnd",
        ui = renderUiElement(
          id = paste0("ui",counter),
          nameDef = "New Text"
          ),
        session = session
        )
    },
    ignoreNULL = TRUE,
    ignoreInit = TRUE
  )



  output$container <- renderUI({
    div()
  })

}

ui <- fluidPage(
  titlePanel("Dynamically Add/Remove UI Elements"),
  hr(),
  actionButton("addBtn", "Add"),
  br(),br(),
  uiOutput("container")
)

shinyApp(ui, server)

Hope it helps.

Upvotes: 3

Related Questions