reset selectizeInputs each time I modify a numericInput in RShiny - r

I'm learning shiny and working with a numericInput connected to many selectizeInputs.
if the numeric input equals to 1 or 2, I would like to create respectively 1 and 2 selectizeInputs and select the "i"th modality of a vector called "modalities" for each selectizeInput EDIT : and that choices = modalities[i] only (and not modalities)
if the numeric input equals to 3 or 4, I would like to create respectively 3 and 4 selectizeInputs which are connected with each other (with choices = modalities). In other words : if an item is selected in one of the selectizeinputs i would like that it disappears from the other selectizeinputs' choices.
In addition (and this is what I have trouble with) I would like to "reset" all the selected SelectizeInputs each time I modify the numericInput. I tried with the observeEvent below and I tried to use an isolate(input$ui_number) but I did not find any solution to my question because i don't understand how to do it... !
Thank you for your help !
library(shiny)
modalities <- LETTERS[1:10]
ui = tabPanel("Change modalities",
numericInput("ui_number", label = "Number of modalities",
min = 1, max = 4, value = 3),
uiOutput("renderui")
)
server = function(input, output, session) {
# Generate modalities select lists
output$renderui <- renderUI({
output = tagList()
for (i in seq_len(input$ui_number)) {
output[[i]] = selectizeInput(paste0("ui_mod_choose", i),
label = paste0("Modality ", i),
choices = modalities, multiple = TRUE)
}
return(output)
})
# if input$ui_number is modified to 3 or 4 : set selected to NULL ##### NOT WORKING
observeEvent({input$ui_number},
{
n <- input$ui_number
if(n%in%c(3,4)){
for (i in seq_len(n)) {
updateSelectizeInput(session, paste0("ui_mod_choose",i),selected=NULL)
}
}
}
)
observe({
n <- input$ui_number
if(n%in%c(1,2)){ #if n=1 or 2 => Select the "i"th modality for each selectizeInput
for (i in seq_len(n)) {
updateSelectizeInput(session, paste0("ui_mod_choose",i),
choices = modalities[i],
selected = modalities[i]
)
}
} else{ # if n=3 or 4 => Remove selected modalities from other select lists
for (i in seq_len(n)) {
vecteur <- unlist(lapply((1:n)[-i], function(i)
input[[paste0("ui_mod_choose",i)]]))
updateSelectizeInput(session, paste0("ui_mod_choose",i),
choices = setdiff(modalities, vecteur),
selected = input[[paste0("ui_mod_choose",i)]])
}
}
})
}
runApp(shinyApp(ui, server))
This issue corresponds to the following of this one :
lapply function using a numericInput parameter around an observeEvent in RShiny
EDIT2 : new try thanks to #Aurèle 's tip.
The only problem which remains is the 1:100 in lapply which can take time to load (did not find a solution to add a reactive content such as 1:input&ui_number around a conditional panel)
library(shiny)
modalities <- LETTERS[1:10]
make_conditional_selectizeInputs <- function() {
do.call(
div,
lapply(1:100, function(i)
conditionalPanel(
condition = sprintf("%d <= input.ui_number", i),
selectizeInput(sprintf("ui_mod_choose%d", i),
label = sprintf("Modality %d", i),
choices = character(0), multiple = TRUE, selected = NULL)
)
)
)
}
ui <- tabPanel(
"Change modalities",
uiOutput("rendernumeric"),
#numericInput("ui_number", label = "Number of modalities", min = 1L, max = max, value = 1L),
make_conditional_selectizeInputs()
)
server <- function(input, output, session) {
max <- 4
output$rendernumeric <- renderUI({
numericInput("ui_number", label = "Number of modalities", min = 1L, max = max, value = 1L)
})
n <- reactive({
n <- input$ui_number
if (is.null(n) || is.na(n) || !n >= 0) 0 else n
})
# Reset all
observeEvent(
eventExpr = n(),
handlerExpr = for (i in seq_len(max))
updateSelectizeInput(
session, sprintf("ui_mod_choose%d", i),
choices = if (n() %in% 1:2 && i <= n()) modalities[i] else modalities,
selected = if (n() %in% 1:2 && i <= n()) modalities[i] else NULL
)
)
all_selected <- reactive({
unlist(lapply(seq_len(max), function(i)
input[[sprintf("ui_mod_choose%d", i)]]))
})
# Update available modalities
observeEvent(
eventExpr = all_selected(),
handlerExpr = if (!n() %in% 1:2) for (i in seq_len(n())) {
x <- input[[sprintf("ui_mod_choose%d", i)]]
other_selected <- setdiff(all_selected(), x)
updateSelectizeInput(session, sprintf("ui_mod_choose%d", i),
choices = setdiff(modalities, other_selected),
selected = x)
}
)
}
runApp(shinyApp(ui, server))

Basically, you just need one more line: selected = if (n %in% 1:2) modalities[i] else NULL whenever you regenerate your selectizeInputs.
library(shiny)
modalities <- LETTERS[1:10]
ui = tabPanel("Change modalities",
numericInput("ui_number", label = "Number of modalities",
min = 1, max = 4, value = 3),
uiOutput("renderui"))
server = function(input, output, session) {
# Generate modalities select lists
output$renderui <- renderUI({
output = tagList()
n <- input$ui_number
n <- if (is.null(n) || is.na(n) || ! n >= 0) 0 else n
for (i in seq_len(n)) {
output[[i]] = selectizeInput(paste0("ui_mod_choose", i),
label = paste0("Modality ", i),
choices = if (n %in% 1:2) modalities[i] else modalities,
multiple = TRUE,
# Add this
selected = if (n %in% 1:2) modalities[i] else NULL)
}
output
})
# Remove selected modalities from other select lists
observe({
n <- isolate(input$ui_number)
if (!n %in% 1:2) for (i in seq_len(n)) {
vecteur <- unlist(lapply((1:n)[-i], function(i)
input[[paste0("ui_mod_choose",i)]]))
updateSelectizeInput(session, paste0("ui_mod_choose",i),
choices = setdiff(modalities, vecteur),
selected = input[[paste0("ui_mod_choose",i)]])
}
})
}
runApp(shinyApp(ui, server))

(This is different enough to be a separate answer).
In https://shiny.rstudio.com/articles/dynamic-ui.html, four different approaches to a dynamic UI in Shiny are suggested, ordered by difficulty:
The conditionalPanel function, which is used in ui.R and wraps a set of UI elements that need to be dynamically shown/hidden.
The renderUI function, which is used in server.R in conjunction with the uiOutput function in ui.R, lets you generate calls to UI functions and make the results appear in a predetermined place in the UI.
The insertUI and removeUI functions, which are used in server.R and allow you to add and remove arbitrary chunks of UI code (all independent from one another), as many times as you want, whenever you want, wherever you want.
Use JavaScript to modify the webpage
Your attempts use the second approach, this answer uses the first one (though it should be doable with any of them):
library(shiny)
modalities <- LETTERS[1:10]
max <- 4L
First, a helper function to build the UI. The number of selectizeInputs is no longer dynamic but fixed to max, and they're alternatively shown/hidden based on input$ui_number:
make_conditional_selectizeInputs <- function(max) {
do.call(
div,
lapply(seq_len(max), function(i)
conditionalPanel(
condition = sprintf("%d <= input.ui_number", i),
selectizeInput(sprintf("ui_mod_choose%d", i),
label = sprintf("Modality %d", i),
choices = character(0), multiple = TRUE, selected = NULL)
)
)
)
}
ui <- tabPanel(
"Change modalities",
numericInput("ui_number", label = "Number of modalities",
min = 1L, max = max, value = 1L),
make_conditional_selectizeInputs(max)
)
The server function has two reactive expressions that help modularize code but are not essential to its logic (n() and all_expected()).
There is no longer a renderUI() (the selectizeInputs are already generated once and for all).
There is an observeEvent() that takes a dependency on input$ui_number and resets all selections and choices when it changes.
The last observeEvent() takes a dependency on all input$ui_mod_choose[i] and updates all the choices whenever there is a new selection.
server <- function(input, output, session) {
n <- reactive({
n <- input$ui_number
if (is.null(n) || is.na(n) || !n >= 0) 0 else n
})
# Reset all
observeEvent(
eventExpr = n(),
handlerExpr = for (i in seq_len(max))
updateSelectizeInput(
session, sprintf("ui_mod_choose%d", i),
choices = if (n() %in% 1:2 && i <= n()) modalities[i] else modalities,
selected = if (n() %in% 1:2 && i <= n()) modalities[i] else NULL
)
)
all_selected <- reactive({
unlist(lapply(seq_len(max), function(i)
input[[sprintf("ui_mod_choose%d", i)]]))
})
# Update available modalities
observeEvent(
eventExpr = all_selected(),
handlerExpr = if (!n() %in% 1:2) for (i in seq_len(n())) {
x <- input[[sprintf("ui_mod_choose%d", i)]]
other_selected <- setdiff(all_selected(), x)
updateSelectizeInput(session, sprintf("ui_mod_choose%d", i),
choices = setdiff(modalities, other_selected),
selected = x)
}
)
}
Essentially it differs from the second approach (with renderUI) in that it removes part of the dependency between input$ui_number and the input$ui_mod_choose[i], at least when they're generated (but there's a residual dependency when they're reset because of updateSelectizeInput. I'm not completely clear why I could make it work with this approach and not with renderUI though).
runApp(shinyApp(ui, server))
This is a screenshot of the reactlog, though it doesn't show the whole picture, because of the necessary impurity of updateSelectizeInput() that mixes the UI and server logics, and creates circular dependencies that can be tricky to reason about:

Related

Removing Table entries using remove UI in Shiny

I am populating a table by using Insert UI elements. I also want to delete both table entries and the inserted panels by using the remove UI elements.
I could delete the panels but as you can see in my demo App the corresponding table values are not deleted and the length of the table remains the same even after clicking the delete button.
How can I delete both the panels and their corresponding table values at the same time?
Why table values are not getting deleted?
library(shiny)
library(tidyverse)
DT <- data.frame(Year = c(1980,1985,1985,1990,1990,1995),
Events = c("Storm", "Earthquake", "Flood", "Draught",
"Earthquake", "Earthquake"),
Area_Loss = c(100, 200, 400, 500, 450,300),
Money = c(1000,2000,3000,4000,5000,6000))
ui <- fluidPage( h4("Updating InserUIs",
selectInput("events","Events",choices=as.character(DT$Events)),
tags$div(id = "Panels"),
actionButton("add","Add"),
tableOutput("table"),
verbatimTextOutput("text")
))
server <- function(session, input, output){
# Reactive values for the number of input panels
vals <- reactiveValues(btn = list(), observers = list())
observeEvent(input$add,ignoreNULL = FALSE,{
l <- length(vals$btn) +1
# Add Panels
for(i in l){
vals$btn[[i]]= insertUI(selector = "#Panels",
ui = splitLayout(id = paste0("Selection",i), where ="afterEnd",
cellWidths = rep("33.33%",3),
selectInput(paste0("year",i), "Year", choices = DT$Year,
selected = ""),
numericInput(paste0("area",i), "Area", min = 0, max = 10000,
value ="", step = 1),
numericInput(paste0("money",i), "Money", min = 0, max = 10000,
value = "", step =1),
div(id ="delete_div",actionButton(paste0("delete",i), "Delete"))
))}
# Update panels
for(i in l){
vals$observers = lapply(l, function(i)
observeEvent(input[[paste0("year",i)]],{
updateNumericInput(session,paste0("area",i),
"Area",min= 0, max= 50000,value = DT$Area_Loss
[DT$Year == input[[paste0("year",i)]]& DT$Events==
input$events] ,step = 0.1)
}))}
for(i in l){
vals$observers = lapply(l, function(i)
observeEvent(input[[paste0("year",i)]],{
updateNumericInput(session,paste0("money",i),
"Money",min= 0, max= 50000,value = DT$Money
[DT$Year == input[[paste0("year",i)]]& DT$Events==
input$events] ,step = 0.1)
}))}
# Delete Panels
for(i in l){
observeEvent(input[[paste0("delete",i)]],{
shiny::removeUI(selector = paste0("#Selection",i))
i <- length(vals$btn) - 1
})}
})
# Reactive table generated from the user inputs
Table <- reactive({
l <- 1:length(vals$btn)
for(i in l){
Year <- unlist(lapply(l, function(i)input[[paste0("year",i)]]))
Area <- unlist(lapply(l, function(i)input[[paste0("area",i)]]))
Money <- unlist(lapply(l, function(i)input[[paste0("money",i)]]))
}
DF0 <- data.frame(Event = input$events,
Year = Year,
Area_loss = Area,
Money = Money
)
DF0
})
# Visualizing the raective table
output$table <- renderTable({
Table()
})
}
shinyApp(ui,server)
Thanks all of you in advance, any suggestion will help me to progress in my app.
I think your problem can be quiet elegantly solved with modules. See comments in the code for details.
library(shiny)
library(dplyr)
DT <- data.frame(Year = c(1980,1985,1985,1990,1990,1995),
Events = c("Storm", "Earthquake", "Flood", "Draught",
"Earthquake", "Earthquake"),
Area_Loss = c(100, 200, 400, 500, 450,300),
Money = c(1000,2000,3000,4000,5000,6000))
##############################Module#############################
## a module consists of all elements which belong together
## i.e. year, area, money and delete button
## take note about the ns() construct which allows for
## namespacing and through this mechanism we can have several
## instances of this module
YAM_ui <- function(id) {
ns <- NS(id)
fluidRow(
id = id,
h3(id),
column(width = 3,
selectInput(ns("year"),
"Year",
DT$Year,
"")),
column(width = 4,
numericInput(ns("area"),
"Area",
0,
0,
10000,
1)),
column(width = 4,
numericInput(ns("money"),
"Money",
0,
0,
10000,
1)),
column(width = 1,
actionButton(ns("delete"), "Delete"))
)
}
## in the server you can access the elements simply by input$element_name
## we have one input reactive (event) which comes from the main app and
## holds the value of the event selectInput
## we return
## - a killSwitch to signal the main app to delete this module
## - a reactive which returns the data from all inputs organized in a data frame
YAM_server <- function(input, output, session, event) {
killMe <- reactiveVal(FALSE)
observe({
req(input$year)
req(event())
updateNumericInput(session,
"area",
min = 0,
max = 50000,
value = DT$Area_Loss[DT$Year == input$year &
DT$Events == event()] ,
step = 0.1)
updateNumericInput(session,
"money",
min = 0,
max = 50000,
value = DT$Money[DT$Year == input$year &
DT$Events == event()] ,
step = 0.1)
})
get_data <- reactive({
req(!is.null(input$year), !is.null(input$area), !is.null(input$money), event())
data.frame(event = event(),
year = input$year,
area = ifelse(input$area == "", NA, input$area),
money = ifelse(input$money == "", NA, input$money))
})
observeEvent(input$delete,
killMe(TRUE))
return(list(delete = killMe,
get_data = get_data))
}
##############################MainApp##############################
ui <- fluidPage(
titlePanel("Modules"),
sidebarLayout(
sidebarPanel(
h4("Updating Inserted UIs"),
selectInput("events",
"Events",
unique(DT$Events)),
actionButton("add",
"Add"),
tableOutput("table")
),
mainPanel(
tags$div(id = "Panels")
)
)
)
## in the main App we have
## - a reactive (handlers) which holds all reactives of all the modules
## - a list (observers) where we create (and delete) observers for the kill
## switch
## When we add a row, we use insertUI to create the html and callModule
## to switch on the modules server logic. We pass the event reactive to
## the module to make it available within the module.
## When we observe a press to the delete button, we remove the handler
## from the lists and remove the corresponding html via removeUI.
## The data table is then updated automatically, because we removed the handler
## and it is not seen in the loop
## To get the table all we have to do is to loop through all handlers and
## call the get_data reactive from the modules to get the data
server <- function(input, output, session) {
handlers <- reactiveVal(list())
observers <- list()
n <- 1
get_event <- reactive({
input$events
})
observeEvent(input$add, {
id <- paste0("row_", n)
n <<- n + 1
insertUI("#Panels",
"beforeEnd",
YAM_ui(id)
)
new_handler <- setNames(list(callModule(YAM_server,
id,
get_event)),
id)
handler_list <- c(handlers(), new_handler)
handlers(handler_list)
})
observe({
hds <- handlers()
req(length(hds) > 0)
new <- setdiff(names(hds),
names(observers))
obs <- setNames(lapply(new, function(n) {
observeEvent(hds[[n]]$delete(), {
removeUI(paste0("#", n))
hds <- handlers()
hds[n] <- NULL
handlers(hds)
observers[n] <<- NULL
}, ignoreInit = TRUE)
}), new)
observers <<- c(observers, obs)
})
output$table <- renderTable({
hds <- req(handlers())
req(length(hds) > 0)
tbl_list <- lapply(hds, function(h) {
h$get_data()
})
do.call(rbind, tbl_list)
})
}
shinyApp(ui, server)
I agree with #thothal that modules help when adding and removing sections of UI and the corresponding data. I've taken a lot of inspiration from their answer and come up with a slightly cleaner (IMHO) implementation.
I've only modified the final server function, where I have managed to do away with the need to keep a list of observers and have captured most of the lifecycle functionality into the add_module function
# utility to hide away the mess of updating the reactiveVal(list())
update_values <- function(values, name, value) {
vals <- values()
vals[[name]] <- value
values(vals)
}
add_module <- function(values, name, server, delete_hook = NULL, remove_selector = NULL) {
# add module server's return to values list
update_values(values, name, server)
# if module has a reactive we should monitor for deleting, do so
if (!is.null(delete_hook)) {
observeEvent(
server[[delete_hook]](), {
removeUI(selector = remove_selector) # remove the ui
update_values(values, name, NULL) # remove the server from our values list
},
ignoreInit = TRUE,
once = TRUE
)
}
}
server <- function(input, output, session) {
handlers <- reactiveVal(list())
get_event <- reactive({
input$events
})
# new
observeEvent(input$add, {
id <- paste0("row_", input$add)
insertUI("#Panels", "beforeEnd", YAM_ui(id))
add_module(
handlers,
name = id,
server = callModule(YAM_server, id, get_event),
delete_hook = "delete",
remove_selector = paste0("#", id)
)
})
# unchanged
output$table <- renderTable({
hds <- req(handlers())
req(length(hds) > 0)
tbl_list <- lapply(hds, function(h) {
h$get_data()
})
do.call(rbind, tbl_list)
})
}
shinyApp(ui, server)

R shiny puzzling warning: Input to asJSON(keep_vec_names=TRUE) is a named vector

I have written a shiny app that permits the user to amend individual rows of a dataframe but when I try to include an option to append new rows I get this warning on the console:
Input to asJSON(keep_vec_names=TRUE) is a named vector. In a future version of jsonlite, this option will not be supported, and named vectors will be translated into arrays instead of objects. If you want JSON object output, please use a named list instead. See ?toJSON.
and in a text input box that should contain an item from one column of the data frame the following appears instead:
[object Object]
There are a few answers here that refer to the warning message but in different conditions than apply in my case, and they appear to have little in common with each other apart from the warning message.
Here is my app for amending the dataframe. It works perfectly.
require(shiny)
in.df <- data.frame(name = c("Alice","Bob","Charles"),
age = c(22, 25, 36))
rownames(in.df) <- NULL
runApp(
list(
ui = fluidPage(
sidebarPanel(
numericInput("line", "Line number", value = 1),
textInput("name", "Name:"),
numericInput("age", "Age:", value = 25),
actionButton("amendButton", "Amend an entry")
),
mainPanel(
tableOutput("table"))
),
server = function(input, output, session){
values <- reactiveValues()
values$df <- in.df
current_line <- reactive({
il <- input$line
nr <- nrow(values$df)
if(il > nr){
return(nr)
} else if(il <= 0){
return(1)
} else{
return(il)
}
})
amendData <- observe({
if(input$amendButton > 0){
newLine <- isolate(c(input$name, input$age))
values$df <- isolate(values$df[- current_line(), ])
isolate(values$df <- rbind(as.matrix(values$df), unlist(newLine)))
values$df <- values$df[order(values$df[,1]),]
}
})
observe({
updateTextInput(session = session,
inputId = 'name',
value = values$df[unlist( current_line()),1]
)
updateNumericInput(session = session,
inputId = 'age',
value = values$df[unlist( current_line()),2]
)
updateNumericInput(session = session ,
inputId = 'line',
value = current_line()
)
})
output$table <- renderTable(values$df )
}
)
)
It seemed to me that it would be a simple matter to add an 'append' option in the following way:
Add a new action button
actionButton("appendButton", "Append an entry")
Include a corresponding handler that can be very similar indeed to the handler for the addButton:
addData <- observe({
if(input$appendButton > 0){
newLine <- isolate(c(input$name, input$age))
isolate(values$df <- rbind(as.matrix(values$df), unlist(newLine)))
values$df <- values$df[order(values$df[,1]),]
}
})
The only difference of substance between the two handlers is that the new one does not need the line
values$df <- isolate(values$df[- current_line(), ])
because in the append case no old row is being removed.
But it does not work: I get the warning and the odd change to the text input box that I described.
In shiny 1.6 I got a running app after I changed amendData <- observe to amendData <- observeEvent. Otherwise the code got stuck in an infinite loop.
However, in order to be able to add new rows I had to change reactive value current_line. The code always resets it to an existing row so that one can never add new entries.
I had changed current_line so that it also allowed it to be nrow + 1 and cleared the numeric input fields when current_line was larger than the number of rows.
Now, I finally saw the situation that was described in the question.
It was caused by values$df <- rbind(as.matrix(values$df), unlist(newLine)). R added the new row with a name. The named rows of the data frame seemed to be the problem when sent to the UI. My guess is that this is a problem deeply buried in the reactive messaging system of Shiny.
require(shiny)
in.df <- data.frame(name = c("Alice","Bob","Charles"),
age = c(22L, 25L, 36L))
rownames(in.df) <- NULL
runApp(
list(
ui = fluidPage(
sidebarPanel(
numericInput("line", "Line number", value = 1),
textInput("name", "Name:"),
numericInput("age", "Age:", value = 25),
actionButton("amendButton", "Amend an entry")
),
mainPanel(
tableOutput("table"))
),
server = function(input, output, session){
values <- reactiveValues()
values$df <- in.df
current_line <- reactive({
il <- req(input$line)
nr <- nrow(values$df)
if(il > nr){
return(nr+1)
} else if (il <= 0){
return(1)
} else {
return(il)
}
})
amendData <- observeEvent(input$amendButton, {
isolate({
newLine <- c(input$name, as.numeric(input$age))
values$df <- values$df[- current_line(), ]
values$df <- rbind(values$df, unname(newLine))
})
values$df <- values$df[order(values$df[,1]),]
})
observe({
updateNumericInput(session = session, inputId = 'line',
value = current_line())
if (current_line() <= nrow(values$df)) {
updateNumericInput(session = session, inputId = 'age',
value = values$df[current_line(), 2])
updateTextInput(session = session, inputId = 'name',
value = values$df[current_line(), 1])
}
else {
updateNumericInput(session = session, inputId = 'age', value = "")
updateNumericInput(session = session, inputId = 'name', value = "")
}
})
output$table <- renderTable( values$df )
}
)
)

lapply function using a numericInput parameter around an observeEvent in RShiny

I would like to create many multiple selectize inputs which are connected with each other. In other words : if an item is selected in one of the selectizeinputs i would like that it disappears from the other selectizeinputs' choices. In addition, i would like that the number of selectize inputs corresponds to the number selected in a numericinput.
The example below is working. The only question I have left is on the following line :
X = 1:100, ####### QUESTION HERE
Instead of 1:100, i would like to put something like 1:input$ui_number but I have the following error in R :
Error in .getReactiveEnvironment()$currentContext() : Operation not allowed without an active reactive context.
And if I put a "reactive" or an "observe" function around the lapply, the observeEvent does not work anymore. Any trick for me ?
Thank you for your help !
modalities <- LETTERS[1:10]
library(shiny)
app <- shinyApp(
ui = tabPanel("Change modalities",
numericInput("ui_number", label="Number of modalities",min = 1, max = 4, value=3),
uiOutput("renderui")
),
server = function(input, output, session) {
output$renderui <- renderUI({
output = tagList()
for(i in 1:input$ui_number){
output[[i]] = tagList()
output[[i]][[1]] = selectizeInput(paste0("ui_mod_choose",i), label=paste0("Modality ",i),choices=modalities, multiple = TRUE)
}
return(output)
})
lapply(
X = 1:100, ####### QUESTION HERE
FUN = function(j){
observeEvent({
input[[paste0("ui_mod_choose",j)]]
},
{
sapply(1:input$ui_number,function(i){
vecteur <- do.call(c,lapply((1:input$ui_number)[-i],function(i){input[[paste0("ui_mod_choose",i)]]}))
updateSelectizeInput(session,paste0("ui_mod_choose",i),choices= modalities[!modalities %in% vecteur],selected = input[[paste0("ui_mod_choose",i)]])
})
},
ignoreNULL = FALSE)
}
)
observeEvent({
input$ui_num
},
{
sapply(1:nput$ui_num,function(i){
updateSelectizeInput(session,paste0("ui_mod_choose",i),choice= modalities,selected=NULL)
})
}
)
}
)
runApp(app)
You could have a single observe() instead of multiple observeEvent():
library(shiny)
modalities <- LETTERS[1:10]
ui = tabPanel("Change modalities",
numericInput("ui_number", label = "Number of modalities",
min = 1, max = 4, value = 3),
uiOutput("renderui"))
server = function(input, output, session) {
# Generate modalities select lists
output$renderui <- renderUI({
output = tagList()
for (i in seq_len(input$ui_number)) {
output[[i]] = selectizeInput(paste0("ui_mod_choose", i),
label = paste0("Modality ", i),
choices = modalities, multiple = TRUE)
}
return(output)
})
# Remove selected modalities from other select lists
observe({
n <- isolate(input$ui_number)
for (i in seq_len(n)) {
vecteur <- unlist(lapply((1:n)[-i], function(i)
input[[paste0("ui_mod_choose",i)]]))
updateSelectizeInput(session, paste0("ui_mod_choose",i),
choices = setdiff(modalities, vecteur),
selected = input[[paste0("ui_mod_choose",i)]])
}
})
}
runApp(shinyApp(ui, server))

probabilistic multiple choice test, sliderInputs sum to 1 constraint

I'm developing a small shinyapp for conducting probabilistic multiple choice tests, see Bernardo, 1997. For each question in the test, there will be say 4 possible answers. Each participant should assign som values to each alternative reflecting their degree of belief that each alternative is the correct answer. I'm recording this input using the sliderInput function. Since the four probabilites must sum to 1, I rescale all four probabilites of the current question (a row in a matrix stored as prob <- reactiveValues( )) to meet this constraint. This is triggered by observeEvent(input$p1, ) etc.
Once these probabilities changes this triggers changes in the four sliderInput put inside renderUI( ) inside the server function such that all sliders are updated. This in turn triggers further calls to the function updating prob but since the probabilities at this point already sum to 1, prob remain unchanged so no further changes to the sliders should occur. You can see for yourself by running the app hosted on shinyapps.io.
This usually works very well, except that in some quite rare cases an infinite loop is set off such that all four sliders keep changing forever. I believe this happens if the user makes a second change to one of the sliders before the three other sliders have had time to adjust.
So my question is really if there is some way of avoiding this loop or if there is some better way of implementing the above idea. I noticed that there is also a updateSliderInput function but I don't really see how this might help solve the problem.
Update: I believe the solution to a similar question involving just two sliders proposed in this thread suffers from the same problem due to the mutual dependency between slider1 and slider2.
library(shiny)
digits=3
step <- .1^digits
# Dummy questions and alternatives
n <- 5
# Miscellaneous functions
updateprob <- function(oldprobs, new, i) {
cat(oldprobs, new, i)
if (new==oldprobs[i]) {
cat("-\n")
oldprobs
} else {
newprobs <- rep(0,4)
oldprobs <- oldprobs + 1e-6 # hack to avoid 0/0 = NaN in special cases
newprobs[-i] <- round(oldprobs[-i]/sum(oldprobs[-i])*(1-new),digits=digits)
newprobs[i] <- new
cat("*\n")
newprobs
}
}
# wrapper function around sliderInput
probsliderInput <- function(inputId,value,submitted=FALSE) {
if (!submitted)
sliderInput(inputId=inputId,
value=value,
label=NULL,
min=0,
max=1,
step=step,
round=-digits,
ticks=FALSE)
}
server <- function(input, output) {
# Initialize the quiz here, possibly permute the quiz
prob <- reactiveValues(prob=matrix(rep(.25,4*n),n,4)) # current choice of probabilities
question <- reactiveValues(i=1) # question number
# Actions to take if pressing next and previous buttons
observeEvent(input$nextquestion,{question$i <- min(question$i+1,n)})
observeEvent(input$previousquestion,{question$i <- max(question$i-1,1)})
# If any of the probability sliders change, then recalculate probabilities to satisfy sum to 1 constraint
observeEvent(input$p1,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p1, 1)
)
observeEvent(input$p2,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p2, 2)
)
observeEvent(input$p3,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p3, 3)
)
observeEvent(input$p4,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p4, 4)
)
# If the probabilities change, update the sliders
output$p1ui <- renderUI({
probsliderInput("p1",prob$prob[question$i,1])
})
output$p2ui <- renderUI({
probsliderInput("p2",prob$prob[question$i,2])
})
output$p3ui <- renderUI({
probsliderInput("p3",prob$prob[question$i,3])
})
output$p4ui <- renderUI({
probsliderInput("p4",prob$prob[question$i,4])
})
# Render the buttons sometimes greyed out
output$previousbutton <- renderUI({
actionButton("previousquestion",icon=icon("angle-left"),label="Previous",
style=if (question$i > 1) "color: #000" else "color: #aaa")
})
output$nextbutton <- renderUI({
actionButton("nextquestion",icon=icon("angle-right"),label="Next",
style=if (question$i < n) "color: #000" else "color: #aaa")
})
# Current question number
output$number <- renderText(paste("Question",question$i))
}
ui <- fluidPage(
uiOutput("previousbutton", inline = TRUE),
uiOutput("nextbutton", inline = TRUE),
textOutput("number"),
uiOutput("p1ui"),
uiOutput("p2ui"),
uiOutput("p3ui"),
uiOutput("p4ui")
)
shinyApp(ui=ui , server=server)
You can suspend() the sliders until everything is recalculated and resume() them afterwards:
library(shiny)
digits=3
step <- .1^digits
# Dummy questions and alternatives
n <- 5
# Miscellaneous functions
updateprob <- function(oldprobs, new, i) {
cat(oldprobs, new, i)
if (new==oldprobs[i]) {
cat("-\n")
oldprobs
} else {
newprobs <- rep(0,4)
oldprobs <- oldprobs + 1e-6 # hack to avoid 0/0 = NaN in special cases
newprobs[-i] <- round(oldprobs[-i]/sum(oldprobs[-i])*(1-new),digits=digits)
newprobs[i] <- new
cat("*\n")
newprobs
}
}
# new functions to suspend and resume a list of observers
suspendMany <- function(observers) invisible(lapply(observers, function(o) o$suspend()))
resumeMany <- function(observers) invisible(lapply(observers, function(o) o$resume()))
# wrapper function around sliderInput
probsliderInput <- function(inputId,value,submitted=FALSE) {
if (!submitted)
sliderInput(inputId=inputId,
value=value,
label=NULL,
min=0,
max=1,
step=step,
round=-digits,
ticks=FALSE)
}
server <- function(input, output) {
# Initialize the quiz here, possibly permute the quiz
prob <- reactiveValues(prob=matrix(rep(.25,4*n),n,4),
ready = F) # current choice of probabilities
question <- reactiveValues(i=1) # question number
# Actions to take if pressing next and previous buttons
observeEvent(input$nextquestion,{question$i <- min(question$i+1,n)})
observeEvent(input$previousquestion,{question$i <- max(question$i-1,1)})
# If any of the probability sliders change, then recalculate probabilities to satisfy sum to 1 constraint
# We put all observers in a list to handle them conveniently
observers <- list(
observeEvent(input$p1,
{
suspendMany(observers)
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p1, 1)
resumeMany(observers)
}
),
observeEvent(input$p2,
{
suspendMany(observers)
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p2, 2)
resumeMany(observers)
}
),
observeEvent(input$p3,
{
suspendMany(observers)
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p3, 3)
resumeMany(observers)
}
),
observeEvent(input$p4,
{
suspendMany(observers)
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p4, 4)
resumeMany(observers)
}
)
)
# If the probabilities change, update the sliders
output$p1ui <- renderUI({
probsliderInput("p1",prob$prob[question$i,1])
})
output$p2ui <- renderUI({
probsliderInput("p2",prob$prob[question$i,2])
})
output$p3ui <- renderUI({
probsliderInput("p3",prob$prob[question$i,3])
})
output$p4ui <- renderUI({
probsliderInput("p4",prob$prob[question$i,4])
})
# Render the buttons sometimes greyed out
output$previousbutton <- renderUI({
actionButton("previousquestion",icon=icon("angle-left"),label="Previous",
style=if (question$i > 1) "color: #000" else "color: #aaa")
})
output$nextbutton <- renderUI({
actionButton("nextquestion",icon=icon("angle-right"),label="Next",
style=if (question$i < n) "color: #000" else "color: #aaa")
})
# Current question number
output$number <- renderText(paste("Question",question$i))
}
ui <- fluidPage(
uiOutput("previousbutton", inline = TRUE),
uiOutput("nextbutton", inline = TRUE),
textOutput("number"),
uiOutput("p1ui"),
uiOutput("p2ui"),
uiOutput("p3ui"),
uiOutput("p4ui")
)
shinyApp(ui=ui , server=server)
The problem you describes comes from a observer loop triggered when updateprob is called. As #AEF is saying you can either suspend the observers in your server.R code or you can disable the event propagation using Javascript.
I see that you do a lot of manually defining sliders in your server.R code so here's a answer where the number of questions and number of sliders are dynamic:
library(shiny)
digits=3
step <- .1^digits
# Dummy questions and alternatives
num.questions <- 6
num.sliders <- sample(2:8, num.questions) # Change to, rep(n, num.questions) for same amount of sliders
# Helper function to calculate new values for sliders
updateprob <- function(oldprobs, new, i) {
oldprobs <- oldprobs + 1e-6 # hack to avoid 0/0 = NaN in special cases
ret <- rep(0,length(oldprobs))
ind.other <- c(1:length(oldprobs))[! 1:length(oldprobs) %in% i]
sum.others <- sum( oldprobs[ind.other] )
range.left <- 1 - new
ret[i] <- new
for( n in ind.other ){
ret[n] <- ( oldprobs[n] * range.left) /sum.others
}
return(ret)
}
# wrapper function around sliderInput
probsliderInput <- function(inputId,value,submitted=FALSE) {
if (!submitted)
sliderInput(inputId=inputId,
value=value,
label=NULL,
min=0,
max=1,
step=step,
round=-digits,
ticks=FALSE)
}
# Helper function, generates HTML for all sliders
generateSliders <- function(id, n){
sliders <- lapply(1:n, function(i){
probsliderInput(sprintf("q%ss%d",id,i),1/n)
})
do.call(fluidRow, sliders)
}
# Generate observers for all sliders and bind a callback to them
generateObservers <- function(id, n, input, session, callback){
lapply(1:n,function(i){
c.id <- sprintf("q%ss%d",id, i)
print(sprintf("Observer for slider with id %s generated",c.id))
observeEvent(input[[ sprintf("q%ss%d",id, i) ]],{
do.call( callback, list(id, n, i, input, session) )
})
})
}
getSlidersValues <- function(id, n, input){ # Get all slider values
unlist(lapply(1:n,function(i){
input[[sprintf("q%ss%d",id,i)]]
}))
}
setSliderValues <-function(id, ns, session, new.vals){ # Set all slider values
suspendMany(observers)
for(i in 1:ns){
local({
il <- i
updateSliderInput( session, sprintf("q%ss%d",id,il),value=new.vals[il])
})
}
resumeMany(observers)
}
# Callbackfunction for all sliders, triggers the change of all slider values
normalizeSliders <- function(id, nt, nc, input, session){
print(sprintf("[q%ss%d] Slider %d moved, total: %d, l: %d",id,nc,nc, nt,length(observers)))
vals <- getSlidersValues(id, nt, input)
new.vals <- updateprob(vals, input[[sprintf("q%ss%d",id, nc)]],nc)
# Not necessary to suspend observers but helps in reducing number of function calls
suspendMany(observers)
for(i in 1:nt){
updateSliderInput( session, sprintf("q%ss%d",id,i),value=new.vals[i])
}
resumeMany(observers)
}
# Thanks to #AEF
suspendMany <- function(observers) invisible(lapply(observers, function(o) o$suspend()))
resumeMany <- function(observers) invisible(lapply(observers, function(o) o$resume()))
initiateProbs <- function(ns){
lapply(ns,function(i){
rep( 1/i, i)
})
}
# server.R
server <- function(input, output, session) {
# matrix(rep(1/num.sliders,num.sliders*num.questions),num.questions,num.sliders)
prob <- reactiveValues( prob= initiateProbs(num.sliders) )
observers <- NULL
observeEvent(input$questionNum, {
q.num <- as.character( input$questionNum )
cns <- num.sliders[[input$questionNum]]
sliders <- generateSliders( q.num, cns ) # Generate sliders
observers <<- generateObservers( q.num, cns, input, session, normalizeSliders) # Generate observers and bind callbacks to all sliders
output$sliders <- renderUI({ sliders })
})
# ------ Toggle question observers --------
observeEvent(input$previousquestion,{
cns <- num.sliders[[input$questionNum]]
if (input$questionNum <= 1) return()
prob$prob[[input$questionNum]] <- getSlidersValues( as.character( input$questionNum ), cns ,input) # Save probability matrix
updateNumericInput(session, "questionNum", value=input$questionNum-1) # Update hidden question counter field
})
observeEvent(input$nextquestion,{
cns <- num.sliders[[input$questionNum]]
if (input$questionNum >= num.questions) return()
prob$prob[[input$questionNum]] <- getSlidersValues( as.character( input$questionNum ), cns,input) # Save probability matrix
updateNumericInput(session, "questionNum", value=input$questionNum+1) # Update hidden question counter field
})
# Triggered on changing question number
observeEvent(input$questionNum,{
# Not necessary to suspend observers but helps in reducing number of function calls
suspendMany(observers)
setSliderValues( as.character( input$questionNum ), num.sliders[[input$questionNum]], session, prob$prob[[input$questionNum]]) # Update sliders from probability matrix
resumeMany(observers)
})
output$number <- renderText(paste("Question", input$questionNum)) # Show question number
}
# ui.R
ui <- fluidPage(
actionButton("previousquestion",icon=icon("angle-left"),label="Previous",
style="color: #000"),
actionButton("nextquestion",icon=icon("angle-right"),label="Next",
style="#000"),
uiOutput("nextbutton", inline = TRUE),
textOutput("number"),
uiOutput('sliders'),
div(numericInput('questionNum','Hidden',1), style="visibility: hidden;")
)
shinyApp(ui=ui , server=server)
Here I'm simply first looping to create the actual HTML elements, then I'm assigning observers to them. The observers have a callback function which is called each time the observer fires.
(I think) I managed to fix the infinite loop of readjusting by adding an actionButton for each slider. Now the user adjusts a slider, and then hits the appropriate recalculate button at which point the sliders update, instead of the sliders constantly trying to update themselves.
Having the four buttons isn't the prettiest and there might be a way to make it clearer what the user has to do, but all the functionality is there.
library(shiny)
digits=3
step <- .1^digits
# Dummy questions and alternatives
n <- 5
# Miscellaneous functions
updateprob <- function(oldprobs, new, i) {
cat(oldprobs, new, i)
if (new==oldprobs[i]) {
cat("-\n")
oldprobs
} else {
newprobs <- rep(0,4)
oldprobs <- oldprobs + 1e-6 # hack to avoid 0/0 = NaN in special cases
newprobs[-i] <- round(oldprobs[-i]/sum(oldprobs[-i])*(1-new),digits=digits)
newprobs[i] <- new
cat("*\n")
newprobs
}
}
# wrapper function around sliderInput
probsliderInput <- function(inputId,value,submitted=FALSE) {
if (!submitted)
sliderInput(inputId=inputId,
value=value,
label=NULL,
min=0,
max=1,
step=step,
round=-digits,
ticks=FALSE)
}
server <- function(input, output) {
# Initialize the quiz here, possibly permute the quiz
prob <- reactiveValues(prob=matrix(rep(.25,4*n),n,4)) # current choice of probabilities
question <- reactiveValues(i=1) # question number
# Actions to take if pressing next and previous buttons
observeEvent(input$nextquestion,{question$i <- min(question$i+1,n)})
observeEvent(input$previousquestion,{question$i <- max(question$i-1,1)})
# If the user presses the actionButton, then recalculate probabilities to satisfy sum to 1 constraint
observeEvent(input$recalc1,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p1, 1)
)
observeEvent(input$recalc2,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p2, 2)
)
observeEvent(input$recalc3,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p3, 3)
)
observeEvent(input$recalc4,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p4, 4)
)
# If the probabilities change, update the sliders
output$p1ui <- renderUI({
probsliderInput("p1",prob$prob[question$i,1])
})
output$p2ui <- renderUI({
probsliderInput("p2",prob$prob[question$i,2])
})
output$p3ui <- renderUI({
probsliderInput("p3",prob$prob[question$i,3])
})
output$p4ui <- renderUI({
probsliderInput("p4",prob$prob[question$i,4])
})
# Render the buttons sometimes greyed out
output$previousbutton <- renderUI({
actionButton("previousquestion",icon=icon("angle-left"),label="Previous",
style=if (question$i > 1) "color: #000" else "color: #aaa")
})
output$nextbutton <- renderUI({
actionButton("nextquestion",icon=icon("angle-right"),label="Next",
style=if (question$i < n) "color: #000" else "color: #aaa")
})
# Current question number
output$number <- renderText(paste("Question",question$i))
}
ui <- fluidPage(
uiOutput("previousbutton", inline = TRUE),
uiOutput("nextbutton", inline = TRUE),
textOutput("number"),
uiOutput("p1ui"),
actionButton(inputId = "recalc1", label = "Recalculate sliders"),
uiOutput("p2ui"),
actionButton(inputId = "recalc2", label = "Recalculate sliders"),
uiOutput("p3ui"),
actionButton(inputId = "recalc3", label = "Recalculate sliders"),
uiOutput("p4ui"),
actionButton(inputId = "recalc4", label = "Recalculate sliders")
)
shinyApp(ui=ui , server=server)
This is one option. Update the sliders only when value has changed, using updateSelectInput
library(shiny)
digits=3
step <- .1^digits
# Dummy questions and alternatives
n <- 5
# Miscellaneous functions
updateprob <- function(oldprobs, new, i) {
cat(oldprobs, new, i)
if (new==oldprobs[i]) {
cat("-\n")
oldprobs
} else {
newprobs <- rep(0,4)
oldprobs <- oldprobs + 1e-6 # hack to avoid 0/0 = NaN in special cases
newprobs[-i] <- round(oldprobs[-i]/sum(oldprobs[-i])*(1-new),digits=digits)
newprobs[i] <- new
cat("*\n")
newprobs
}
}
# wrapper function around sliderInput
probsliderInput <- function(inputId,value,submitted=FALSE) {
if (!submitted)
sliderInput(inputId=inputId,
value=value,
label=NULL,
min=0,
max=1,
step=step,
round=-digits,
ticks=FALSE)
}
server <- function(input, output, session) {
# Initialize the quiz here, possibly permute the quiz
prob <- reactiveValues(prob=matrix(rep(.25,4*n),n,4)) # current choice of probabilities
question <- reactiveValues(i=1) # question number
# Actions to take if pressing next and previous buttons
observeEvent(input$nextquestion,{question$i <- min(question$i+1,n)})
observeEvent(input$previousquestion,{question$i <- max(question$i-1,1)})
# If any of the probability sliders change, then recalculate probabilities to satisfy sum to 1 constraint
observeEvent(input$p1,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p1, 1)
)
observeEvent(input$p2,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p2, 2)
)
observeEvent(input$p3,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p3, 3)
)
observeEvent(input$p4,
prob$prob[question$i,] <- updateprob(prob$prob[question$i,], input$p4, 4)
)
observeEvent(prob$prob ,{
if (is.null(input$p1 ) || is.null(input$p2 ) || is.null(input$p3 ) || is.null(input$p4 ) ) { return(NULL)}
if ( prob$prob[question$i,1] != input$p1) {
updateSelectInput(session = session, inputId = 'p1', selected = prob$prob[question$i,1] )
}
if ( prob$prob[question$i,2] != input$p2) {
updateSelectInput(session = session, inputId = 'p2', selected = prob$prob[question$i,2] )
}
if ( prob$prob[question$i,3] != input$p3) {
updateSelectInput(session = session, inputId = 'p3', selected = prob$prob[question$i,3] )
}
if ( prob$prob[question$i,4] != input$p4) {
updateSelectInput(session = session, inputId = 'p4', selected = prob$prob[question$i,4] )
}
})
# If the probabilities change, update the sliders
output$p1ui <- renderUI({
isolate(probsliderInput("p1",prob$prob[question$i,1]))
})
output$p2ui <- renderUI({
isolate( probsliderInput("p2",prob$prob[question$i,2]))
})
output$p3ui <- renderUI({
isolate(probsliderInput("p3",prob$prob[question$i,3]))
})
output$p4ui <- renderUI({
isolate(probsliderInput("p4",prob$prob[question$i,4]))
})
# Render the buttons sometimes greyed out
output$previousbutton <- renderUI({
actionButton("previousquestion",icon=icon("angle-left"),label="Previous",
style=if (question$i > 1) "color: #000" else "color: #aaa")
})
output$nextbutton <- renderUI({
actionButton("nextquestion",icon=icon("angle-right"),label="Next",
style=if (question$i < n) "color: #000" else "color: #aaa")
})
# Current question number
output$number <- renderText(paste("Question",question$i))
}
ui <- fluidPage(
uiOutput("previousbutton", inline = TRUE),
uiOutput("nextbutton", inline = TRUE),
textOutput("number"),
uiOutput("p1ui"),
uiOutput("p2ui"),
uiOutput("p3ui"),
uiOutput("p4ui")
)
shinyApp(ui=ui , server=server)

Changing Numeric Inputs to 0 if Check Box is Unchecked

I was wondering if it was possible to set the value of a numeric input via updateNumericInput to be equal to 0 if a checkbox is not clicked. Below is how my code is set up at the moment to generate the check boxes and numeric inputs. I had to use loops to create a dynamic number of check boxes and inputs due to the nature of the app so I would really appreciate any help linking the two while keeping the functionality.
Server file:
shinyServer(function(input, output, session) {
output$inputs1 <- renderUI({
numSliders <- input$sources
lapply(1:numSliders, function(i) {
numericInput(
inputId = paste0('slider1', i),
label = df[i,2],
value = df[i,3]*(input$budget)/100)
})
})
output$checks1 <- renderUI({
numSliders <- input$sources
lapply(1:numSliders, function(i) {
checkboxInput(
inputId = paste0('check1', i),
label = df[i,2],
value = TRUE
)
})
})
}
UI:
shinyUI(fluidPage(fluidRow(
sidebarLayout(
sidebarPanel(
column(5,numericInput("budget", "Budget", value = 0),
uiOutput("checks1")),
column(5,uiOutput("inputs1"))),
mainPanel()
)
)
)
)
Please let me know if there is any sort of workaround for this.
Thanks in advance!
Since you can only generate a finite number of widgets the easiest way of creating an observer for each checkboxInput is to create a global variable, say, max_widgets which gives an upper bound on widgets. You then restrict the maximal value of numericInput which controls a number of widgets to max_widgets (so input$sources) and require within renderUIs that
req(numSliders > 0 & numSliders <= max_widgets)
(I would use validate and need to inform the user that the number of widgets has to be non negative and is bound to max_widgets but in my shiny version there is a bug and validate doesn't work as supposed.)
You then create observers for each checkboxInput on the server side:
lapply(1:max_widgets, function(i) {
observeEvent(input[[paste0('check', i)]], {
print(paste0("update of numeric", i))
updateNumericInput(session, inputId = paste0('numeric', i),
value = 0)
})
})
Note that this will create observers for all possible checkboxes (checkboxes may not even exist - shiny won't complain :) )
This may not be perfect but, as said, you will have only one observer for each checkbox.
If you dynamically generate observers in a following way (without a global variable max_widgets)
observe({
lapply(1:input$sources, function(i) {
observeEvent(input[[paste0('check', i)]], {
print(paste0("numeric", i, " = ", input[[paste0('numeric', i)]]))
updateNumericInput(session, inputId = paste0('numeric', i),
value = 0)
})
})
it will work too but each time you will generate new widgets you will also create an observer for it. So you may get multiple observers for each checkboxInput!
If your app is small then it won't matter much but in general it may lead to bugs. You can easy deal with it but it makes the code slightly more complicated - there was a question that touched on this problem.
Full example:
library(shiny)
rm(list = ls())
max_widgets <- 15
server <- shinyServer(function(input, output, session) {
output$inputs1 <- renderUI({
numSliders <- input$sources
# My shiny version has a bug and can't use validate(need(...)) because
# it doesn't work as suppossed
req(numSliders > 0 & numSliders <= max_widgets)
lapply(1:numSliders, function(i) {
numericInput(
inputId = paste0('numeric', i),
# label = df[i,2],
paste0("Input ", i),
# value = df[i,3] * (input$budget) / 100)
value = i * (input$budget) / 100)
})
})
output$checks1 <- renderUI({
numSliders <- input$sources
req(numSliders > 0 & numSliders <= max_widgets)
lapply(1:numSliders, function(i) {
list(
checkboxInput(
inputId = paste0('check', i),
# label = df[i,2],
label = paste0("Checkbox ", i),
value = TRUE
),
br()
)
})
})
lapply(1:max_widgets, function(i) {
observeEvent(input[[paste0('check', i)]], {
print(paste0("update of numeric", i))
updateNumericInput(session, inputId = paste0('numeric', i),
value = 0)
})
})
})
ui <- shinyUI(fluidPage(fluidRow(
sidebarLayout(
sidebarPanel(
column(5,
numericInput("budget", "Budget", value = 0),
hr(),
br(),
uiOutput("checks1")
),
column(5,
numericInput("sources", "Sources", value = 0, min = 0, max = max_widgets),
hr(),
uiOutput("inputs1")
)
),
mainPanel()
)
)))
shinyApp(ui, server)

Resources