R: Wie trenne ich die Codelogik elegant von UI / HTML-Tags?

9

Problem

Wenn dynamisch ui-Elemente zu schaffen ( shiny.tag, shiny.tag.list, ...), finde ich es oft schwierig , es von meiner Code - Logik zu trennen und in der Regel mit einem gewundenen Durcheinander von verschachteltem Endetags$div(...) , gemischt mit Schleifen und bedingten Anweisungen. Es ist zwar nervig und hässlich anzusehen, aber auch fehleranfällig, z. B. wenn Änderungen an HTML-Vorlagen vorgenommen werden.

Reproduzierbares Beispiel

Angenommen, ich habe die folgende Datenstruktur:

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

Wenn ich diese Struktur jetzt in UI-Tags verschieben möchte, habe ich normalerweise Folgendes:

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)

Wie Sie sehen können, ist dies bereits ziemlich chaotisch und immer noch nichts im Vergleich zu meinen echten Beispielen.

Gewünschte Lösung

Ich hatte gehofft, etwas in der Nähe einer Vorlagen- Engine für R zu finden, mit dem Vorlagen und Daten separat definiert werden können :

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

Frühere Versuche

Zuerst dachte ich, das shiny::htmlTemplate()könnte eine Lösung bieten, aber dies würde nur mit Dateien und Textzeichenfolgen funktionieren, nicht mit shiny.tags. Ich habe mir auch einige R-Pakete wie Whisker angesehen , aber diese scheinen die gleiche Einschränkung zu haben und unterstützen keine Tags oder Listenstrukturen.

Vielen Dank!

Komfortadler
quelle
Sie könnten eine CSS-Datei unter einem wwwOrdner speichern und dann die Stylesheets anwenden?
MKa
Im Fall der Anwendung von CSS sicher, aber ich suchte nach einem allgemeinen Ansatz, der Änderungen in der HTML-Struktur usw. ermöglicht
Comfort Eagle
Nichts Nützliches zum Hinzufügen, aber Upvoting und Kommentieren in Mitleid. Im Idealfall htmlTemplate()würden Bedingungen und Schlaufen ua Lenker, Schnurrbart, Zweig ...
Will

Antworten:

2

Ich mag es, zusammensetzbare und wiederverwendbare UI-Elemente mit Funktionen zu erstellen, die Shiny HTML-Tags (oder htmltoolsTags) erzeugen . In Ihrer Beispiel-App konnte ich ein "Seiten" -Element und dann zwei generische Inhaltscontainer identifizieren und dann einige Funktionen für diese erstellen:

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

Und dann könnte ich meine Benutzeroberfläche mit so etwas zusammenstellen:

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)

Jedes Mal, wenn ich das Styling oder den HTML-Code eines Elements anpassen muss, gehe ich direkt zu der Funktion, die dieses Element generiert.

Außerdem habe ich in diesem Fall gerade die Daten eingefügt. Ich denke, die Datenstruktur in Ihrem Beispiel mischt Daten wirklich mit UI-Bedenken (Styling, HTML-Tags), was einige der Verschachtelungen erklären könnte. Die einzigen Daten, die ich sehe, sind "orange" als Kopfzeile und "Amtsenthebung" / "Werkzeug" als Inhalt.

Wenn Sie kompliziertere Daten haben oder spezifischere UI-Komponenten benötigen, können Sie Funktionen wie Bausteine ​​wieder verwenden:

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

Ich hoffe, das hilft. Wenn Sie nach besseren Beispielen suchen, können Sie den Quellcode hinter Shinys Eingabe- und Ausgabeelementen (z. B. selectInput()) überprüfen , bei denen es sich im Wesentlichen um Funktionen handelt, die HTML-Tags ausspucken. Ein Template-Motor könnte auch funktionieren, aber es besteht keine wirkliche Notwendigkeit, wenn Sie bereits htmltools+ die volle Leistung von R haben.

greg L.
quelle
Danke für die Antwort! Früher habe ich es auch so gemacht, aber es wird ziemlich unpraktisch, wenn ein Großteil des HTML nicht wiederverwendet werden kann. Ich denke, eine Art Template-Engine wäre die einzig praktikable Lösung: /
Comfort Eagle
1

Vielleicht könnten Sie überlegen, in glue()und zu schauen get().

erhalten():

get() kann Strings in Variablen / Objekte verwandeln.

So könnte man verkürzen:

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

zu

get(y$type)(y$value)

(siehe das folgende Beispiel).

kleben():

glue()bietet eine Alternative zu paste0(). Es könnte besser lesbar sein, wenn Sie viele Zeichenfolgen und Variablen zu einer Zeichenfolge zusammenfassen. Ich gehe davon aus, dass es auch der Syntax Ihres gewünschten Ergebnisses nahe kommt.

Anstatt:

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

Sie würden schreiben:

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

Ihr Beispiel würde vereinfachen:

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

Verwenden von:

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

Alternativen:

Ich denke, htmltemplate ist eine gute Idee, aber ein weiteres Problem sind die unerwünschten Leerzeichen: https://github.com/rstudio/htmltools/issues/19#issuecomment-252957684 .

Tonio Liebrand
quelle
Danke für deinen Beitrag. Während Ihr Code kompakter ist, bleibt das Problem des Mischens von HTML und Logik bestehen. : /
Comfort Eagle