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)
Related
In my Shiny application, I have the functionality to insert a new line of information, this is done via InsertUI. Then for every added line, the source of that line can be updated via an actionButton. The actionButton leads to a modalDialog, allowing the user to insert the text for the source there.
To prevent multiple lines getting the same source after updating, a moduleServer is used for the part around the modalDialog. That works well, with the only exception that after the first time the modalDialog is used (with inside an action button that updates the text after insertion), the modalDialog closes directly after clicking. Reclicking does give the pop-up again, but is somewhat annoying.
Any suggestions to prevent this?
Example UI and server with this behaviour:
UI.R
library(shiny)
shinyUI(fluidPage(
div(id = "input_add_div_block"),
br(),
# Input for the new line
div(style="display:inline-block", # div inline-block is used to get two input boxes on the same line
textInput(inputId = "input_new_line",
label = "Give description of new line")
),
# Adding a new line
div(style="display:inline-block", # div inline-block is used to get two input boxes on the same line
id = "input_add_div",
actionButton("input_add", "Add new line")
)
))
Server.R
library(shiny)
shinyServer(function(input, output, session) {
data <- reactiveValues(
lines = list()
)
observeEvent(input$input_add, {
current_lines <- length(data$lines)
n <- current_lines + 1
insertUI(selector = "#input_add_div_block",
where = "beforeEnd", # insert after last element
ui = fluidPage(
div(style="display:inline-block"), # div inline-block is used to get two input boxes on the same line
# Adding input field
numericInput(inputId = paste0("input_", n),
label = paste0(input$input_new_line, " - % of previous line"),
value = 10,
min = 0,
max = 100
),
actionButton(inputId = paste0("input_", n,"_source"),
label = "No source")
)
)
observeEvent(input[[paste0("input_", current_lines+1,"_source")]],{
update_source_UI(id = paste0("source",n), data = data, n = n)
update_source_Server(id = paste0("source",n), data = data, n = n, original_session = session)
})
})
})
update_source_UI <- function(id, data, label = "Update UI", n){
ns <- NS(id)
showModal(
modalDialog(
title = "Update source",
textInput(inputId = ns("new_source"), label = "Update the source",
value = "Assumption"),
actionButton(inputId = ns("add_source"),
label = "Update the source"),
"\n",
actionButton(inputId = ns("cancel"),
label = "Cancel"),
easyClose = TRUE,
footer = NULL
)
)
}
# Pop-up to change the source of the funnel line input
update_source_Server <- function(id, data, n, original_session){
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
# When the user clicks "Update the source", the source is updated
observeEvent(input$add_source,{
updateActionButton(session = original_session,
inputId = paste0("input_", n,"_source"),
label = paste0("Source: ", input$new_source))
removeModal()
data$button_status[[1]] <- FALSE
})
# When the user clicks "Cancel", the modal is removed without changes
observeEvent(input$cancel,{
removeModal()
})
}
)
}
A few points:
I've never seen showModule in the UI part, normally you use it in the server; it's interesting that it works nevertheless
at least in your example, n is always 1, therefore you don't have unique ids which leads to problems
I find passing session objects to modules to refer to something defined in the main server quite complicated
I propose to pack everything for one line into a module and then handle the logic there. I think it's easier and you already work with modules.
My take:
library(shiny)
one_line_UI <- function(id, input_new_line){
ns <- NS(id)
tagList(
div(style="display:inline-block"), # div inline-block is used to get two input boxes on the same line
# Adding input field
numericInput(inputId = ns("input_number"),
label = paste0(input_new_line(), " - % of previous line"),
value = 10,
min = 0,
max = 100
),
actionButton(inputId = ns("input_source"),
label = "No source")
)
}
# Pop-up to change the source of the funnel line input
one_line_Server <- function(id, data){
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
observeEvent(input$input_source, {
showModal(
modalDialog(
title = "Update source",
textInput(inputId = ns("new_source"), label = "Update the source",
value = "Assumption"),
actionButton(inputId = ns("add_source"),
label = "Update the source"),
"\n",
actionButton(inputId = ns("cancel"),
label = "Cancel"),
easyClose = TRUE,
footer = NULL
)
)
})
# When the user clicks "Update the source", the source is updated
observeEvent(input$add_source,{
updateActionButton(inputId = "input_source",
label = paste0("Source: ", input$new_source))
removeModal()
data$button_status[[1]] <- FALSE
})
# When the user clicks "Cancel", the modal is removed without changes
observeEvent(input$cancel,{
removeModal()
})
}
)
}
ui <- fluidPage(
div(id = "input_add_div_block"),
br(),
# Input for the new line
div(style="display:inline-block", # div inline-block is used to get two input boxes on the same line
textInput(inputId = "input_new_line",
label = "Give description of new line")
),
# Adding a new line
div(style="display:inline-block", # div inline-block is used to get two input boxes on the same line
id = "input_add_div",
actionButton("input_add", "Add new line")
)
)
server <- function(input, output, session) {
data <- reactiveValues(
lines = list(),
n = 0
)
observeEvent(input$input_add, {
n <- data$n + 1
insertUI(selector = "#input_add_div_block",
where = "beforeEnd", # insert after last element
ui = one_line_UI(id = n,
input_new_line = reactive({input$input_new_line}))
)
one_line_Server(id = n, data = data)
data$n <- n
})
}
shinyApp(ui, server)
If you want more information, you can also check out my tutorial how to dynamically add modules.
My app contains two tabSetPanel that are created based on user input (in the example below the user input is a group of radio buttons). The user input determines both the number of tabs in both tabSetPanels AND the selected tab. I also want the two tabSetPanels to display the same tab whenever user input is changed or different tab is selected in either sets. The original app contains feature that prevents me from merging the two tabSetPanels. The problem is that both tabSetPanels initially display tabs without any content.
Here is a minimal reproducible example:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
# User input for number of tabs and selected tab
radioButtons("tabSelector", "Select Number of Tabs", 1:3, 1),
br()
),
mainPanel(
uiOutput(
"set1"
),
uiOutput(
"set2"
)
)
)
)
server <- function(input, output, sesstion){
output$set1 <- renderUI({
tabs <- list()
for(i in seq_len(input$tabSelector)){
tabs[[i]] <- tabPanel(
title = paste0("tab",i),
value = i,
numericInput(
paste0("num",i),
"Number",
value = 0
)
)
}
do.call(tabsetPanel, c(tabs,
list(id = "set1",
selected = input$tabSelector)))
})
output$set2 <- renderUI({
tabs <- list()
for(i in seq_len(input$tabSelector)){
tabs[[i]] <- tabPanel(
title = paste0("tab",i),
value = i,
numericInput(
paste0("num",i),
"Number",
value = 0
)
)
}
do.call(tabsetPanel, c(tabs,
list(id = "set2",
selected = input$tabSelector)))
})
# Bind the two tabSetPanels
observeEvent(input$set1, {
updateTabsetPanel(inputId = "set2", selected = input$set1)
})
observeEvent(input$set2,{
updateTabsetPanel(inputId = "set1", selected = input$set2)
})
} # end server function
if (interactive()) {
shinyApp(ui, server)
}
Thanks in advance
The value of a tabPanel must be a character string :
output$set1 <- renderUI({
tabs <- list()
for(i in seq_len(input$tabSelector)){
tabs[[i]] <- tabPanel(
title = paste0("tab",i),
value = as.character(i),
numericInput(
paste0("num",i),
"Number",
value = 0
)
)
}
do.call(tabsetPanel, c(tabs,
list(id = "set1",
selected = as.character(input$tabSelector))
))
})
In output$set2 you don't need to set the value of the selected argument, because it will be set by the updateTabsetPanel.
Duplicated ids are not allowed in HTML, so you have to change the id paste0("num",i) of your numeric inputs to something else in one of the two tabsets.
I want to be able to add or remove new words every time I click the "Add words"/"Remove words" buttons. But I want the app to save the previously added/removed words. For example, if I add 'hello' as my first word, and then I add 'bye', I want my vector of words to be ['hello', 'bye']. If I then remove 'hello', my vector should be ['bye']
This is what I have been able to achieve so far
# Define UI ----------
ui <- fluidPage(
# Sidebar panel
sidebarLayout(
sidebarPanel(
selectInput('addrm',
label = h3('Remove or add words'),
choices = list('Remove words' = 1,
'Add words' = 2)),
textInput('words',
label = 'You can introduce multiple words separated by comma',
placeholder = 'Enter words...'),
uiOutput('button')
),
# Main panel
mainPanel(
textOutput('removeWords')
)
)
)
# Define server logic required ------------
server <- function(input, output){
output$button <- renderUI({
if (input$addrm == 1){
actionButton('button', label = 'Remove words')
} else{
actionButton('button', label = 'Add words')
}
})
stopwords <- c()
output$removeWords <- renderText({
input$button
isolate({ # Only runs when the button is clicked
stopwords <- unique(c(stopwords, unlist(strsplit(gsub(' ', '', input$words), ','))))
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
Nevermind, I was able to find what I was looking for in the following post
Just in case anyone wants the answer:
# Define UI ----------
ui <- fluidPage(
# Sidebar panel
sidebarLayout(
sidebarPanel(
selectInput('addrm',
label = h3('Remove or add words'),
choices = list('Remove words' = 1,
'Add words' = 2)),
textInput('words',
label = 'You can introduce multiple words separated by comma',
placeholder = 'Enter words...'),
uiOutput('button')
),
# Main panel
mainPanel(
textOutput('removeWords')
)
)
)
# Define server logic required ------------
server <- function(input, output){
output$button <- renderUI({
if (input$addrm == 1){
actionButton('button', label = 'Remove words')
} else{
actionButton('button', label = 'Add words')
}
})
values <- reactiveValues()
values$stopwords <- c()
output$removeWords <- renderText({
input$button
isolate({ # Only runs when the button is clicked
values$stopwords <- unique(c(values$stopwords, unlist(strsplit(gsub(' ', '', input$words), ','))))
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
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)
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))