How to make selectizeInput function reactive to multiple user inputs? - r

This post is a follow-on to yesterday's post, How to make selectInput choices reactive?.
The data frame shown at the top of the image below and generated via the MWE at the bottom of this post has two types of period measurements: Period_1 and Period_2. Period_1 represents the number of months elapsed since the element arose, and Period_2 is a calendar month representation in YYYY-MM form. I inserted a radioButton() giving the user the choice of which period type ("periodType") to run through the simple placeholder function in the server section, but am unsure of an efficient way to do this, especially in the selectizeInput() functions currently in the ui section, without resorting to renderUI(). Any suggestions for how to do this?
This image better explains:
MWE code:
library(shiny)
library(data.table)
DT <- data.table(
ID = c(1, 1, 1, 2, 2, 2, 3, 3, 3),
Period_1 = c(1, 2, 3, 1, 2, 3, 1, 2, 3),
Period_2 = c("2020-01","2020-02","2020-03","2020-02","2020-03","2020-04","2020-03","2020-04","2020-05"),
Values = c(5, 10, 15, 0, 2, 4, 3, 6, 9)
)
all_choices <- function(x) {unique(x)}
ui <- fluidPage(
tableOutput("data"),
radioButtons("periodType",
label = "Period type selection:",
choiceNames = c('Period_1','Period_2'),
choiceValues = c('Period_1','Period_2'),
selected = 'Period_1',
inline = TRUE
),
selectizeInput(
inputId = "fromPeriod",
label = "From period:",
choices = setdiff(all_choices(DT$Period_1), last(all_choices(DT$Period_1))),
selected = 1
),
selectizeInput(
inputId = "toPeriod",
label = "To period:",
choices = setdiff(all_choices(DT$Period_1), first(all_choices(DT$Period_1))),
selected = 2
),
tableOutput("dataSelect")
)
server <- function(input, output, session) {
output$data <- renderTable({DT})
observeEvent(input$fromPeriod, {
freezeReactiveValue(input, "toPeriod")
updateSelectizeInput(
session,
inputId = "toPeriod",
choices = all_choices(DT$Period_1)[all_choices(DT$Period_1) > input$fromPeriod],
selected = max(all_choices(DT$Period_1)[all_choices(DT$Period_1) > input$fromPeriod])
)
}, ignoreInit = TRUE)
output$dataSelect <- renderTable({
setorder(DT[Period_1 %in% c(input$fromPeriod, input$toPeriod)], Period_1)
}, rownames = TRUE)
}
shinyApp(ui, server)

We can update the choices based on the selection:
library(shiny)
library(data.table)
DT <- data.table(
ID = c(1, 1, 1, 2, 2, 2, 3, 3, 3),
Period_1 = c(1, 2, 3, 1, 2, 3, 1, 2, 3),
Period_2 = c("2020-01","2020-02","2020-03","2020-02","2020-03","2020-04","2020-03","2020-04","2020-05"),
Values = c(5, 10, 15, 0, 2, 4, 3, 6, 9)
)
all_choices_p1 <- unique(DT$Period_1)
all_choices_p2 <- unique(DT$Period_2)
ui <- fluidPage(
tableOutput("data"),
radioButtons("periodType",
label = "Period type selection:",
choiceNames = c('Period_1','Period_2'),
choiceValues = c('Period_1','Period_2'),
selected = 'Period_1',
inline = TRUE
),
selectizeInput(
inputId = "fromPeriod",
label = "From period:",
choices = all_choices_p1[-length(all_choices_p1)],
selected = 1
),
selectizeInput(
inputId = "toPeriod",
label = "To period:",
choices = all_choices_p1[-1],
selected = 2
),
tableOutput("dataSelect")
)
server <- function(input, output, session) {
all_choices_reactive <- reactiveVal(all_choices_p1)
output$data <- renderTable({DT})
observeEvent(input$periodType, {
if(input$periodType == "Period_1"){
all_choices_reactive(all_choices_p1)
} else {
all_choices_reactive(all_choices_p2)
}
updateSelectizeInput(
session,
inputId = "fromPeriod",
choices = all_choices_reactive()[-length(all_choices_reactive())]
)
updateSelectizeInput(
session,
inputId = "toPeriod",
choices = all_choices_reactive()[-1]
)
})
observeEvent(input$fromPeriod, {
freezeReactiveValue(input, "toPeriod")
updateSelectizeInput(
session,
inputId = "toPeriod",
choices = all_choices_reactive()[all_choices_reactive() > input$fromPeriod],
selected = max(all_choices_reactive()[all_choices_reactive() > input$fromPeriod])
)
}, ignoreInit = TRUE)
output$dataSelect <- renderTable({
if(input$periodType == "Period_1"){
keep_cols <- c("ID", "Period_1", "Values")
setorder(DT[Period_1 %in% c(input$fromPeriod, input$toPeriod), ..keep_cols], Period_1)
} else {
keep_cols <- c("ID", "Period_2", "Values")
setorder(DT[Period_2 %in% c(input$fromPeriod, input$toPeriod), ..keep_cols], Period_2)
}
}, rownames = TRUE)
}
shinyApp(ui, server)

Related

shiny conditionalPanel update problem with output.table-condition

I am trying to build an app that displays tables for different years. In some years some tables do not exist. I solved this with conditionalPanel and the condition that the tables exist. This works at first. The problem is that it doesn't work once a table is retrieved that doesn't exist. You can try this on this test page (http://46.231.205.192/Tests/). After opening the app, one table per year is visible. If you go to the next table with >, the error message for 2021 is correctly displayed at the bottom. If you then go back <, the error continues to be displayed, although the table for 2021 exists in that case.
I think the problem arises from conditionalPanel with output.Table_2021 but I can't fix it.
Can you see a solution?
library(shiny)
library(DT)
Table_1 <- data.frame(Antworten = "mean", Total = 3, US = 3.5, FR = 4, IT = 2, male = 0, female = 1)
Table_2 <- data.frame(Antworten = "mean", Total = 2, US = 2.5, FR = 3, IT = 1, male = 1, female = 2)
Table_1_2021 <- data.frame(Antworten = "mean", Total = 4, US = 4.5, FR = 5, IT = 3, male = 3, female = 10)
# in 2021 the Table_2 is missing
tabnames <- c("Table_1", "Table_2")
# Columns
kopfvariablen <- c("region", "sex")
default_vars <- c("region")
# Shiny ----
ui <- fluidPage(
titlePanel(title=div("Tables")),
sidebarLayout(
sidebarPanel(width = 2, tags$style(".well {background-color: #ffffff; border-color: #ffffff}"),
a(br(), br(), br(), br()),
checkboxInput(
inputId = "year_2022",
label = "Tabs: 2022",
value = TRUE),
checkboxInput(
inputId = "year_2021",
label = "Tabs: 2021",
value = TRUE)
),
mainPanel(
align = "center",
actionButton("prevBin", "<", class="btn btn-success"),
actionButton("nextBin", ">", class="btn btn-success"),
selectInput(
inputId = "dataset",
label = "",
choices = tabnames,
width = "60%"),
conditionalPanel(
condition = "input.year_2022 == 1 ",
DT::dataTableOutput("Table_2022")),
conditionalPanel(inline = T,
condition = "input.year_2021 == 1 && output.Table_2021", # I think the problem comes with this line.
DT::dataTableOutput('Table_2021')),
conditionalPanel(inline = T,
condition = "input.year_2021 == 1 && output.Table_2021 == null",
h4("[This Question was not asked in 2021.]", align = "left", style = "color:grey"))
)
))
server = function(input, output, session) {
# "next" and "previous" buttons
output$prevBin <- renderUI({
actionButton("prevBin",
label = "Previous")
})
output$nextBin <- renderUI({
actionButton("nextBin",
label = "Next")
})
observeEvent(input$prevBin, {
current <- which(tabnames == input$dataset)
if(current > 1){
updateSelectInput(session, "dataset",
choices = as.list(tabnames),
selected = tabnames[current - 1])
}
})
observeEvent(input$nextBin, {
current <- which(tabnames == input$dataset)
if(current < length(tabnames)){
updateSelectInput(session, "dataset",
choices = as.list(tabnames),
selected = tabnames[current + 1])
}
})
# Tables 2022 -----
output$Table_2022 <- DT::renderDataTable({
# Data with names from input
data <- get(input$dataset)
data_fin <- data[,1:7]
#subheader as list
subheader <- list()
subheader <- c(subheader, "US", "FR", "IT")
subheader <- c(subheader, "male", "female")
# The header
topheader_txt <- "tr(th(rowspan = 2, 'Values'), th(rowspan = 2, 'Total') , th(class = 'dt-center', colspan = 3, 'region'), th(class = 'dt-center', colspan = 2, 'sex')"
topheader <- parse(text=paste0(topheader_txt, ")"))
#Container for topheader and subheader
sketch = htmltools::withTags(table(
class = 'display',
thead(
eval(topheader),
tr(lapply(rep(subheader, 1), th))
)
)
)
# Table with container
DT::datatable(data_fin,
container = sketch,
rownames = F)
})
# Tables 2021 -----
output$Table_2021 <- DT::renderDataTable({
#Daten aus Auswahl
data <- get(paste0(input$dataset, "_2021"))
data_fin <- data[,1:7]
#subheader list
subheader <- list()
subheader <- c(subheader, "US", "FR", "IT")
subheader <- c(subheader, "male", "female")
topheader_txt <- "tr(th(rowspan = 2, 'Values'), th(rowspan = 2, 'Total') , th(class = 'dt-center', colspan = 3, 'region'), th(class = 'dt-center', colspan = 2, 'sex')"
topheader <- parse(text=paste0(topheader_txt, ")"))
#Container for topheader and subheader
sketch = htmltools::withTags(table(
class = 'display',
thead(
eval(topheader),
tr(lapply(rep(subheader, 1), th))
)
)
)
js <- c(
"function(settings){",
" var datatable = settings.oInstance.api();",
" var table = datatable.table().node();",
" var caption = '2021'",
" $(table).append('<caption style=\"caption-side: top-right; text-align: center; margin: 8px 0; font-size: 2em\">' + caption + '</caption>');",
"}"
)
# Table with container
DT::datatable(data_fin,
container = sketch,
rownames = F,
caption = tags$caption(
style="caption-side: bottom; text-align: left; margin: 8px 0;"
),
extensions = c('Buttons'),
options = list(initComplete = JS(js))
)
})
}
shinyApp(ui, server)
The solution comes with: outputOptions(output, "Table_2021", suspendWhenHidden = FALSE) in the server function.
library(shiny)
library(DT)
Table_1 <- data.frame(Antworten = "mean", Total = 3, US = 3.5, FR = 4, IT = 2, male = 0, female = 1)
Table_2 <- data.frame(Antworten = "mean", Total = 2, US = 2.5, FR = 3, IT = 1, male = 1, female = 2)
Table_1_2021 <- data.frame(Antworten = "mean", Total = 4, US = 4.5, FR = 5, IT = 3, male = 3, female = 10)
# in 2021 the Table_2 is missing
tabnames <- c("Table_1", "Table_2")
# Columns
kopfvariablen <- c("region", "sex")
default_vars <- c("region")
# Shiny ----
ui <- fluidPage(
titlePanel(title=div("Tables")),
sidebarLayout(
sidebarPanel(width = 2, tags$style(".well {background-color: #ffffff; border-color: #ffffff}"),
a(br(), br(), br(), br()),
checkboxInput(
inputId = "year_2022",
label = "Tabs: 2022",
value = TRUE),
checkboxInput(
inputId = "year_2021",
label = "Tabs: 2021",
value = TRUE)
),
mainPanel(
align = "center",
actionButton("prevBin", "<", class="btn btn-success"),
actionButton("nextBin", ">", class="btn btn-success"),
selectInput(
inputId = "dataset",
label = "",
choices = tabnames,
width = "60%"),
conditionalPanel(
condition = "input.year_2022 == 1 ",
DT::dataTableOutput("Table_2022")),
conditionalPanel(inline = T,
condition = "input.year_2021 == 1 && output.Table_2021", # I think the problem comes with this line.
DT::dataTableOutput('Table_2021')),
conditionalPanel(inline = T,
condition = "input.year_2021 == 1 && output.Table_2021 == null",
h4("[This Question was not asked in 2021.]", align = "left", style = "color:grey"))
)
))
server = function(input, output, session) {
outputOptions(output, "Table_2021", suspendWhenHidden = FALSE) # Solution
# "next" and "previous" buttons
output$prevBin <- renderUI({
actionButton("prevBin",
label = "Previous")
})
output$nextBin <- renderUI({
actionButton("nextBin",
label = "Next")
})
observeEvent(input$prevBin, {
current <- which(tabnames == input$dataset)
if(current > 1){
updateSelectInput(session, "dataset",
choices = as.list(tabnames),
selected = tabnames[current - 1])
}
})
observeEvent(input$nextBin, {
current <- which(tabnames == input$dataset)
if(current < length(tabnames)){
updateSelectInput(session, "dataset",
choices = as.list(tabnames),
selected = tabnames[current + 1])
}
})
# Tables 2022 -----
output$Table_2022 <- DT::renderDataTable({
# Data with names from input
data <- get(input$dataset)
data_fin <- data[,1:7]
#subheader as list
subheader <- list()
subheader <- c(subheader, "US", "FR", "IT")
subheader <- c(subheader, "male", "female")
# The header
topheader_txt <- "tr(th(rowspan = 2, 'Values'), th(rowspan = 2, 'Total') , th(class = 'dt-center', colspan = 3, 'region'), th(class = 'dt-center', colspan = 2, 'sex')"
topheader <- parse(text=paste0(topheader_txt, ")"))
#Container for topheader and subheader
sketch = htmltools::withTags(table(
class = 'display',
thead(
eval(topheader),
tr(lapply(rep(subheader, 1), th))
)
)
)
# Table with container
DT::datatable(data_fin,
container = sketch,
rownames = F)
})
# Tables 2021 -----
output$Table_2021 <- DT::renderDataTable({
#Daten aus Auswahl
data <- get(paste0(input$dataset, "_2021"))
data_fin <- data[,1:7]
#subheader list
subheader <- list()
subheader <- c(subheader, "US", "FR", "IT")
subheader <- c(subheader, "male", "female")
topheader_txt <- "tr(th(rowspan = 2, 'Values'), th(rowspan = 2, 'Total') , th(class = 'dt-center', colspan = 3, 'region'), th(class = 'dt-center', colspan = 2, 'sex')"
topheader <- parse(text=paste0(topheader_txt, ")"))
#Container for topheader and subheader
sketch = htmltools::withTags(table(
class = 'display',
thead(
eval(topheader),
tr(lapply(rep(subheader, 1), th))
)
)
)
js <- c(
"function(settings){",
" var datatable = settings.oInstance.api();",
" var table = datatable.table().node();",
" var caption = '2021'",
" $(table).append('<caption style=\"caption-side: top-right; text-align: center; margin: 8px 0; font-size: 2em\">' + caption + '</caption>');",
"}"
)
# Table with container
DT::datatable(data_fin,
container = sketch,
rownames = F,
caption = tags$caption(
style="caption-side: bottom; text-align: left; margin: 8px 0;"
),
extensions = c('Buttons'),
options = list(initComplete = JS(js))
)
})
}
shinyApp(ui, server)

R shiny: update reactive expression

I'm trying to update a reactive expression with selectInput fired by an actionButton, but I don't succeed. Here's the (almost) minimal example code:
library(tidyverse)
library(shiny)
library(DT)
data <- tibble(ID = 1:9,
x = c(5, 4, 3, 4, 5, 7, 4, 2, 5),
min = c(NA, NA, -1, NA, NA, NA, NA, -1, NA),
rating = NA_integer_)
ui <- fluidPage(
DTOutput("tbl", width = "100%"),
hr(),
fluidRow(
column(4, selectInput(inputId = "min", label = "Choose min", choices = 1)),
column(4, selectInput(inputId = "rating", label = "Please rate",
choices = c("Choose one", "1: Yes" = "1", "2: No" = "2"))),
column(4, fluidRow(
column(12, tags$div(HTML("<p style = \"margin-bottom: 5px;\"><strong>Submit</strong></p>"))),
column(12, actionButton("submit", "Submit rating and save to data"))
)))
)
server <- function(input, output, session) {
mins <- reactive({
data %>% filter(min == -1) %>% pull(ID)
})
observeEvent(mins(), {
updateSelectInput(session, inputId = "min", choices = mins())
})
mins_table <- reactive({
data %>% filter(ID %in% mins())
})
tbl <- reactive({
DT::datatable(mins_table(),
caption = "Min to rate",
rownames = FALSE,
options = list(paging = FALSE,
scrollX = FALSE,
searching = FALSE,
ordering = FALSE,
lengthChange = FALSE)) %>%
formatStyle("ID", target = "row", fontWeight = styleEqual(as.integer(input$min), "bold"))
})
output$tbl <- renderDT({
tbl()
})
observeEvent(input$submit, {
tmp <- which(mins() == input$min)
# write rating to mins_table (to show rating in app) --> doesn't work:
################ Error occurs in the following line
mins_table()$rating[tmp] <<- as.integer(input$rating) # Error in <<-: invalid (NULL) left side of assignment
# write rating to data and save file locally (overwrite) --> works fine
data$rating[data$ID == input$min] <<- as.integer(input$rating)
saveRDS(data, file = "output/data2.rds")
# go to next min
updateSelectInput(session, inputId = "min", selected = mins()[tmp + 1])
})
}
shinyApp(ui, server)
The error occurs in observeEvent.
Any help is much appreciated.
I believe it's because mins_table is a reactive expression. Change it to a data.frame if you want to assign a value, then assign that data.frame to the reactive.
mt <- mins_table()
mt$rating[tmp] <- as.integer(input$rating)

How to reactively format data table columns?

In the simplified functioning code at the bottom, in the colDefs = list() section of datatable() under renderDT(), in server section, I manually center-align the two right-most column outputs of the table using the instructions targets = 1:2, class = "dt-center".
I've been trying to make the number of columns that are formatted in this manner reactive, based on the actual number of columns detected in the output table -- because in the full code this is extracted from, the number of output table columns varies based on the actual composition of the data. In the below code commented out with # you can see my latest attempt to reactively format the columns, and of course it doesn't work.
Please, how do I reactively format the data table columns where all columns to the right of the header rows are center-aligned?
Simplified functioning code:
library(dplyr)
library(DT)
library(shiny)
library(shinyWidgets)
library(tidyverse)
ui <-
fluidPage(
fluidRow(
column(width = 8,
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(inputId = "grouping",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE),
DT::dataTableOutput("sums")
)
)
)
server <- function(input, output, session) {
data <- reactive({
data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(1000.01, 20, 30, 40, 50, 60),
ColB = c(15.06, 25, 35, 45, 55, 65)
)
})
summed_data <- reactive({
data() %>%
group_by(!!sym(input$grouping)) %>%
select("ColA","ColB") %>% summarise(across(everything(), sum))
})
output$data <- renderTable(data())
output$sums <- renderDT({
datatable(
data = summed_data(),
rownames = FALSE,
options =
list(
columnDefs = list(
list(targets = 0, class = "dt-left"),
list(targets = 1:2, class = "dt-center")
# list(targets = 1:ncol(summed_data()), class = "dt-center")
)
),
)
})
}
shinyApp(ui, server)
The following seems to work. However, I'm not sure why your initial approach doesn't work - it looks good to me.
library(dplyr)
library(DT)
library(shiny)
library(shinyWidgets)
library(tidyverse)
ui <-
fluidPage(fluidRow(
column(
width = 8,
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "grouping",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
),
DT::dataTableOutput("sums")
)
))
server <- function(input, output, session) {
data <- reactive({
data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(1000.01, 20, 30, 40, 50, 60),
ColB = c(15.06, 25, 35, 45, 55, 65)
)
})
summed_data <- reactive({
data() %>%
group_by(!!sym(input$grouping)) %>%
select("ColA", "ColB") %>% summarise(across(everything(), sum))
})
output$data <- renderTable(data())
output$sums <- renderDT({
print(names(summed_data())[1])
datatable(
data = summed_data(),
rownames = FALSE,
options = list(columnDefs = list(
list(className = 'dt-left', targets = 0),
list(className = 'dt-center', targets = seq_len(ncol(summed_data())) - 1)
))
)
})
}
shinyApp(ui, server)

Two Reactive Picker Input, Retain Selection Previous Event Shiny R

I am constructing a shiny app. In the UI I have one selectInput and one pickerInput. Of course the pickerInput depends on the selectInput. In the example below, I want to find a way how to preserve what has been selected in pickerInput when users change the selectInput.
In the example below, let's imagine a user who selects Period 1: X to Z and either UK or USA or both UK and USA. What I want is that if that user changes Period 1: X to Z to Period 2: X to Y that UK be automatically selected -- or stay selected -- (because UK is among the choices of Period 2: X to Y).
So, how to retain what has been selected in pickerInput when input_period changes.
Thank you!
choice_name <- c('UK','USA','UK','USA','BE','BE')
choice_id <- c(1, 2, 1, 2, 3, 3)
period <- c('period1', 'period1', 'period2', 'period3', 'period3', 'period3')
data <- data.frame(choice_name, choice_id, period)
choices_picker <- unique(data$choice_id)
names(choices_picker) <- unique(data$choice_name)
ui <- bootstrapPage(
absolutePanel(left = 10, bottom = 10, draggable = TRUE,
selectInput(inputId = "input_period", label = "Period",
choices = c("Period 1: X to Z" = "period1", "Period 2: X to Y" = "period2", "Period 3: X to X" = "period3"),
selected = "period1"),
pickerInput(inputId = "picker_cty",
label = "Select Country",
choices = choices_picker,
multiple = TRUE),
))
server <- function(input, output, session) {
# Reactive pickerInput ---------------------------------
observeEvent(input$input_period, {
data1 <- data[data$period == input$input_period,]
datau <- unique(data$choice_id)
data1u <- unique(data1$choice_id)
disabled_choices <- ifelse(datau %in% data1u, 0,1)
# Generate reactive picker input
updatePickerInput(session = session,
inputId = "picker_cty",
choices = choices_picker,
choicesOpt = list(
disabled = disabled_choices,
style = ifelse(disabled_choices,
yes = "color: rgba(119, 119, 119, 0.5);",
no = "")
))
}, ignoreNULL=FALSE)
}
shinyApp(ui, server)
You can use select = option. Try this
choice_name <- c('UK','USA','UK','USA','BE','BE')
choice_id <- c(1, 2, 1, 2, 3, 3)
period <- c('period1', 'period1', 'period2', 'period3', 'period3', 'period3')
data <- data.frame(choice_name, choice_id, period)
data2 <- data[data$period == "period1",]
choices_picker <- unique(data$choice_id)
names(choices_picker) <- unique(data$choice_name)
datau <- unique(data$choice_id)
data2u <- unique(data2$choice_id)
disabled_choicez <- ifelse(datau %in% data2u, 0,1)
ui <- bootstrapPage(
absolutePanel(left = 10, bottom = 10, draggable = TRUE,
selectInput(inputId = "input_period", label = "Period",
choices = c("Period 1: X to Z" = "period1", "Period 2: X to Y" = "period2", "Period 3: X to X" = "period3"),
selected = "period1" ),
pickerInput(inputId = "picker_cty",
label = "Select Country",
choices = choices_picker,
choicesOpt = list(
disabled = disabled_choicez,
style = ifelse(disabled_choicez,
yes = "color: rgba(119, 119, 119, 0.5);",
no = "")
),
selected = character(0),
multiple = TRUE),
))
server <- function(input, output, session) {
observe({print(input$picker_cty)})
# Reactive pickerInput ---------------------------------
observeEvent(input$input_period, {
data1 <- data[data$period == input$input_period,]
datau <- unique(data$choice_id)
data1u <- unique(data1$choice_id)
disabled_choices <- ifelse(datau %in% data1u, 0,1)
if (is.null(input$picker_cty)) selected = character(0)
else {
if (sum(data1u %in% input$picker_cty)>0) {
selected = data1u[data1u %in% input$picker_cty]
}else selected = character(0)
}
# Generate reactive picker input
updatePickerInput(session = session,
inputId = "picker_cty",
choices = choices_picker,
selected = selected,
choicesOpt = list(
disabled = disabled_choices,
style = ifelse(disabled_choices,
yes = "color: rgba(119, 119, 119, 0.5);",
no = "")
))
}, ignoreInit = TRUE)
}
shinyApp(ui, server)

Arrange 3 dynamic number of inputs in a row and action on two columns of inputs based on third column

I am building a shiny app to map two different text inputs. I do the matching using string distances but they might be erroneous. So, I am planning to develop a shiny app where the subject matter experts can use the click and dropdown to select match unique data.
If I have fixed number of rows, I can achieve something like below:: However, when I don't know the number of rows in data, how can I dynamically design user-interface to get the required output?
After the user have performed the required mapping. I want to perform some action after the button click. Additionally, if the user has clicked mapped (the check box). I want to leave that row out of the final action.
library(shiny)
set.seed(42)
n_samp = 5 # this comes from the input
indx <- sample(1:20, n_samp)
let_small <- letters[indx]
let_caps <- sample(LETTERS[indx])
# user input
ui <- fluidPage(
selectInput(inputId = "n_samp_choice", label = NULL,
choices = 1:20, width = 500), # number of samples
fluidRow( # first row checkbox
column(width = 2, offset = 0,
checkboxInput("correct1", label = NULL, FALSE)
),
column(width = 2, offset = 0, # text input originial
textInput(inputId = "original1", value = let_small[1], label = NULL )
),
column(width = 5, # options for match
selectInput(inputId = "options1", label = NULL,
choices = let_caps, width = 500)
)
),
fluidRow(
column(width = 2, offset = 0,
checkboxInput("correct1", label = NULL, FALSE)
),
column(width = 2, offset = 0,
textInput(inputId = "original2", value = let_small[2], label = NULL )
),
column(width = 5,
selectInput(inputId = "options2", label = NULL,
choices = let_caps, width = 500)
)
),
fluidRow(
column(width = 2, offset = 0,
checkboxInput("correct1", label = NULL, FALSE)
),
column(width = 2, offset = 0,
textInput(inputId = "original3", value = let_small[3], label = NULL )
),
column(width = 5,
selectInput(inputId = "options3", label = NULL,
choices = let_caps, width = 500)
)
),
fluidRow(
column(width = 2, offset = 0,
checkboxInput("correct1", label = NULL, FALSE)
),
column(width = 2, offset = 0,
textInput(inputId = "original4", value = let_small[4], label = NULL )
),
column(width = 5,
selectInput(inputId = "options4", label = NULL,
choices = let_caps, width = 500)
)
),
fluidRow(
column(width = 2, offset = 0,
checkboxInput("correct1", label = NULL, FALSE)
),
column(width = 2, offset = 0,
textInput(inputId = "original5", value = let_small[5], label = NULL )
),
column(width = 5,
selectInput(inputId = "options5", label = NULL,
choices = let_caps, width = 500)
),
column(width = 2, offset = 0,
uiOutput("actionBut.out")
)
)
)
server <- function(input, output, session) {
output$actionBut.out <- renderUI({
print(input$original1)
session$sendCustomMessage(type="jsCode",
list(code= "$('#text').prop('disabled',true)"))
actionButton("copyButton1","Copy Code")
})
observeEvent(input$copyButton1, {
if(tolower(input$options1) == tolower(input$options1) &
tolower(input$options2) == tolower(input$options2) &
tolower(input$options3) == tolower(input$options3) &
tolower(input$options4) == tolower(input$options4) &
tolower(input$options5) == tolower(input$options5))
{
print("great job")
}else{
unmapp <- which(c(input$correct1, input$correct2,
input$correct3, input$correct4,
input$correct5))
print("The following are unmatched")
print(let_caps[unmapp])
}
})
}
shinyApp(ui = ui, server = server)
You can create a dynamic design using Shiny Modules and UIOutput.
Step1: Create a module to be called by a loop:
moduleUI <- function(id) {
ns <- NS(id)
tagList(
fluidRow( # first row checkbox
column(width = 2, offset = 0,
checkboxInput(ns("correct"), label = NULL, FALSE)
),
column(width = 2, offset = 0, # text input originial
textInput(inputId = ns("original"), value = let_small[id], label = NULL )
),
column(width = 5, # options for match
selectInput(inputId = ns("options"), label = NULL,
choices = let_caps, width = 500)
)
)
)
}
Step2: Create a UIOutput, that will serve as a placeholder for the module.
uiOutput("module_placeholder")
Step3: Add server logic:
I added a numericInput that allows you to simulate different number of rows. E.g.: If you set it to 5, the module will be generated 5 times.
This observer allows you to generate any number of instances of the module.
observe( {
output$module_placeholder <- renderUI( {
lapply(1:input$num, moduleUI)
})
})
The ids of the objects will be 1-correct, 1-original, 1-options for the first module, 2-correct, 2-original, etc. for the second module, ...
It is important because you can access input elements using input[[NAME_OF_THE_ELEMENT]].
So for example I use lapply to check if input$original == input$options for every module. (Similar to your code, but it's general, so it works for any number of modules)
cond <- unlist(lapply(to_check, function(x) {
tolower(input[[paste(x, "original", sep="-")]]) == tolower(input[[paste(x, "options", sep="-")]])
}))
See full code:
library(shiny)
set.seed(42)
n_samp = 10 # this comes from the input
indx <- sample(1:20, n_samp)
let_small <- letters[indx]
let_caps <- sample(LETTERS[indx])
moduleUI <- function(id) {
ns <- NS(id)
tagList(
fluidRow( # first row checkbox
column(width = 2, offset = 0,
checkboxInput(ns("correct"), label = NULL, FALSE)
),
column(width = 2, offset = 0, # text input originial
textInput(inputId = ns("original"), value = let_small[id], label = NULL )
),
column(width = 5, # options for match
selectInput(inputId = ns("options"), label = NULL,
choices = let_caps, width = 500)
)
)
)
}
ui <- fluidPage(
numericInput(inputId = "num", label = "Select number of modules", value = 1, min = 1),
selectInput(inputId = "n_samp_choice", label = NULL,
choices = 1:20, width = 500), # number of samples
uiOutput("module_placeholder"),
uiOutput("actionBut.out")
)
server <- function(input, output, session) {
observe( {
output$module_placeholder <- renderUI( {
lapply(1:input$num, moduleUI)
})
})
output$actionBut.out <- renderUI({
print(input$original1)
session$sendCustomMessage(type="jsCode",
list(code= "$('#text').prop('disabled',true)"))
actionButton("copyButton","Copy Code")
})
observeEvent(input$copyButton, {
checked <- unlist(lapply(1:input$num, function(x) {
if(input[[paste(x, "correct", sep="-")]]) x
}))
if(length(checked) == 0) {
to_check <- 1:input$num
} else {
to_check <- (1:input$num)[-checked]
}
cond <- unlist(lapply(to_check, function(x) {
tolower(input[[paste(x, "original", sep="-")]]) == tolower(input[[paste(x, "options", sep="-")]])
}))
if(all(cond)) {
print("great job")
} else {
unmapp <- which(!cond)
optns <- unlist(lapply(1:input$num, function(x) {
input[[paste(x, "options", sep="-")]]
}))
print("The following are unmatched")
print(optns[to_check][unmapp])
}
})
}
shinyApp(ui = ui, server = server)
uiOutput("mappings")
where you have the inputs now and in the server you place something like this
output$mappings <- renderUI({
tagList(
lapply(
1:length(someList),
function(idx){
fluidRow( # first row checkbox
column(width = 2, offset = 0,
checkboxInput(paste0("correct",idx), label = NULL, FALSE)
),
column(width = 2, offset = 0, # text input originial
textInput(inputId = paste0("original",idx), value = let_small[1], label = NULL )
),
column(width = 5, # options for match
selectInput(inputId = paste0("options",idx), label = NULL,
choices = let_caps, width = 500)
)
)
}
)
)
})
to then get the values you can do something like this
observe({
lapply(
1:length(someList),
function(idx){input[[paste0("correct",idx)]]}
)
})
taking your example it could look something like this
library(shiny)
set.seed(42)
n_samp = 5 # this comes from the input
indx <- sample(1:20, n_samp)
let_small <- letters[indx]
let_caps <- sample(LETTERS[indx])
# user input
ui <- fluidPage(
selectInput(inputId = "n_samp_choice", label = NULL,
choices = 1:20, width = 500), # number of samples
uiOutput("mappings"),
)
server <- function(input, output, session) {
output$actionBut.out <- renderUI({
print(input$original1)
session$sendCustomMessage(type="jsCode",
list(code= "$('#text').prop('disabled',true)"))
actionButton("copyButton1","Copy Code")
})
output$mappings <- renderUI({
tagList(
lapply(
1:5,
function(idx){
fluidRow( # first row checkbox
column(width = 2, offset = 0,
checkboxInput(paste0("correct",idx), label = NULL, FALSE)
),
column(width = 2, offset = 0, # text input originial
textInput(inputId = paste0("original",idx), value = let_small[idx], label = NULL )
),
column(width = 5, # options for match
selectInput(inputId = paste0("options",idx), label = NULL,
choices = let_caps, width = 500)
)
)
}
)
)
})
lapply(
1:5,
function(idx){
observeEvent(input[[paste0("options",idx)]],
{
print(input[[paste0("options",idx)]])
},
ignoreInit = TRUE)
}
)
observeEvent(input$copyButton1, {
if(tolower(input$options1) == tolower(input$options1) &
tolower(input$options2) == tolower(input$options2) &
tolower(input$options3) == tolower(input$options3) &
tolower(input$options4) == tolower(input$options4) &
tolower(input$options5) == tolower(input$options5))
{
print("great job")
}else{
unmapp <- which(c(input$correct1, input$correct2,
input$correct3, input$correct4,
input$correct5))
print("The following are unmatched")
print(let_caps[unmapp])
}
})
}
shinyApp(ui = ui, server = server)

Resources