R: ¿Cómo separar elegantemente la lógica del código de las etiquetas UI / html?

9

Problema

Al crear dinámicamente ui-elementos ( shiny.tag, shiny.tag.list, ...), a menudo resulta difícil separarlo de mi lógica de código y por lo general terminan con un lío complicado de anidado tags$div(...), mezclado con bucles y sentencias condicionales. Si bien es molesto y feo de ver, también es propenso a errores, por ejemplo, al hacer cambios en las plantillas html.

Ejemplo reproducible

Digamos que tengo la siguiente estructura de datos:

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

Si ahora quiero insertar esta estructura en etiquetas ui, generalmente termino con algo como:

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)

Como puede ver, esto ya es bastante desordenado y todavía nada en comparación con mis ejemplos reales.

Solución deseada

Esperaba encontrar algo cerca de un motor de plantillas para R, que permitiera definir plantillas y datos por separado :

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

Intentos anteriores

Primero, pensé que shiny::htmlTemplate()podría ofrecer una solución, pero esto solo funcionaría con archivos y cadenas de texto, no con shiny.tags. También eché un vistazo a algunos paquetes r como el bigote , pero parece que tienen la misma limitación y no admiten etiquetas o estructuras de lista.

¡Gracias!

Águila de la comodidad
fuente
¿Podría guardar un archivo CSS en la wwwcarpeta y luego aplicar las hojas de estilo?
MKa
En el caso de aplicar css, claro, pero estaba buscando un enfoque general que permita cambios en la estructura html, etc.
Comfort Eagle el
No hay nada útil que agregar, excepto votar y comentar con compasión. Idealmente, htmlTemplate()permitiría condicionales y bucles ala manillar, bigote, ramita ...
Será el

Respuestas:

2

Me gusta crear elementos de interfaz de usuario componibles y reutilizables utilizando funciones que producen etiquetas HTML brillantes (o htmltoolsetiquetas). Desde su aplicación de ejemplo, podría identificar un elemento de "página", y luego dos contenedores de contenido genérico, y luego crear algunas funciones para ellos:

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

Y luego podría componer mi interfaz de usuario con algo como esto:

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)

Cada vez que necesito modificar el estilo o HTML de un elemento, simplemente voy directamente a la función que genera ese elemento.

Además, acabo de incluir los datos en este caso. Creo que la estructura de datos en su ejemplo realmente mezcla datos con preocupaciones de UI (estilo, etiquetas HTML), lo que podría explicar algunas de las enrevesadas. Los únicos datos que veo son "naranja" como encabezado e "impugnación" / "herramienta" como contenido.

Si tiene datos más complicados o necesita componentes de IU más específicos, puede usar funciones nuevamente como bloques de construcción:

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

Espero que ayude. Si está buscando mejores ejemplos, puede consultar el código fuente detrás de los elementos de entrada y salida de Shiny (por ejemplo selectInput()), que son esencialmente funciones que escupen etiquetas HTML. Un motor de plantillas también podría funcionar, pero no hay necesidad real cuando ya tienes htmltools+ toda la potencia de R.

Greg L
fuente
¡Gracias por la respuesta! También solía hacerlo así, pero resulta poco práctico cuando gran parte del html no se puede reutilizar. Supongo que algún tipo de motor de plantillas sería la única solución viable: /
Comfort Eagle el
1

Tal vez podrías considerar investigar glue()y get().

obtener():

get() puede convertir cadenas en variables / objetos.

Para que puedas acortar:

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

a

get(y$type)(y$value)

(ver el ejemplo a continuación).

pegamento():

glue()proporciona una alternativa a paste0(). Podría ser más legible si concéntrate muchas cadenas y variables en una cadena. Supongo que también se parece a la sintaxis del resultado deseado.

En vez de:

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

Tu escribirías:

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

Su ejemplo se simplificaría a:

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

Utilizando:

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

Alternativas:

Creo que htmltemplate es una buena idea, pero otro problema son los espacios en blanco no deseados: https://github.com/rstudio/htmltools/issues/19#issuecomment-252957684 .

Tonio Liebrand
fuente
Gracias por tu contribución. Si bien su código es más compacto, el problema de mezclar html y lógica permanece. : /
Comfort Eagle el