Search code examples
rinputshinydynamicshinyjs

How to add/remove input fields dynamically by a button in shiny


I've been trying to find a solution how to add and remove input fields with a button in shiny. I don't have a source code since I haven't made that much progress, but this jQuery example (http://www.mkyong.com/jquery/how-to-add-remove-textbox-dynamically-with-jquery/) gives a good idea on what I'm trying to accomplish. Is this possible in shiny or should I use shinyjs to do this? Thank you in advance!


Solution

  • EDIT: I read the jQuery example a bit more, and added a code snippet doing what I think you were looking for.

    I don't know jQuery, so I couldn't make much out of the example link. I took a guess on what you wanted, but I think the key idea is the use of renderUI and uiOutput even if my suggestion here misses the point.

    To toggle a ui element:

    If you specifically don't want to use shinyjs, you could do something like this:

    library(shiny)
    
    ui <- shinyUI(fluidPage(
    
      actionButton("btn", "Toggle Textbox"),
    
      textOutput("btn_val"),
      uiOutput("textbox_ui")
    
    ))
    
    server <- shinyServer(function(input, output, session) {
    
      output$btn_val <- renderPrint(print(input$btn))
    
      textboxToggle <- reactive({
    
        if (input$btn %% 2 == 1) {
          textInput("textin", "Write something:", value = "Hello World!")
        }
    
      })
    
      output$textbox_ui <- renderUI({ textboxToggle() })
    
    })
    
    shinyApp(ui, server)
    

    To add and remove elements:

    After reading a bit of the jQuery example, I think this is similar to what you were looking for:

    library(shiny)
    
    ui <- shinyUI(fluidPage(
    
      sidebarPanel(
    
          actionButton("add_btn", "Add Textbox"),
          actionButton("rm_btn", "Remove Textbox"),
          textOutput("counter")
    
        ),
    
      mainPanel(uiOutput("textbox_ui"))
    
    ))
    
    server <- shinyServer(function(input, output, session) {
    
      # Track the number of input boxes to render
      counter <- reactiveValues(n = 0)
    
      observeEvent(input$add_btn, {counter$n <- counter$n + 1})
      observeEvent(input$rm_btn, {
        if (counter$n > 0) counter$n <- counter$n - 1
      })
    
      output$counter <- renderPrint(print(counter$n))
    
      textboxes <- reactive({
    
        n <- counter$n
    
        if (n > 0) {
          lapply(seq_len(n), function(i) {
            textInput(inputId = paste0("textin", i),
                      label = paste0("Textbox", i), value = "Hello World!")
          })
        }
    
      })
    
      output$textbox_ui <- renderUI({ textboxes() })
    
    })
    
    shinyApp(ui, server)
    

    The problem with this approach is that each time you press the add or remove button, all of the input boxes get re-rendered. This means that any input you might have had on them disappears.

    I think you could get around that by also saving the current input values of the input boxes into a reactiveValues object, and setting the values from the object as the starting values of the re-rendered input boxes by using the value option in textInput. I'll leave the implementation of that for now, though.