【发布时间】:2016-03-21 10:24:02
【问题描述】:
实际问题
我如何设计(*)一个闪亮的应用程序,其中某些 UI 元素依赖于需要系统处理的多个条件?
(*) 以一种不会让你发疯的可维护方式 ;-)
详情
我读过Build a dynamic UI that reacts to user input 和喜欢conditionalPanel(),但我觉得它对于我想要构建的timetracking app (source code on GitHub) 来说太“一维”了。
我想做的事:
-
拥有一个(或多个)可以触发条件 UI 部分的 UI 元素:
状态 1
-
那些有条件的 UI 部分通常有一些输入字段和至少两个操作按钮:
Create和Cancel:状态 2
-
如果单击
Create,则应适当处理输入(例如将内容写入数据库),然后条件 UI 部分应再次“消失”,因为其条件“已过期”:状态 3
状态 4
-
如果
Cancel被点击,UI部分应该再次“消失”,因为它的状态“过期”:状态 4
随后点击
Trigger应再次“开始循环”
多依赖和动态依赖状态的问题:
AFAIU,如果我只是将依赖项(即下面的 input$action_trigger、input$action_create 和 input$action_cancel)放入构建条件 UI 的反应式上下文中,那么我将面临多轮失效,直到所有依赖项都达到稳定状态(参见下面的output$ui_conditional <- renderUI({}))。
从用户体验的角度来看,这感觉就像必须多次单击元素直到获得所需的内容(查看我的 timetracking app 中的这种“多次单击必要”行为的示例)。
这就是为什么我想出引入某种“依赖状态清除”层的想法(参见下面的ui_decision <- reactive({}))
当前解决方案
我目前的解决方案感觉非常错误,非常脆弱且维护成本很高。你也可以在GitHub找到它
全局:
library(shiny)
GLOBALS <- list()
GLOBALS$debug$enabled <- TRUE
# Auxiliary functions -----------------------------------------------------
createDynamicUi_conditional <- function(
input,
output,
ui_decision,
debug = GLOBALS$debug$enabled
) {
if (debug) {
message("Dynamic UI: conditional ----------")
print(Sys.time())
}
## Form components //
container <- list()
field <- "title"
name <- "Title"
value <- ""
container[[field]] <- textInput(field, name, value)
field <- "description"
name <- "Description"
value <- ""
container[[field]] <- textInput(field, name, value)
## Bundle in box //
value <- if (ui_decision == "hide") {
div()
} else if (ui_decision == "show" || ui_decision == "create") {
container$buttons <- div(style="display:inline-block",
actionButton("action_create", "Create"),
actionButton("action_cancel", "Cancel")
)
do.call(div, args = list(container, title = "conditional dynamic UI"))
} else {
"Not implemented yet"
}
# print(value)
value
}
用户界面部分:
# UI ----------------------------------------------------------------------
ui <- fluidPage(
actionButton("action_trigger", "Trigger 1"),
h3("Database state"),
textOutput("result"),
p(),
uiOutput("ui_conditional")
)
服务器部分:
# Server ------------------------------------------------------------------
server <- function(input, output, session) {
#####################
## REACTIVE VALUES ##
#####################
db <- reactiveValues(
title = "",
description = ""
)
ui_control <- reactiveValues(
action_trigger = 0,
action_trigger__last = 0,
action_create = 0,
action_create__last = 0,
action_cancel = 0,
action_cancel__last = 0
)
#################
## UI DECISION ##
#################
ui_decision <- reactive({
## Dependencies //
## Trigger button:
value <- input$action_trigger
if (ui_control$action_trigger != value) ui_control$action_trigger <- value
## Create button:
## Dynamically created within `createDynamicUi_conditional`
value <- input$action_create
if (is.null(value)) {
value <- 0
}
if (ui_control$action_create != value) {
ui_control$action_create <- value
}
## Cancel button:
## Dynamically created within `createDynamicUi_conditional`
value <- input$action_cancel
if (is.null(value)) {
value <- 0
}
if (ui_control$action_cancel != value) {
ui_control$action_cancel <- value
}
if (GLOBALS$debug$enabled) {
message("Dependency clearance -----")
message("action_trigger:")
print(ui_control$action_trigger)
print(ui_control$action_trigger__last)
message("action_create:")
print(ui_control$action_create)
print(ui_control$action_create__last)
message("action_cancel:")
print(ui_control$action_cancel)
print(ui_control$action_cancel__last)
}
ui_decision <- if (
c (ui_control$action_trigger == 0 && ui_control$action_trigger == 0) ||
c(
ui_control$action_trigger > 0 &&
ui_control$action_trigger <= ui_control$action_trigger__last &&
ui_control$action_cancel > 0 &&
ui_control$action_cancel > ui_control$action_cancel__last
) ||
c(
ui_control$action_create == 0 &&
ui_control$action_create__last > 0
)
) {
"hide"
} else if (
ui_control$action_trigger >= ui_control$action_trigger__last &&
ui_control$action_create == ui_control$action_create__last
) {
## Synchronize //
ui_control$action_cancel__last <- ui_control$action_cancel
"show"
} else if (
ui_control$action_create > ui_control$action_create__last
) {
"create"
} else {
"Not implemented yet"
}
if (GLOBALS$debug$enabled) {
print(ui_decision)
}
## Synchronize //
ui_control$action_trigger__last <- ui_control$action_trigger
ui_control$action_create__last <- ui_control$action_create
ui_decision
})
output$ui_conditional <- renderUI({
createDynamicUi_conditional(input, output, ui_decision = ui_decision())
})
#################
## WRITE TO DB ##
#################
writeToDb <- reactive({
ui_decision <- ui_decision()
if (ui_decision == "create") {
db$title <- input$title
db$description <- input$description
}
})
###################
## RENDER RESULT ##
###################
output$result <- renderText({
writeToDb()
c(
paste0("Title: ", db$title),
paste0("Description: ", db$description)
)
})
}
运行应用:
shinyApp(ui, server)
大图
这是我真正想到的应用程序:timetrackr
它是在没有引入上面草拟的间隙层的情况下构建的。虽然它确实提供了所需的功能,但很多时候,您需要多次单击 UI 元素,直到达到稳定的依赖状态,这真的很烦人。
【问题讨论】:
标签: javascript r user-interface conditional shiny