在 Shiny, R 中顺序生成动态用户界面
Sequentially Generating a Dynamic User Interface in Shiny, R
我在 R 中创建了一个动态用户界面,它在流动的行环境中生成四个 selectizeInput
框,并有一组匹配的观察者观察输入。如果选择 addFilter
按钮,则会生成一个额外的流体行,其中包含另外四个框。
我的问题与保存之前输入的值有关。我有一组反应值存储用户输入。然后将这些值输入 updateselectize
上的观察者。如果用户点击缓慢,一切正常。但是,如果用户快速单击,有时这些值会被删除。
我相信这是因为当脚本开始设置新界面时,带有观察者的旧界面还没有完全呈现;但值已被重置。
下面是一个MWE。请注意,要触发错误,您需要快速双击“添加过滤器”按钮。
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
})
})
和 UI.R:
library(shiny)
shinyUI(fluidPage(
# Application title
titlePanel("Dynamic SelectizeInput UI"),
sidebarLayout(
sidebarPanel(
actionButton("AddFilter", label = "Add a Filter"),
uiOutput("filters")
),
mainPanel(
h1("")
)
)
))
您的代码可以简化。你不需要更新 selectizeInput 控件,因为每次选择按钮时都会生成新控件。
你有太多的反应变量。我更改为全局变量。如果你不想使用全局变量,那么使用带有 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)
我在 R 中创建了一个动态用户界面,它在流动的行环境中生成四个 selectizeInput
框,并有一组匹配的观察者观察输入。如果选择 addFilter
按钮,则会生成一个额外的流体行,其中包含另外四个框。
我的问题与保存之前输入的值有关。我有一组反应值存储用户输入。然后将这些值输入 updateselectize
上的观察者。如果用户点击缓慢,一切正常。但是,如果用户快速单击,有时这些值会被删除。
我相信这是因为当脚本开始设置新界面时,带有观察者的旧界面还没有完全呈现;但值已被重置。
下面是一个MWE。请注意,要触发错误,您需要快速双击“添加过滤器”按钮。
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
})
})
和 UI.R:
library(shiny)
shinyUI(fluidPage(
# Application title
titlePanel("Dynamic SelectizeInput UI"),
sidebarLayout(
sidebarPanel(
actionButton("AddFilter", label = "Add a Filter"),
uiOutput("filters")
),
mainPanel(
h1("")
)
)
))
您的代码可以简化。你不需要更新 selectizeInput 控件,因为每次选择按钮时都会生成新控件。 你有太多的反应变量。我更改为全局变量。如果你不想使用全局变量,那么使用带有 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)