Sequentially Generating a Dynamic User Interface in Shiny, R - r

I've created a dynamic user interface in R which generates four selectizeInput boxes within a fluid row environment with a matching set of observers watching for input. If an addFilter button is selected, then an additional fluid row is generated with another four boxes.
My issue surrounds the saving of previously inputted values. I have a group of reactive values storing the user inputs. These values are then inputted into the observers on the updateselectize. If a user clicks slowly, it all works fine. However, if a user clicks quickly, sometimes the values are erased.
I believe this happens because by the time the script comes to setting up the new interface, the old one with observers hasn't quite been rendered; but the values have been reset.
Below is a MWE. Note that to trigger the bug, you need to double click on the Add Filter button quickly.
Server.R:
library(shiny)
shinyServer(function(session, input, output) {
rValues <- reactiveValues(filter_counter = 1, filter_counter2 = 1, filter_waiter = 1, savedFil_1 = "", savedOp_1 = "",
savedCrit_1 = "", savedAO_1 = "")
columns <- c("C1", "C2")
operators <- c("O1", "O2")
values <- c("V1", "V2")
andor <- c("&&", "||")
observeEvent(input$AddFilter,{
if (rValues[['filter_waiter']] == 1) {
# Step 1: Store
lapply(1:rValues$filter_counter, function(i) {
rValues[[paste0("savedFil_", i)]] <- input[[paste0("fil_", i)]]
rValues[[paste0("savedOp_", i)]] <- input[[paste0("filOperators_", i)]]
rValues[[paste0("savedCrit_", i)]] <- input[[paste0("filCriteria_", i)]]
rValues[[paste0("savedAO_", i)]] <- input[[paste0("andor_", i)]]
})
# Step 2: Increment counter
rValues$filter_counter <- rValues$filter_counter + 1
# Step 3: Set filter waiter
rValues[['filter_waiter']] <- 0
}
})
output$filters <- renderUI({
if (rValues[['filter_waiter']] == 1) {
ui <- lapply(1:rValues$filter_counter, function(i) {
fluidRow(
column(4, selectizeInput(paste0("fil_", i), label = NULL, choices = NULL)),
column(2, selectizeInput(paste0("filOperators_", i), label = NULL, choices = NULL)),
column(4, selectizeInput(paste0("filCriteria_", i), label = NULL, choices = NULL)),
column(2, selectizeInput(paste0("andor_", i), label = NULL, choices = NULL)))
})
return(ui)
}
})
### Create observers for filters
observe({
lapply(1:rValues$filter_counter, function(i){
# Update the Selectizes
updateSelectizeInput(session, paste0("fil_", i), selected = rValues[[paste0("savedFil_", i)]], choices = columns, server = TRUE)
updateSelectizeInput(session, paste0("filOperators_", i), selected = rValues[[paste0("savedOp_", i)]], choices = operators, server = TRUE)
updateSelectizeInput(session, paste0("filCriteria_", i), selected = rValues[[paste0("savedCrit_", i)]], choices = values, server = TRUE)
updateSelectizeInput(session, paste0("andor_", i), selected = rValues[[paste0("savedAO_", i)]], choices = c("", "&&", "||"), server = TRUE)
})
rValues[['filter_waiter']] <- 1
})
})
And the UI.R:
library(shiny)
shinyUI(fluidPage(
# Application title
titlePanel("Dynamic SelectizeInput UI"),
sidebarLayout(
sidebarPanel(
actionButton("AddFilter", label = "Add a Filter"),
uiOutput("filters")
),
mainPanel(
h1("")
)
)
))

Your code can be simplified. Yo do not need to update the selectizeInput controls as you are generating new controls each time the button is selected.
You have too many reactive variables. I change to global variables. If you do not want to use global variables, then use reactive variables with isolate.
library(shiny)
filter_counter <- 0
filter_values <- list()
columns <- c("C1", "C2")
operators <- c("O1", "O2")
values <- c("V1", "V2")
andor <- c("&&", "||")
server<- shinyServer(function(session, input, output) {
output$filters <- renderUI({
input$AddFilter
# Step 1: store filter values
if ( filter_counter>0) {
lapply(1:filter_counter, function(i) {
filter_values[[paste0("savedFil_", i)]] <<- input[[paste0("fil_", i)]]
filter_values[[paste0("savedOp_", i)]] <<- input[[paste0("filOperators_", i)]]
filter_values[[paste0("savedCrit_", i)]] <<- input[[paste0("filCriteria_", i)]]
filter_values[[paste0("savedAO_", i)]] <<- input[[paste0("andor_", i)]]
})
}
# Step 2: Increment counter
filter_counter <<- filter_counter + 1
# Step 3: generate selectInputs
ui <- lapply(1:filter_counter, function(i){
fluidRow(
column(4,selectizeInput(paste0("fil_", i), label= 'fil', selected = filter_values[[paste0("savedFil_", i)]], choices = columns )),
column(2,selectizeInput(paste0("filOperators_", i),label= 'filop', selected = filter_values[[paste0("savedOp_", i)]], choices = operators )),
column(4,selectizeInput(paste0("filCriteria_", i),label= 'filcri', selected = filter_values[[paste0("savedCrit_", i)]], choices = values )),
column(2,selectizeInput(paste0("andor_", i),label= 'andor', selected = filter_values[[paste0("savedAO_", i)]], choices = c("", "&&", "||") ))
)
})
})
})
ui <- shinyUI(fluidPage(
# Application title
titlePanel("Dynamic SelectizeInput UI"),
sidebarLayout(
sidebarPanel(
actionButton("AddFilter", label = "Add a Filter"),
uiOutput("filters")
),
mainPanel(
h1("")
)
)
))
shinyApp(ui , server)

Related

Using two reactives in shiny that depend on each other

I have been trying to create a dashboard with up to 3 inputs and then plot some data. I have done this part but the requirement now has changed that every time there is a selection of a new variable they should also be able to filter the data based on the new input. Here has been my attempt so far:
UI:
library(shiny)
ui <- fluidPage(
sidebarPanel(
tags$br(),
uiOutput("textbox_ui"),
uiOutput("filter_ui"),
tags$br(),
actionButton("add_btn", "Add Factor"),
actionButton("rm_btn", "Remove Factor"),
tags$br(),
actionButton("make","Create Graph and Tables")
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Data stuff")
)
)
)
Server:
server <- function(input, output) {
# Track the number of input boxes to render
counter <- reactiveValues(n = 0)
AllInputs <- reactive({
x <- reactiveValuesToList(input)
})
observeEvent(input$add_btn, {
if(counter$n >2){
2
}else{
counter$n <- counter$n + 1
}
})
observeEvent(input$rm_btn, {
if (counter$n > 0) counter$n <- counter$n - 1
})
textboxes <- reactive({
n <- counter$n
if (n > 0) {
isolate({
lapply(seq_len(n), function(i) {
selectInput(inputId = paste0("var", i+1),
label = "",
choices = colnames(mtcars),
selected = AllInputs()[[paste0("var", i+1)]])
})
})
}
})
filterboxes <- reactive({
n <- counter$n
extrainputs <- sapply(seq_len(n), function(i) {
AllInputs()[[paste0("var", i+1)]]
})
summvar <- c(input$var1, extrainputs)
if(n > 0 ){
isolate({
lapply(1:length(summvar), function(x){
text <- summvar[x]
val_level <- unique(mtcars[[text]])
selectInput(inputId = paste0("fil",x+1),
label = paste0("Filter for ", text),
choices = val_level,
multiple = TRUE,
selected = val_level)
})
})
}
})
output$textbox_ui <- renderUI({ textboxes() })
output$filter_ui <- renderUI({ filterboxes() })
}
Two problems arise with this set up so far. One I cannot unselect any of the values when they appear in the filter second I see this warning on the sever side "Warning: Error in .subset2: invalid subscript type 'list'". My reactive skills are quite poor and any suggestions (reactive or not) would be appreciated.
As suggested in my comment...
library(shiny)
myfun <- function(df, var1) {
df %>% mutate(newvar = !!sym(var1)) # create newvar
}
ui <- fluidPage(
sidebarPanel(
tags$br(),
# uiOutput("textbox_ui"),
# uiOutput("filter_ui"),
tags$br(),
tags$div(id = 'placeholder'),
actionButton("add_btn", "Add Factor"),
actionButton("removeBtn", "Remove Factor"),
tags$br(),
actionButton("make","Create Graph and Tables")
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Data stuff")
)
)
)
server <- function(input, output, session) {
# Track the number of variables
numvars <- reactiveVal(0)
### keep track of elements/lines inserted and not yet removed
inserted <- c()
observeEvent(input$add_btn, {
if(input$add_btn==0) {
return(NULL)
}
else {
if (numvars()<0) {
numvars(0) # clicking on remove button too many times yields negative number; reset it to zero
}
newValue <- numvars() + 1 # newValue <- rv$numvars + 1
numvars(newValue) # rv$numvars <- newValue
# btn needs to be adjusted if removing and adding factors
if (input$removeBtn==0){
btn <- input$add_btn
}else {
if (input$add_btn > input$removeBtn) {
btn <- input$add_btn - input$removeBtn # add_btn counter does not decrease
}else btn <- numvars()
}
id <- paste0('txt', btn)
insertUI(
selector = '#placeholder',
## wrap element in a div with id for ease of removal
ui = tags$div(
selectInput(inputId = paste0("var", btn),
label = "",
choices = colnames(mtcars)
),
selectInput(inputId = paste0("fil",btn),
label = paste0("Filter for ", id),
choices = "",
multiple = TRUE),
id = id
)
)
}
# inserted <<- c(id, inserted) ## removes first one first
inserted <<- c(inserted, id) ## removes last one first
}, ignoreInit = TRUE) ## end of observeevent for add_btn
observe({
#print(numvars())
lapply(1:numvars(), function(i){
observeEvent(input[[paste0("var",i)]], {
mydf <- mtcars
mydf2 <- myfun(mydf,input[[paste0("var",i)]])
mysub <- unique(mydf2$newvar)
nam <- as.character(input[[paste0("var",i)]])
updateSelectInput(session = session,
inputId = paste0("fil",i),
label = paste0("Filter for ", nam),
choices = mysub,
selected = mysub
)
})
})
})
observeEvent(input$removeBtn, {
newValue <- numvars() - 1
numvars(newValue)
removeUI(
## pass in appropriate div id
selector = paste0('#', inserted[length(inserted)])
)
inserted <<- inserted[-length(inserted)]
print(inserted)
}, ignoreInit = TRUE)
}
shinyApp(ui = ui, server = server)

Prevent Select Input From Resetting With Streaming Data Updates

I am trying to come up with a way to prevent a select input from resetting when the data it depends upon changes. Ideally, as more data arrives, the choices expand, silently, without visual disruption or input value resetting. I've tried using updateSelectInput, but without success. I've created an example that reasonably approximates my problem, have left in my comments and ideas to show where I tried to come up with a solution, and am hoping someone else has a better idea they can share. As always, thank you in advance. -nate
library(shiny)
if (interactive()) {
ui <- fluidPage(
titlePanel("Is It Possible To Prevent The Select Input From Resetting with New Data Arriving?"),
sidebarLayout(
sidebarPanel(
shiny::uiOutput(outputId = "streaming_select")
),
mainPanel(
tableOutput("table")
)
)
)
server<- function(input, output, session){
session_launched<- reactiveValues(count=1)
fake_global_rv_list<- reactiveValues()
fake_global_rv_list$tmp<- data.frame(glob_0001=runif(10))
session_rv_list<- reactiveValues()
session_rv_list$tmp<- data.frame(sess_0001=runif(10))
# Simulating Streaming Data every 7 seconds
shiny::observe({
shiny::invalidateLater(millis = 7000)
shiny::isolate({
shiny::showNotification(ui = "Generating Random Data", type = "default", duration = 3)
tmp<- data.frame(runif(10) )
colnames(tmp)<- paste0("stream_",format(as.numeric(Sys.time())))
session_rv_list$tmp<- cbind(session_rv_list$tmp, tmp) # Put the random data into the reactive Values list
})
})
full_dat<- shiny::reactive({ cbind(fake_global_rv_list$tmp, session_rv_list$tmp) })
# Table of 'Streaming' Data
output$table <- renderTable({
full_dat()
})
## Select Input that let's you pick a single column
output$streaming_select<- shiny::renderUI({
if(!is.null(full_dat())){
if(session_launched$count==1){
out<- shiny::selectizeInput(inputId = "streaming_select_input", label="Pick A Column", choices = unique(colnames(full_dat())), selected = NULL, multiple = TRUE)
}
}
})
## Possible Ideas (?) BELOW
# select_choices<- shiny::eventReactive(full_dat(), {
# if(!is.null(full_dat())){
# if(session_launched$count==1){
# out<- list( choices = unique(colnames(full_dat())), selected = NULL)
# #shiny::selectizeInput(inputId = "streaming_select_input", label="Pick A Column", choices = unique(colnames(full_dat())), selected = NULL, multiple = TRUE)
# session_launched$count<- 2
# return(out)
# } else if(session_launched$count > 1){
# old_selections<- input$streaming_select_input
# out<- list( choices = unique(colnames(full_dat())), selected = old_selections)
# return(out)
# #shiny::updateSelectizeInput(session, inputId = "streaming_select_input", choices = unique(colnames(full_dat())), selected = old_selections)
# }
# }
# })
# observeEvent(select_choices(), {
# cat("STR of select_choices is...", "\n")
# cat(str(select_choices()), "\n")
# })
#
# shiny::observeEvent(full_dat(), {
# if(session_launched$count != 1){
# old_selections<- input$streaming_select_input
# shiny::updateSelectizeInput(session, inputId = "streaming_select_input", choices = unique(colnames(full_dat())), selected = old_selections)
# }
# })
}
shinyApp(ui, server)
}
Below is an example that works. I create the selectizeInput in the ui part, and update it on change of the full_dat data frame using an observeEvent. I had to store and reset the selection in this update step to prevent it from being set to NULL.
library(shiny)
if (interactive()) {
ui <- fluidPage(
titlePanel("Is It Possible To Prevent The Select Input From Resetting with New Data Arriving?"),
sidebarLayout(
sidebarPanel(
shiny::selectizeInput(inputId = "streaming_select_input", label="Pick A Column",
choices = NULL,
selected = NULL,
multiple = TRUE)
),
mainPanel(
tableOutput("table")
)
)
)
server<- function(input, output, session){
session_launched<- reactiveValues(count=1)
fake_global_rv_list<- reactiveValues()
fake_global_rv_list$tmp<- data.frame(glob_0001=runif(10))
session_rv_list<- reactiveValues()
session_rv_list$tmp<- data.frame(sess_0001=runif(10))
# Simulating Streaming Data every 7 seconds
shiny::observe({
shiny::invalidateLater(millis = 7000)
shiny::isolate({
shiny::showNotification(ui = "Generating Random Data", type = "default", duration = 3)
tmp<- data.frame(runif(10) )
colnames(tmp)<- paste0("stream_",format(as.numeric(Sys.time())))
session_rv_list$tmp<- cbind(session_rv_list$tmp, tmp) # Put the random data into the reactive Values list
})
})
full_dat<- shiny::reactive({ cbind(fake_global_rv_list$tmp, session_rv_list$tmp) })
# Table of 'Streaming' Data
output$table <- renderTable({
full_dat()
})
## Select Input that let's you pick a single column
observeEvent(full_dat(), {
selectedCols <- input$streaming_select_input
updateSelectizeInput(session, "streaming_select_input", choices = colnames(full_dat()), selected = selectedCols)
})
}
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)

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)

How to customize tabPanel added by clicking a button in Shiny

I have a sidebarLayout app in which I've set up buttons to add and remove tabPanels in the sidebarPanel. However, I can't figure out how to customize those tabPanels. My code is below:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(width = 3, fixed=T,
h3("L2 Machine"),
actionButton('moreL2', tags$b('Add L2')),
actionButton('lessL2', tags$b('Remove L2')),
uiOutput('panelset'),
tabPanel("L2panel",
numericInput(inputId='L2amount', 'Select L2 Amount', value=0),
selectInput(inputId='L2type', 'Select L2 Type', c('Percent', 'Absolute')),
uiOutput('L2daterange')
)
),
mainPanel(
verbatimTextOutput('L2a'),
verbatimTextOutput('L2t')
)
)
)
server <- function(input, output) {
output$L2a <- renderPrint(input$L2amount)
output$L2t <- renderPrint(input$L2type)
output$panelset <- renderUI({
n <- seq(max(input$moreL2 - input$lessL2 + 1, 1))
tabList <- lapply(paste("Pan", n), tabPanel)
do.call(tabsetPanel, tabList)
})
output$L2daterange <- renderUI({
dateRangeInput(inputId='L2daterange',
label='Select Adjustment Period',
start='01-01-2010', end='01-12-2015'
)
})
}
shinyApp(ui, server)
Currently, I have numericInput(), selectInput(), and uiOutput() inside tabPanel(). Instead I want each tabPanel created by clicking the button "Add L2" to have it's own set of numericInput, selectInput and uiOutput.
You create indeed different tabPanels but they are empty - both numericInput and selectInput are not inside of dynamic tabPanels. The solution is based on https://gist.github.com/wch/5436415/ and you can find there an extensive explanation why do you need a function local to render outputs with a for loop.
As said above, you created correctly dynamic tabPanels but they are empty. Within lapply you should specify unique widgets as arguments to tabPanel.
output$panelset <- renderUI({
n <- seq(max(input$moreL2 - input$lessL2 + 1, 1))
tabList <- lapply(paste("Pan", n), tabPanel)
do.call(tabsetPanel, tabList)
})
Here I coded an example of how you can do it the correct way. Each time you create a unique tabPanel with a unique set of widgets.
tabList <- lapply(n, function(i) {
tabPanel(
title = paste0('Pan', i),
numericInput(inputId = paste0('L2amount', i), 'Select L2 Amount', value = 0),
selectInput(inputId = paste0('L2type', i), 'Select L2 Type', c('Percent', 'Absolute')),
dateRangeInput(inputId = paste0('L2daterange',i),
label = 'Select Adjustment Period',
start = '01-01-2010', end = '01-12-2015'))
})
do.call(tabsetPanel, tabList)
})
Then for each tabPanel with unique set of widgets you have to create unique set of outputs and then you can render values of your widgets.
Full solution:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(width = 3, fixed=T,
h3("L2 Machine"),
actionButton('moreL2', tags$b('Add L2')),
actionButton('lessL2', tags$b('Remove L2')),
uiOutput('panelset')
),
mainPanel(
uiOutput("dynamic")
)
)
)
TMAX <- 10 # specify maximal number of dynamic panels
server <- function(input, output) {
output$panelset <- renderUI({
n <- seq(max(input$moreL2 - input$lessL2 + 1, 1))
# You have to create each time a new set of unique widgets
tabList <- lapply(n, function(i) {
tabPanel(
title = paste0('Pan', i),
numericInput(inputId = paste0('L2amount', i), 'Select L2 Amount', value = 0),
selectInput(inputId = paste0('L2type', i), 'Select L2 Type', c('Percent', 'Absolute')),
dateRangeInput(inputId = paste0('L2daterange',i),
label = 'Select Adjustment Period',
start = '01-01-2010', end = '01-12-2015'))
})
do.call(tabsetPanel, tabList)
})
output$dynamic <- renderUI({
n <- seq(max(input$moreL2 - input$lessL2 + 1, 1))
# You want to render n-times different outputs and each time you have
# k different outputs -- > need a list within a list.
lapply(n, function(i) {
list(
h5(paste0("Pan", i, " widgets")),
verbatimTextOutput(paste0('L2a', i)),
verbatimTextOutput(paste0('L2t', i)),
verbatimTextOutput(paste0('L2dat', i)),
br()
)
})
})
for (i in 1:TMAX) {
local({
my_i <- i
# Outputs
L2a <- paste0('L2a', my_i)
L2t <- paste0('L2t', my_i)
L2dat <- paste0('L2dat', my_i)
list(
output[[L2a]] <- renderPrint({ input[[paste0('L2amount', my_i)]] }),
output[[L2t]] <- renderPrint({ input[[paste0('L2type', my_i)]] }),
output[[L2dat]] <- renderPrint({ input[[paste0('L2daterange', my_i)]] })
)
})
}
}
shinyApp(ui, server)

Resources