R: Bagaimana memisahkan logika kode dari UI / html-tag secara elegan?

9

Masalah

Ketika secara dinamis membuat elemen ui ( shiny.tag,, shiny.tag.list...), saya sering merasa sulit untuk memisahkannya dari logika kode saya dan biasanya berakhir dengan kekacauan bersarang tags$div(...), dicampur dengan loop dan pernyataan kondisional. Meskipun menjengkelkan dan jelek untuk dilihat, itu juga rawan kesalahan, misalnya ketika membuat perubahan ke html-templates.

Contoh yang bisa direproduksi

Katakanlah saya memiliki struktur data berikut:

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

Jika sekarang saya ingin mendorong struktur ini menjadi ui-tag, saya biasanya berakhir dengan sesuatu seperti:

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)

Seperti yang Anda lihat, ini sudah sangat berantakan dan masih tidak seberapa dibandingkan dengan contoh nyata saya.

Solusi yang diinginkan

Saya berharap menemukan sesuatu yang dekat dengan mesin templating untuk R, yang akan memungkinkan untuk mendefinisikan template dan data secara terpisah :

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

Upaya sebelumnya

Pertama, saya pikir itu shiny::htmlTemplate()bisa menawarkan solusi, tetapi ini hanya akan bekerja dengan file dan string teks, bukan shiny.tags. Saya juga telah melihat beberapa paket-r seperti kumis , tetapi mereka tampaknya memiliki batasan yang sama dan tidak mendukung tag atau struktur daftar.

Terima kasih!

Kenyamanan elang
sumber
Anda dapat menyimpan file css di bawah wwwfolder dan kemudian menerapkan style sheet?
MKa
Dalam kasus menerapkan css, tentu saja, tetapi saya sedang mencari pendekatan umum yang memungkinkan untuk perubahan dalam struktur html, dll.
Comfort Eagle
Tidak ada yang berguna untuk ditambahkan selain upvoting dan komentar dalam simpati. Idealnya, htmlTemplate()akan memungkinkan untuk conditional dan loop ala setang, kumis, ranting ...
Will

Jawaban:

2

Saya suka membuat elemen UI komposable dan dapat digunakan kembali menggunakan fungsi yang menghasilkan tag (atau htmltoolstag) Shiny HTML . Dari contoh aplikasi Anda, saya dapat mengidentifikasi elemen "halaman", dan kemudian dua wadah konten umum, dan kemudian membuat beberapa fungsi untuk itu:

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

Dan kemudian saya bisa membuat UI saya dengan sesuatu seperti ini:

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)

Setiap kali saya perlu mengubah style atau HTML suatu elemen, saya langsung saja ke fungsi yang menghasilkan elemen itu.

Juga, saya baru saja menggarisbawahi data dalam kasus ini. Saya pikir struktur data dalam contoh Anda benar-benar mencampur data dengan masalah UI (gaya, tag HTML), yang mungkin menjelaskan beberapa kekusutan. Satu-satunya data yang saya lihat adalah "oranye" sebagai header, dan "impeach" / "alat" sebagai konten.

Jika Anda memiliki data yang lebih rumit atau memerlukan komponen UI yang lebih spesifik, Anda dapat menggunakan fungsi lagi seperti blok bangunan:

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

Semoga itu bisa membantu. Jika Anda mencari contoh yang lebih baik, Anda dapat memeriksa kode sumber di belakang elemen input dan output Shiny (misalnya selectInput()), yang pada dasarnya adalah fungsi yang mengeluarkan tag HTML. Mesin templating juga bisa bekerja, tetapi tidak ada kebutuhan nyata ketika Anda sudah mendapatkan htmltools+ kekuatan penuh dari R.

greg L
sumber
Terima kasih atas jawabannya! Saya biasa melakukannya seperti ini juga, tetapi menjadi sangat tidak praktis ketika banyak html tidak dapat digunakan kembali. Saya kira beberapa jenis template-engine akan menjadi satu-satunya solusi yang layak: /
Comfort Eagle
1

Mungkin Anda bisa mempertimbangkan untuk melihat glue()dan get().

Dapatkan():

get() dapat mengubah string menjadi variabel / objek.

Jadi Anda dapat mempersingkat:

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

untuk

get(y$type)(y$value)

(lihat contoh di bawah).

lem():

glue()menyediakan alternatif untuk paste0(). Ini bisa lebih mudah dibaca jika Anda memusatkan banyak string dan variabel ke string. Saya menganggap itu juga terlihat dekat dengan sintaks dari hasil yang Anda inginkan.

Dari pada:

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

Anda akan menulis:

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

Contoh Anda akan disederhanakan menjadi:

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

Menggunakan:

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

Alternatif:

Saya pikir htmltemplate adalah ide yang bagus, tetapi masalah lain adalah spasi putih yang tidak diinginkan: https://github.com/rstudio/htmltools/issues/19#issuecomment-252957684 .

Tonio Liebrand
sumber
Terima kasih atas masukan Anda. Meskipun kode Anda lebih ringkas, masalah pencampuran html dan logika tetap ada. : /
Comfort Eagle