R:如何优雅地将代码逻辑与UI / html-tags分开?


9

问题

动态创建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.tags。我也看了一些诸如whisker的 r-package ,但是它们似乎具有相同的局限性,并且不支持标签或列表结构。

谢谢!


您可以将css文件保存在www文件夹下,然后应用样式表?
MKa

在应用CSS的情况下,肯定的,但我一直在寻找一个通用的方法,允许在HTML的结构等的变化
舒适老鹰

补充,在敬意中进行评论和评论无用。理想的情况下,htmlTemplate()将允许条件和循环ALA车把,胡子,小枝...
威尔

Answers:


2

我喜欢使用产生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, ...)
}

然后,我可以用以下内容编写用户界面:

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标签的函数。模板引擎也可以工作,但是当您已经拥有htmltools+ R的全部功能时,则没有真正的需求。


谢谢您的回答!我也曾经这样做过,但是当许多html无法重用时,它变得不切实际。我猜想某种模板引擎将是唯一可行的解​​决方案:/
Comfort Eagle

1

也许您可以考虑研究glue()get()

得到():

get() 可以将字符串转换为变量/对象。

因此,您可以缩短:

if (y[["type"]] == "h1") {
    tags$h1(y[["value"]])
} else if (y[["type"]] == "p") {
    tags$p(y[["value"]])
}

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)}) 
      )
    })
  )
)

使用:

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")
    )
  )  
)

备择方案:

我认为htmltemplate是个好主意,但另一个问题是不需要的空格:https : //github.com/rstudio/htmltools/issues/19#issuecomment-252957684


感谢您的输入。虽然您的代码更加紧凑,但是混合使用html和逻辑的问题仍然存在。:/
Comfort Eagle
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.