Search code examples
rdynamicshinytextinput

Keep UI Text Input after adding or removing Inputs


I'm building a small UI where a user will enter into a splitLayout row of text that builds a statement (not needed for this question) to solve a puzzle.

First Input

However, if the user decides he/she needs an additional row or less rows to solve the puzzle I'd like adding or removing a new row of inputs to NOT delete the remaining input rows.

Second Input Attempt * the gray is a placeholder.

How can I best achieve my desired result of:

enter image description here

Please find my trimmed code below. Thanks for your input.

library(shiny)

# Define UI
ui <- fluidPage(
  # Application title
  titlePanel("Identify A, B and C"),
  sidebarLayout(
    sidebarPanel(width = 5,
                 helpText("Present a statement and receive a response: 1 is a Knight who always tells the truth, 2 is a Knave who always lies, and 3 is a Normal who can do either."),
                 # Number of Questions
                 numericInput(inputId = "Questions", label = "Number of Questions", 
                              value = 1, min = 1, max = 10, step = 1),
                 splitLayout(cellWidths = c("25%","70%"), 
                             style = "border: 1px solid silver;",
                             cellArgs = list(style = "padding: 3px"),
                             uiOutput("textQuestions"), uiOutput("textQuestions2"))
    ),
    mainPanel(
      # Right hand side output
    )
  )
)

# Define server logic 
server <- function(input, output) {
  ####### I don't want these to delete initially everytime??
  output$textQuestions <- renderUI({
    Questions <- as.integer(input$Questions)
    lapply(1:Questions, function(i) {
      textInput(inputId = paste0("Who", i), label = paste0(i, ". Ask:"), placeholder = "A")
    })
  })
  ########
  output$textQuestions2 <- renderUI({
    Questions <- as.integer(input$Questions)
    lapply(1:Questions, function(i) {
      textInput(inputId = paste0("Q", i) , label = paste0("Logic:"), 
                value = "", placeholder = "A == 1 & (B != 2 | C == 3)")
    })
  })
  ######
}

# Run the application 
shinyApp(ui = ui, server = server)

Solution

  • It looks like someone already gave you an answer using uiOutput+renderUI, so I'm going to go the other route: using insertUI and removeUI.

    Instead of having a numeric input for "number of questions", I replaced it with a button for "add question" and one for "remove question". I have a variable keeping track of how many questions there are. Every time "add question" is pressed, we add one row. When "remove question" is pressed, we remove the last row.

    Here's the code:

    library(shiny)
    
    # Define UI
    ui <- fluidPage(
      # Application title
      titlePanel("Identify A, B and C"),
      sidebarLayout(
        sidebarPanel(
          width = 5,
          helpText("Present a statement and receive a response: 1 is a Knight who always tells the truth, 2 is a Knave who always lies, and 3 is a Normal who can do either."),
          # Buttons to add/remove a question
          actionButton("add", "Add question"),
          actionButton("remove", "Remove question"),
          div(id = "questions",
              style = "border: 1px solid silver;")
        ),
        mainPanel(
          # Right hand side output
        )
      )
    )
    
    # Define server logic 
    server <- function(input, output) {
      # Keep track of the number of questions
      values <- reactiveValues(num_questions = 0)
    
      # Add a question
      observeEvent(input$add, ignoreNULL = FALSE, {
        values$num_questions <- values$num_questions + 1
        num <- values$num_questions
        insertUI(
          selector = "#questions", where = "beforeEnd",
          splitLayout(
            cellWidths = c("25%","70%"), 
            cellArgs = list(style = "padding: 3px"),
            id = paste0("question", num),
            textInput(inputId = paste0("Who", num),
                      label = paste0(num, ". Ask:"),
                      placeholder = "A"),
            textInput(inputId = paste0("Q", num) ,
                      label = paste0("Logic:"),
                      placeholder = "A == 1 & (B != 2 | C == 3)")
          )
        )
      })
    
      # Remove a question
      observeEvent(input$remove, {
        num <- values$num_questions
        # Don't let the user remove the very first question
        if (num == 1) {
          return()
        }
        removeUI(selector = paste0("#question", num))
        values$num_questions <- values$num_questions - 1
      })
    
    
    }
    
    # Run the application 
    shinyApp(ui = ui, server = server)
    

    EDIT

    OP requested a way to retrieve the user's input based on a question number. To do that:

    1. Add the following to the UI

      numericInput("question_num", "Show question number", 1),
      textOutput("question")
      
    2. Add the following to the server

      get_question <- function(q) {
        paste(
          input[[paste0("Who", q)]],
          ":",
          input[[paste0("Q", q)]]
        )
      }
      
      output$question <- renderText({
        get_question(input$question_num)
      })