Keep UI Text Input after adding or removing Inputs - r

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.
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.
* the gray is a placeholder.
How can I best achieve my desired result of:
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)

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:
Add the following to the UI
numericInput("question_num", "Show question number", 1),
textOutput("question")
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)
})

You could store it in a reactive value:
global <- reactiveValues(ask = c(), logic = c())
observe({
Questions <- as.integer(input$Questions)
lapply(1:Questions, function(i) {
inputVal <- input[[paste0("Who", i)]]
if(!is.null(inputVal)){
global$logic[i] <- inputVal
}
inputValQ <- input[[paste0("Q", i)]]
if(!is.null(inputValQ)){
global$ask[i] <- inputValQ
}
})
})
That would yield the following code for you example:
As a side effect the values would also be stored if a input was removed and then retaken.
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) {
global <- reactiveValues(ask = c(), logic = c())
observe({
Questions <- as.integer(input$Questions)
lapply(1:Questions, function(i) {
inputVal <- input[[paste0("Who", i)]]
if(!is.null(inputVal)){
global$ask[i] <- inputVal
}
inputValQ <- input[[paste0("Q", i)]]
if(!is.null(inputValQ)){
global$logic[i] <- inputValQ
}
})
})
####### 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", value = global$ask[i])
})
})
########
output$textQuestions2 <- renderUI({
Questions <- as.integer(input$Questions)
lapply(1:Questions, function(i) {
textInput(inputId = paste0("Q", i) , label = paste0("Logic:"), value = global$logic[i],
placeholder = "A == 1 & (B != 2 | C == 3)")
})
})
######
}
# Run the application
shinyApp(ui = ui, server = server)

Related

How to save the inputs, inside Data Frame which is inside the list?

I tried to keep reprex as simple as possible.
I want to save with the ADD button currently chosen inputs, inside Data Frame (selected by index passed by userId input), which is inside the list, and later on use this Data Frame to render a table (in the final app make a plot).
Here I figured out, how to save values inside the data frame. (not data frame inside a list)
How to save input to data frame, and use it later in Shiny?
Now Add button returns this:
Warning: Error in choosen_user: unused argument (rbind(choosen_user(), new_day_rate())) <- this is propably because I used reactive() not reactiveVal(), but with reactiveVal() there is this error:
Warning: Error in .getReactiveEnvironment()$currentContext: Operation not allowed without an active reactive context.
You tried to do something that can only be done from inside a reactive consumer.
library(shiny)
# Saved_users_list normally came from external file
saved_users_list <- list(data.frame(date = c(as.Date("2022-04-18"),
as.Date("2022-04-19")),
rate = c(8,1),
day_comment = c("Found a gf",
"Broke my arm")),
data.frame(date = c(as.Date("2022-04-18"),
as.Date("2022-04-19")),
rate = c(10,1),
day_comment = c("Found a job",
"They fired me")))
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("userId", "userId", choices = c(1:2)),
sliderInput("day_rate", "Rate your day", min = 0, max = 10, value = 5, step = 0.5),
dateInput("date", "Pick a date"),
textAreaInput("comment", "Comment", placeholder = "Add a description (OPTIONAL)"),
actionButton("add", "Add"),
actionButton("test", "Test values") # Button to test inputs values
),
mainPanel(
tableOutput("test_table")
)
)
)
server <- function(input, output, session) {
users_list <- reactiveVal(saved_users_list)
selected_user <- reactive(as.numeric(input$userId))
output$test_table <- renderTable({
users_list()[selected_user()]
})
new_day_rate <- reactive(list(data.frame(date = input$date,
rate = input$day_rate,
day_comment = input$comment)))
choosen_user <- reactive(users_list()[[selected_user()]])
# Button to add values to the data frame inside users_list
observeEvent(input$add, {
# users_list()[[selected_user()]] <- rbind(users_list()[[selected_user()]], as.data.frame(new_day_rate())) # Error in <-: invalid (NULL) left side of assignment
choosen_user(rbind(choosen_user(), new_day_rate())) # Here I tried to implement a solution from linked question
})
# Button to test inputs values
observeEvent(input$test, {
message("userId: ", input$userId, " ", class(input$userId))
message("selected_user(): ", selected_user())
message("new_day_rate(): ", new_day_rate())
message("str(new_day_rate()): ", str(new_day_rate()))
message("users_list()[[selected_user()]]: ",users_list()[[selected_user()]])
})
}
shinyApp(ui, server)
I think you're after reactiveValues? Something like:
library(shiny)
# Saved_users_list normally came from external file
saved_users_list <- list(
data.frame(
date = c(as.Date("2022-04-18"), as.Date("2022-04-19")),
rate = c(8,1),
day_comment = c("Found a gf", "Broke my arm")
),
data.frame(
date = c(as.Date("2022-04-18"), as.Date("2022-04-19")),
rate = c(10,1),
day_comment = c("Found a job", "They fired me")
)
)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("userId", "userId", choices = c(1:2)),
sliderInput("day_rate", "Rate your day", min = 0, max = 10, value = 5, step = 0.5),
dateInput("date", "Pick a date"),
textAreaInput("comment", "Comment", placeholder = "Add a description (OPTIONAL)"),
actionButton("add", "Add"),
actionButton("test", "Test values") # Button to test inputs values
),
mainPanel(
tableOutput("test_table")
)
)
)
server <- function(input, output, session) {
cache <- reactiveValues(saved_users = saved_users_list)
selected_user <- reactive(as.numeric(input$userId))
output$test_table <- renderTable({
cache$saved_users[selected_user()]
})
new_day_rate <- reactive(
data.frame(
date = as.Date(input$date),
rate = input$day_rate,
day_comment = input$comment
)
)
observeEvent(input$add, {
cache$saved_users[[selected_user()]] <- rbind(
cache$saved_users[[selected_user()]], new_day_rate()
)
})
observeEvent(input$test, {
message("userId: ", input$userId, " ", class(input$userId))
message("selected_user(): ", selected_user())
message("new_day_rate(): ", new_day_rate())
message("str(new_day_rate()): ", str(new_day_rate()))
message("users_list()[[selected_user()]]: ", cache$saved_users[[selected_user()]])
})
}
shinyApp(ui, server)

Clear data when removeUI used

I am using observeEvent to insert a UI (in the example below, it adds an additional numeric input box) with insertUI and likewise observeEvent to remove those UIs via removeUI. The issue is that the data that is created through those UIs does not clear or reset. In the example below, you can enter numbers in separate numeric input boxes and press Go! to sum them together. Upon removing elements, it should clear our that data and re-sum, but it keeps the last number added.
For example, if 1 is in the first box and two more input boxes are added, both with 1s, pressing Go! correctly sums them to 3. However, removing the two text boxes and pressing Go! keeps 3 when it should just show 1.
I am sure I have missed something simple. My reprex is below:
library(shiny)
ui <- fluidPage(
# Application title
titlePanel("example"),
# button input
sidebarLayout(
sidebarPanel(
numericInput(
"number",
"A Number 1-10",
1,
min = 1,
max = 10,
step = NA,
width = NULL),
#add/remove buttons
fluidRow(column(
6,
actionButton('insertBtn', 'Add Number'),
actionButton('removeBtn', 'Remove Number'),
tags$div(id = 'placeholder')
)),
# go button
fluidRow(column(
6, actionButton(inputId = "go", label = "Go!"),
))
),
# output
mainPanel(
htmlOutput("total")
)
)
)
server <- function(input, output) {
# add text boxes when clicked
inserted <- c()
observeEvent(input$insertBtn, {
btn <- input$insertBtn
id <- paste0('number', btn)
insertUI(
selector = '#placeholder',
## wrap element in a div with id for ease of removal
ui = tags$div(
fluidRow(
numericInput(
id,
"A Number 1-10",
1,
min = 1,
max = 10,
step = NA,
width = NULL)
),
id = id)
)
inserted <<- c(id, inserted)
})
# remove text boxes
observeEvent(input$removeBtn, {
removeUI(
## pass in appropriate div id
selector = paste0('#', inserted[length(inserted)])
)
inserted <<- inserted[-length(inserted)]
})
# add numbers in boxes
final_number_text <- eventReactive(input$go, {
btn <- input$insertBtn
if(btn > 0){
total <- input$number
for(i in 1:btn){
total<- total+ (input[[paste0("number", i)]])
}
} else {
total <- input$number
}
HTML(paste(total))
})
output$total <- renderText({
final_number_text()
})
}
# Run the application
shinyApp(ui = ui, server = server)
We may need seq_along which would be bug free when the length is 0
for(i in seq_along(inserted)) {...}
Please note that when you remove a number, you are not decreasing the value of input$insertBtn. Therefore, you want to count only the elements that are in inserted. Therefore, replace
for(i in 1:btn){...}
with
lapply(inserted, function(par) {total <<- total + input[[par]]})
Full code
library(shiny)
ui <- fluidPage(
# Application title
titlePanel("example"),
# button input
sidebarLayout(
sidebarPanel(
numericInput(
"number",
"A Number 1-10",
1,
min = 1,
max = 10,
step = NA,
width = NULL),
#add/remove buttons
fluidRow(column(
6,
actionButton('insertBtn', 'Add Number'),
actionButton('removeBtn', 'Remove Number'),
tags$div(id = 'placeholder')
)),
# go button
fluidRow(column(
6, actionButton(inputId = "go", label = "Go!"),
))
),
# output
mainPanel(
htmlOutput("total")
)
)
)
server <- function(input, output, session) {
# add text boxes when clicked
inserted <- c()
observeEvent(input$insertBtn, {
btn <- input$insertBtn
id <- paste0('number', btn)
insertUI(
selector = '#placeholder',
## wrap element in a div with id for ease of removal
ui = tags$div(
fluidRow(
numericInput(
id,
"A Number 1-10",
1,
min = 1,
max = 10,
step = NA,
width = NULL)
),
id = id)
)
# inserted <<- c(id, inserted) ## removes first one first
inserted <<- c(inserted, id) ## removes last one first
})
# remove text boxes
observeEvent(input$removeBtn, {
removeUI(
## pass in appropriate div id
selector = paste0('#', inserted[length(inserted)])
)
inserted <<- inserted[-length(inserted)]
})
# add numbers in boxes
final_number_text <- eventReactive(input$go, {
total <- input$number
if(length(inserted) > 0){
lapply(inserted, function(par) {total <<- total + input[[par]]})
}
HTML(paste(total))
})
output$total <- renderText({
final_number_text()
})
}
# Run the application
shinyApp(ui = ui, server = server)

How to combine multiple inputs in one vector in shiny. Number of inputs depends on user's choice

I'm trying to create an UI in which user can choose some objects (as many as they want) and their respective weights. The weight input fields appear only when there's more than one object and increase as the user selects more objects. This part already works.
What I need is a vector that holds all the weights saved in the w1, w2 and so on.
I've tried using for loops and sapply with get() function but can't access the input$w1, input$w2 etc.
library(shiny)
# Create list of objects
object_list <- vector()
object_list <- paste0("O_", 1:10)
names(object_list) <- paste("Object", 1:10)
ui <- dashboardPage(
dashboardHeader(title = "Dynamic UI"),
dashboardSidebar(
width = 700,
fluidRow(
column(7, selectInput("chosen_objects", "Chosen objects", choices = object_list, multiple = TRUE, width = "100%")),
column(5, uiOutput("weights"))
)
),
dashboardBody(
fluidPage(tabBox(width=2500,
tabPanel(
title = "Table"
)
)
)
)
)
server <- function(input,output) {
objects_number <- reactive({length(input$chosen_objects)})
output$weights <- renderUI({
if (is.na(objects_number()) | objects_number() <= 1)
return(NULL)
lapply(1:objects_number(), function(i) {
id <- paste0("w", i)
textInput(id, paste("Weight of", input$chosen_objects[i]), value = input[[id]], width = "50%", placeholder = "%")
})
})
}
shinyApp(ui, server)
Is there a way to collect the dynamic inputs in one vector or list?
I have made a few changes and based on your code I think you are good enough to see and get them by yourself. Let me know if you have any questions -
library(shiny)
# Create list of objects
object_list <- vector()
object_list <- paste0("O_", 1:10)
names(object_list) <- paste("Object", 1:10)
ui <- dashboardPage(
dashboardHeader(title = "Dynamic UI"),
dashboardSidebar(
width = 700,
fluidRow(
column(7, selectInput("chosen_objects", "Chosen objects",
choices = object_list, multiple = TRUE, width = "100%")),
column(5, uiOutput("weights"))
)
),
dashboardBody(
fluidPage(tabBox(width=2500,
tabPanel(
title = "Table",
verbatimTextOutput("weight_output")
)
)
)
)
)
server <- function(input,output) {
objects_number <- reactive({length(input$chosen_objects)})
output$weights <- renderUI({
if (is.na(objects_number()) | objects_number() <= 1)
return(NULL)
lapply(gsub("[A-Z]+_", "", input$chosen_objects), function(i) {
id <- paste0("w", i)
textInput(id, paste("Weight of", paste0("O_", i)),
value = NULL, width = "50%", placeholder = "%")
})
})
output$weight_output <- renderPrint({
req(input$chosen_objects)
sapply(paste0("w", gsub("[A-Z]+_", "", input$chosen_objects)), function(a) input[[a]])
})
}
shinyApp(ui, server)

Showing and hiding inputs based on checkboxGroupInput

My shiny app begins with a checkboxGroupInput which contains the names of three companies: A, B and C. It also has 3 hidden numeric inputs, each corresponding to a company. Potential investors may select the name of the company they wish to invest in and specifiy the amount they are willing to invest. When the name of a company is checked, the corresponding numeric input shows up. Also, when the company name is unchecked, the numeric input disappears.
The checkboxGroupInput is called company. The 3 numericInput fields are respectively called amountA, amountB and amountC and are all generated inside a uiOutput. They are hidden with the hidden function of shinyjs.
library(shiny)
library(shinyjs)
library(magrittr)
ui <- fluidPage(
useShinyjs(),
checkboxGroupInput(inputId = "company", label = "Select a company", choices = LETTERS[1:3]),
uiOutput(outputId = "amounts")
)
server <- function(input, output){
company_names <- LETTERS[1:3]
num_ids <- paste0("amount", LETTERS[1:3])
output$amounts <- renderUI({
num_inputs <- lapply(1:3, function(i){
numericInput(inputId = num_ids[i], label = paste0("Investment in ", company_names[i]), value = 0, min = 0, max = 5000)
}) %>% tagList
shinyjs::hidden(num_inputs)
})
observeEvent(eventExpr = input$company, handlerExpr = {
if(length(input$company) == 0){
for(i in num_ids){
shinyjs::hide(id = i)
}
} else {
for(i in input$company){
shinyjs::toggle(id = paste0("amount", i), condition = input$company)
}
}
})
}
shinyApp(ui = ui, server = server)
The problem with my app is that the intended dynamics between the checkboxGroupInput and the numericInput fields are not working as intended. For instance, once a numericInput is shown, it cannot be hidden anymore by unchecking the boxes. How can I handle this?
The code pasted above is fully functional. Thank you very much.
I fixed your code by explicitly show/hide the numericInput when the corresponding check box is selected/unselected. Also I change the observeEvent with observe to make sure that the observer reacts when none of the check boxes are selected.
library(shiny)
library(shinyjs)
library(magrittr)
ui <- fluidPage(
useShinyjs(),
checkboxGroupInput(inputId = "company", label = "Select a company", choices = LETTERS[1:3]),
uiOutput(outputId = "amounts")
)
server <- function(input, output){
company_names <- LETTERS[1:3]
num_ids <- paste0("amount", LETTERS[1:3])
output$amounts <- renderUI({
num_inputs <- lapply(1:3, function(i){
numericInput(inputId = num_ids[i], label = paste0("Investment in ", company_names[i]), value = 0, min = 0, max = 5000)
}) %>% tagList
shinyjs::hidden(num_inputs)
})
observe({
for(i in company_names){
if (i %in% input$company) {
shinyjs::show(id = paste0("amount", i))
} else {
shinyjs::hide(id = paste0("amount", i))
}
}
})
}
shinyApp(ui = ui, server = server)

Unexpected values from text input in Rshiny

I am making an app that takes input from a slider to create the matching number of input text boxes. However, when I print the values from the input boxes it does not always update.
Example:
Pick 3 on slider input. Put 1,2,3 into the 3 text boxes respectively.Hit submit. Prints number = 1 number = 2 number = 3. When I move the slider to 2 and hit enter, I get number = 1 number = 2 despite no values being in the text input anymore. If I move the slider to 4, I will than get the output number = NA number = NA number = 3 number = NA.
Clearly it is remembering previous input values, but I cannot understand why or how to fix it.
ui.R
shinyUI(fluidPage(
fluidRow(
column(4, wellPanel(
sliderInput("numObs", "Number of observations", 1, 30, 3),
uiOutput("buttons"),
submitButton(text = "Apply Changes", icon = NULL)
)),
column(8,
textOutput("a")
)
)
))
server.R
shinyServer(function(input, output) {
output$buttons <- renderUI({
obs <- input$"numObs";
objs <-list();
for (i in 1:obs ){
objs <- list(objs, numericInput(inputId = paste0("t", i), "Day:", NA),br());
}
objs <- fluidRow(objs);
})
t<- function(){
for(i in 1:input$"numObs"){
if(i ==1){
t <- c(as.numeric(input[[paste0("t",i)]])[1]);
}
else{
t <- c(t,as.numeric(input[[paste0("t",i)]])[1]);
}
}
return(t);
}
output$a<- renderText({
paste("number = ", t());
})
})
I made some changes and introduced a few things to your code. Its better to use actionButton than the submitButton as it is more flexible. If you dont like the style of the actionButton, look into Shiny Themes
rm(list = ls())
library(shiny)
ui =(fluidPage(
fluidRow(
column(4, wellPanel(
sliderInput("numObs", "Number of observations", 1, 30, 3),
uiOutput("buttons"),
actionButton("goButton", "Apply Changes")
)),
column(8,textOutput("a"))
) ))
server = (function(input, output) {
output$buttons <- renderUI({
obs <- input$"numObs";
objs <-list();
for (i in 1:obs ){
objs <- list(objs, numericInput(inputId = paste0("t", i), "Day:", NA),br());
}
objs <- fluidRow(objs);
})
# keep track if the Number of obseervations change
previous <- eventReactive(input$goButton, {
input$numObs
})
t <- eventReactive(input$goButton, {
for(i in 1:input$"numObs"){
if(i ==1){
t <- c(as.numeric(input[[paste0("t",i)]])[1]);
}
else{
t <- c(t,as.numeric(input[[paste0("t",i)]])[1]);
}
}
return(t)
})
output$a<- renderText({
if(previous() != input$numObs){
return()
}
paste("number = ", t());
})
})
runApp(list(ui = ui, server = server))

Resources