rshinypurrrjstreer

How to create nested div elements based on a nested list


I'd like to recursively create div elements in shiny based on the text elements of a nested list:

nodes <- list(
      list(
        text = "RootA",
        state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE),
        children = list(
          list(
            text = "ChildA1",
            state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE)
          ),
          list(
            text = "ChildA2",
            state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE)
          )
        )
      ),
      list(
        text = "RootB",
        state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE),
        children = list(
          list(
            text = "ChildB1",
            state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE),
            children = list(
              list(
                text = "ChildB1a",
                state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE)
              ),
              list(
                text = "ChildB1b",
                state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE)
              )
            )
          ),
          list(
            text = "ChildB2",
            state = list(loaded = TRUE, opened = TRUE, selected = FALSE, disabled = FALSE)
          )
        )
      )
    )

I tried using purrr:map, but it is only working on the top level:

purrr::map(nodes, function(x){
  if("text" %in% names(x)){
    div(x$text)
  }
})

This is my expected output (using the style parameter just for illustration):

library(shiny)

divs <- list(
  div("RootA",
      div("ChildA1"),
      div("ChildA2"),
      style = "background-color: aqua;"
  ),
  div("RootB",
      div("ChildB1",
          div("ChildB1a"),
          div("ChildB1b")),
      div("ChildB2"),
      style = "background-color: lightgreen;"
    )
)
   
ui <- fluidPage(
  divs
)

server <- function(input, output, session) {
  
}

shinyApp(ui, server)

Solution

  • You need to do a bit of recursion. This should work

    to_div <- function(x) {
      shiny::div(x$text, if ("children" %in% names(x)) lapply(x$children, to_div))
    }
    
    divs <- lapply(nodes, to_div)
    

    This returns

    [[1]]
    <div>
      RootA
      <div>ChildA1</div>
      <div>ChildA2</div>
    </div>
    
    [[2]]
    <div>
      RootB
      <div>
        ChildB1
        <div>ChildB1a</div>
        <div>ChildB1b</div>
      </div>
      <div>ChildB2</div>
    </div>