Get list of elements in shinydashboard body

Viewed 116

I have a shiny app, which uses a shinydashboard sidebar as navigation. Each named tab is its own tabSetPanel. Because each tabSetPanel can have a different number of tabs (and over time, I'm hoping it to be possible for more to be added and the whole app still function), I'd like to get the names of the tabs programmatically.

Is there a way I can get a list output for the tab elements of the body object? e.g.

tabs

tabs$plots
"iris"

tabs$plots
"mtcars"

reprex:

library(shiny)
library(shinydashboard)
library(magrittr)
library(ggplot2)


body <- dashboardBody(
  tabItem(
    "plots",
    tabsetPanel(
      tabPanel(
        "iris",
        tags$h1("iris"),
        sidebarLayout(
          sidebarPanel(
            selectInput("x_var", "Select X variable: ", choices = names(iris)),
            selectInput("y_var", "Select Y variable: ", choices = names(iris))
          ),
          mainPanel(
            plotOutput("iris_plot"),
            textOutput("ui_elements")
          )
        )
      ),
      tabPanel(
        "mtcars",
        tags$h1("mtcars"),
        sidebarLayout(
          sidebarPanel(
            selectInput("x_var_cars", "Select X variable: ", choices = names(mtcars)),
            selectInput("y_var_cars", "Select Y variable: ", choices = names(mtcars))
          ),
          mainPanel(
            plotOutput("mtcars_plot")
          )
        )
      )
    )
  )
)

ui <- dashboardPage(
  dashboardHeader(title = "Test App"),
  dashboardSidebar(
    sidebarMenu(
      id = "sidebar_menu",
      menuItem(
        "plots", tabName = "plots"
      )
    )
  ),
  body
)

server <- function(input, output) {
  output$iris_plot <- renderPlot({
    iris %>% 
      ggplot() +
      aes(x = .data[[input$x_var]], y = .data[[input$y_var]], colour = Species) +
      geom_point()
  })
  
  output$mtcars_plot <- renderPlot({
    mtcars %>% 
      ggplot() +
      aes(x = .data[[input$x_var_cars]], y = .data[[input$y_var_cars]]) +
      geom_point()
  })
}

shinyApp(ui, server)

1 Answers

My unsophisticated attempt. Digging around the body object it's nested list of lists of lists ... Most of the lists are of length 1 (I think they correspond to div tags in the shiny app) so I can get the names of the tabs in the main 'pages' by looping through for the number of pages. For reference I'll call these tabs: 'child tabs'.

Limitations

Hard-coded

This will only work assuming I know the exact routes (how many child elements are between):

  • from root level to page tags
  • from the page tags to tab tags

Obviously if I wanted to scale, I'd need a programmatic way of getting the routes as well. Luckily in this case, I know the number of tags between these main levels won't change.

Speed

This is also quite slow. I'm not sure if this originates from me using a loop. I thought of unlisting the body object and just indexing for certain elements (tags) but that was also quite slow. It would also not be able to distinguish between a tab from a tabsetPanel (a child tab) and tab from a tabBox (a grandchild tab), which would be necessary.

# set 'root' to pages
page_layer <- body$children[[1]]$children

# get number of pages
n_pages <- length(page_layer)

child_tabs <- list()

# for each page...
for (i in 1:n_pages) {
  # get the name of the page
  page <- page_layer[[i]]$attribs$`id`
  # Get the number of child tabs. If the page has no child tabs, indexing will
  # throw error so it needs to be caught.
  child_tabs[[page]] <- tryCatch(
    length(page_layer[[i]]$children[[1]]$children[[1]]$children[[1]]),
    error = function(e) 0
    )
  
  # if there are any child tabs...
  if (child_tabs[[i]] > 0) {
    # get the name of each 
    for (j in 1:child_tabs[[i]]) {
    child_tabs[[i]][j] <- page_layer[[i]]$children[[1]]$children[[1]]$children[[1]][[j]]$children[[1]]$attribs$`data-value`
    }
  }
}

child_tabs

Output

$`shiny-tab-plots`
[1] "iris"   "mtcars"
Related