R: Làm thế nào để tách biệt logic mã từ các thẻ UI / html?


9

Vấn đề

Khi tự động tạo các phần tử ui ( shiny.tag,, shiny.tag.list...), tôi thường gặp khó khăn khi tách nó khỏi logic mã của mình và thường kết thúc bằng một mớ hỗn độn của lồng nhau tags$div(...), trộn lẫn với các vòng lặp và các câu lệnh có điều kiện. Mặc dù khó chịu và xấu xí khi nhìn vào, nó cũng dễ bị lỗi, ví dụ như khi thay đổi các mẫu html.

Ví dụ sinh sản

Giả sử tôi có cấu trúc dữ liệu sau:

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

Nếu bây giờ tôi muốn đẩy cấu trúc này vào thẻ ui, tôi thường kết thúc bằng một cái gì đó như:

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)

Như bạn có thể thấy, điều này đã khá lộn xộn và vẫn không có gì so với các ví dụ thực sự của tôi.

Giải pháp mong muốn

Tôi đã hy vọng tìm thấy một cái gì đó gần với một công cụ tạo khuôn mẫu cho R, điều đó sẽ cho phép xác định các mẫu và dữ liệu riêng biệt :

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

Những nỗ lực trước đây

Đầu tiên, tôi nghĩ rằng shiny::htmlTemplate()có thể cung cấp một giải pháp, nhưng điều này sẽ chỉ hoạt động với các tệp và chuỗi văn bản, không phải shiny.tags. Tôi cũng đã xem xét một số gói r như whisker , nhưng những gói đó dường như có cùng giới hạn và không hỗ trợ thẻ hoặc cấu trúc danh sách.

Cảm ơn bạn!


Bạn có thể lưu tệp css trong wwwthư mục và sau đó áp dụng biểu định kiểu?
MKa

Trong trường hợp áp dụng css, chắc chắn, nhưng tôi đang tìm kiếm một cách tiếp cận chung cho phép thay đổi cấu trúc html, v.v.
Comfort Eagle

Không có gì hữu ích để thêm nhưng nâng cao và bình luận trong giao tiếp. Lý tưởng nhất là htmlTemplate()sẽ cho phép các điều kiện và vòng lặp ala tay lái, ria mép, cành cây ...
Sẽ

Câu trả lời:


2

Tôi thích tạo các thành phần UI có thể kết hợp và có thể sử dụng lại bằng cách sử dụng các hàm tạo thẻ HTML (hoặc htmltoolsthẻ) Shiny . Từ ứng dụng ví dụ của bạn, tôi có thể xác định một yếu tố "trang" và sau đó là hai thùng chứa nội dung chung và sau đó tạo một số chức năng cho các yếu tố đó:

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

Và sau đó tôi có thể soạn UI của mình với một cái gì đó như thế này:

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)

Bất cứ khi nào tôi cần điều chỉnh kiểu dáng hoặc HTML của một phần tử, tôi chỉ cần đi thẳng đến chức năng tạo ra phần tử đó.

Ngoài ra, tôi vừa mới nhập dữ liệu trong trường hợp này. Tôi nghĩ rằng cấu trúc dữ liệu trong ví dụ của bạn thực sự trộn dữ liệu với các mối quan tâm về UI (kiểu dáng, thẻ HTML), điều này có thể giải thích một số vấn đề phức tạp. Dữ liệu duy nhất tôi thấy là "màu cam" làm tiêu đề và "luận tội" / "công cụ" làm nội dung.

Nếu bạn có dữ liệu phức tạp hơn hoặc cần các thành phần UI cụ thể hơn, bạn có thể sử dụng lại các chức năng như các khối xây dựng:

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

Mong rằng sẽ giúp. Nếu bạn đang tìm kiếm các ví dụ tốt hơn, bạn có thể kiểm tra mã nguồn đằng sau các yếu tố đầu vào và đầu ra của Shiny (ví dụ selectInput()), về cơ bản là các chức năng phun ra các thẻ HTML. Một công cụ tạo khuôn mẫu cũng có thể hoạt động, nhưng không có nhu cầu thực sự khi bạn đã có htmltools+ toàn bộ sức mạnh của R.


Cảm ơn bạn đã trả lời! Tôi đã từng làm nó như thế này, nhưng nó trở nên không thực tế khi phần lớn html không thể được sử dụng lại. Tôi đoán một số loại công cụ mẫu sẽ là giải pháp khả thi duy nhất: /
Comfort Eagle

1

Có lẽ bạn có thể xem xét nhìn vào glue()get().

được():

get() có thể biến chuỗi thành biến / đối tượng.

Vì vậy, bạn có thể rút ngắn:

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

đến

get(y$type)(y$value)

(xem ví dụ dưới đây).

keo dán():

glue()cung cấp một sự thay thế cho paste0(). Nó có thể dễ đọc hơn nếu bạn tập trung nhiều chuỗi và biến vào một chuỗi. Tôi giả sử nó cũng gần với cú pháp của kết quả mong muốn của bạn.

Thay vì:

paste0("height: ", x$height, "px; background-color: ", x$color, ";")

Bạn sẽ viết:

glue("height:{x$height}px; background-color:{x$color};")

Ví dụ của bạn sẽ đơn giản hóa thành:

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

Sử dụng:

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

Lựa chọn thay thế:

Tôi nghĩ htmltemplate là một ý tưởng tốt, nhưng một vấn đề khác là các khoảng trắng không mong muốn: https://github.com/rstudio/htmltools/issues/19#issuecomment-252957684 .


Cảm ơn vì đầu vào của bạn. Mặc dù mã của bạn nhỏ gọn hơn, vấn đề trộn html và logic vẫn còn. : /
Comfort Eagle
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.