[动态创建ui元素(shiny.tag
,shiny.tag.list
,...)时,我经常发现很难将其与代码逻辑分开,并且通常最终会产生混乱的嵌套tags$div(...)
,并与循环和条件语句。虽然令人讨厌和难看,但它也容易出错,例如更改html模板时。
假设我具有以下数据结构:
my_data <- list(
container_a = list(
color = "orange",
height = 100,
content = list(
vec_a = c(type = "p", value = "impeach"),
vec_b = c(type = "h1", value = "orange")
)
),
container_b = list(
color = "yellow",
height = 50,
content = list(
vec_a = c(type = "p", value = "tool")
)
)
)
如果我现在想将此结构放入ui标签中,通常会得到类似以下内容:
library(shiny)
my_ui <- tagList(
tags$div(
style = "height: 400px; background-color: lightblue;",
lapply(my_data, function(x){
tags$div(
style = paste0("height: ", x$height, "px; background-color: ", x$color, ";"),
lapply(x$content, function(y){
if (y[["type"]] == "h1") {
tags$h1(y[["value"]])
} else if (y[["type"]] == "p") {
tags$p(y[["value"]])
}
})
)
})
)
)
server <- function(input, output) {}
shinyApp(my_ui, server)
如您所见,与我的真实示例相比,这已经很混乱,而且仍然一无所有。
我希望为R找到接近模板引擎的东西,这将允许分别定义模板和数据:
# syntax, borrowed from handlebars.js
my_template <- tagList(
tags$div(
style = "height: 400px; background-color: lightblue;",
"{{#each my_data}}",
tags$div(
style = "height: {{this.height}}px; background-color: {{this.color}};",
"{{#each this.content}}",
"{{#if this.content.type.h1}}",
tags$h1("this.content.type.h1.value"),
"{{else}}",
tags$p(("this.content.type.p.value")),
"{{/if}}",
"{{/each}}"
),
"{{/each}}"
)
)
[首先,我认为shiny::htmlTemplate()
可以提供解决方案,但这仅适用于文件和文本字符串,不适用于shiny.tag
。我也看过一些r-packages,例如whisker
,但这些r-packages似乎有相同的限制,不支持标签或列表结构。
谢谢!
我喜欢使用产生Shiny HTML标签(或htmltools
标签)的函数来创建可组合和可重用的UI元素。从您的示例应用程序中,我可以确定一个“页面”元素,然后确定两个通用内容容器,然后为它们创建一些功能:
library(shiny)
my_page <- function(...) {
div(style = "height: 400px; background-color: lightblue;", ...)
}
my_content <- function(..., height = NULL, color = NULL) {
style <- paste(c(
sprintf("height: %spx", height),
sprintf("background-color: %s", color)
), collapse = "; ")
div(style = style, ...)
}
然后我可以用以下内容编写我的UI:
my_ui <- my_page(
my_content(
p("impeach"),
h1("orange"),
color = "orange",
height = 100
),
my_content(
p("tool"),
color = "yellow",
height = 50
)
)
server <- function(input, output) {}
shinyApp(my_ui, server)
每次需要调整元素的样式或HTML时,我都会直接转到生成该元素的函数。
此外,在这种情况下,我只是内联了数据。我认为您示例中的数据结构确实将数据与UI问题(样式,HTML标记)混合在一起,这可能解释了一些复杂性。我看到的唯一数据是标题为“ orange”,内容为“ impeach” /“ tool”。
如果您有更复杂的数据或需要更多特定的UI组件,则可以再次使用诸如构建块之类的功能:
my_content_card <- function(title = "", content = "") {
my_content(
h1(title),
p(content),
color = "orange",
height = 100
)
}
my_ui <- my_page(
my_content_card(title = "impeach", content = "orange"),
my_content(
p("tool"),
color = "yellow",
height = 50
)
)
希望有帮助。如果您正在寻找更好的示例,则可以查看Shiny输入和输出元素(例如selectInput()
)背后的源代码,这些元素本质上是吐出HTML标签的函数。一个模板引擎也可以工作,但是当您已经具有selectInput()
+ R的全部功能时,则没有真正的需求。
也许您可以考虑研究htmltools
和glue()
。
get():
[get()
可以将字符串变成变量/对象。
所以您可以缩短:
get()
to
if (y[["type"]] == "h1") {
tags$h1(y[["value"]])
} else if (y[["type"]] == "p") {
tags$p(y[["value"]])
}
(请参阅下面的示例)。
glue():
get(y$type)(y$value)
提供了glue()
的替代方案。如果将大量字符串和变量集中到一个字符串中,则可能更易于理解。我认为它也看起来与您想要的结果的语法接近。
而不是:
paste0()
您会写:
paste0("height: ", x$height, "px; background-color: ", x$color, ";")
您的示例将简化为:
glue("height:{x$height}px; background-color:{x$color};")
使用:
tagList(
tags$div(style = "height: 400px; background-color: lightblue;",
lapply(my_data, function(x){
tags$div(style = glue("height:{x$height}px; background-color:{x$color};"),
lapply(x$content, function(y){get(y$type)(y$value)})
)
})
)
)
替代项:
我认为htmltemplate是个好主意,但另一个问题是不需要的空格:library(glue)
my_data <- list(
container_a = list(
color = "orange",
height = 100,
content = list(
vec_a = list(type = "p", value = "impeach"),
vec_b = list(type = "h1", value = "orange")
)
),
container_b = list(
color = "yellow",
height = 50,
content = list(
vec_a = list(type = "p", value = "tool")
)
)
)
。