Ekapunk
Ekapunk

Reputation: 153

Hyperlink that switches to a "hidden" tab in shinydashboard

I want a hyperlink to work in practical terms just as a tab button would. This is my code so far (it has two normal tabs and a "hyperlink tab"):

library(shiny)
library(shinydashboard)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(
    sidebarMenu(
      id = "tabs",
      menuItem("Tab 1", tabName = "tab1", icon = icon("home")),
      menuItem("Tab 2", tabName = "tab2", icon = icon("chart-bar")),
      sidebarUserPanel(
        actionLink("show_hidden_tab", "Show Hidden Tab")
      )
    )
  ),
  dashboardBody(
    
      
    uiOutput("hidden_tab_ui")
    
  )
)

server <- function(input, output, session) {
  hiddenTabContent <- reactiveVal(F)
  
  observeEvent(input$show_hidden_tab, {
    hiddenTabContent(!hiddenTabContent())
    
  })
  
  observeEvent(input$tabs, {
    hiddenTabContent(F)
    updateTabItems(session, "tabs", selected = input$tabs)
  })
  
  output$hidden_tab_ui <- renderUI({
    if(hiddenTabContent()){
      h2("Hidden Tab Content")
      
    }else{
      tabItems(
        tabItem(tabName = "tab1",
                h2("Tab 1 Content")
        ),
        tabItem(tabName = "tab2",
                h2("Tab 2 Content")
        )
      )
      
    }
    
  })
}

shinyApp(ui, server)

It almost works properly, except for the fact that when the app is launched, the content of tab 1 is not displayed until the user switches to tab 2, and then goes back to tab 1

How can I fix this and make the first tab's content visible as soon as the app is launched?

Upvotes: 0

Views: 48

Answers (2)

Jamie
Jamie

Reputation: 1965

Below is an option where you can use shinyjs::hidden to hide the menuItem.

Then you can use your actionLink as a way to show the hidden tab item by using updateTabItems.

library(shiny)
library(shinydashboard)
library(shinyjs)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(
    sidebarMenu(
      id = "tabs",
      menuItem("Tab 1", tabName = "tab1", icon = icon("home")),
      menuItem("Tab 2", tabName = "tab2", icon = icon("chart-bar")),
      hidden(menuItem("Tab 3", tabName = "hidden_tab", icon = icon("chart-bar"))),
      sidebarUserPanel(
        actionLink("show_hidden_tab", "Show Hidden Tab")
      )
    )
  ),
  dashboardBody(
    useShinyjs(),
    tabItems(
      tabItem(tabName = "tab1",
              h2("Tab 1 Content")
      ),
      tabItem(tabName = "tab2",
              h2("Tab 2 Content")
      ),
      tabItem(tabName = "hidden_tab",
            h2("Hidden Content")
      )
    )
  )
)


server <- function(input, output, session) {
  # display hidden tab when action link is clicked
  observeEvent(input$show_hidden_tab, {
    updateTabItems(session, "tabs", "hidden_tab")
  })
}

shinyApp(ui, server)

Example:

enter image description here

Upvotes: 1

Till
Till

Reputation: 6663

shihydashboard::tabItems() shows that behavior when the tabs are created dynamically with shiny::renderUI(). You can insert some Javascript that waits for the browser to load the tabs and then activate the first tab. I modified your code below to do that.

library(shiny)
library(shinydashboard)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(
    sidebarMenu(
      id = "tabs",
      menuItem("Tab 1", tabName = "tab1", icon = icon("home")),
      menuItem("Tab 2", tabName = "tab2", icon = icon("chart-bar")),
      sidebarUserPanel(
        actionLink("show_hidden_tab", "Show Hidden Tab")
      )
    )
  ),
  dashboardBody(
    tags$script(
      HTML(
        "
          function waitForElm(selector) {
              return new Promise(resolve => {
                  if (document.querySelector(selector)) {
                      return resolve(document.querySelector(selector));
                  }

                  const observer = new MutationObserver(mutations => {
                      if (document.querySelector(selector)) {
                          resolve(document.querySelector(selector));
                          observer.disconnect();
                      }
                  });

                  observer.observe(document.body, {
                      childList: true,
                      subtree: true
                  });
              });
          }

          waitForElm('#shiny-tab-tab1').then((elm) => {
            //console.log('Element is ready');
            //console.log(elm.textContent);
            elm.classList.add('active');
          });
       "
      )),
    
    
    uiOutput("hidden_tab_ui")
    
  )
)

server <- function(input, output, session) {
  hiddenTabContent <- reactiveVal(F)
  
  observeEvent(input$show_hidden_tab, {
    hiddenTabContent(!hiddenTabContent())
    
  })
  
  observeEvent(input$tabs, {
    hiddenTabContent(F)
    updateTabItems(session, "tabs", selected = input$tabs)
  })
  
  output$hidden_tab_ui <- renderUI({
    if(hiddenTabContent()){
      h2("Hidden Tab Content")
      
    }else{
      tabItems(
        tabItem(tabName = "tab1",
                h2("Tab 1 Content")
        ),
        tabItem(tabName = "tab2",
                h2("Tab 2 Content"))
        
      )
      
    }
    
  })
}

shinyApp(ui, server)

I noticed that there are more issues with this solution than just the first tab not being shown on page load:

  • When the hidden tab link is clicked again, nothing is shown.
  • After the hidden tab link is clicked, the last clicked menu item is still active and can not be clicked before another menu item is clicked.

I think you might be better off not using renderUI() to create the tabs. Instead, create three tabs in the UI definition, but skip the menu item for the third (hidden) tab. Attach an onlick event to the actionLink that execute a Javascript function which:

  1. deactivates the currently active tab.
  2. activates the hidden tab
  3. deactivates the currently active menu item

Here are some Javascript snippets for you to toy with:

When tab 2 is active/displayed, the code below will show tab 1 and hide tab 2.

document.querySelector("#shiny-tab-tab1").classList.add('active');
document.querySelector("#shiny-tab-tab2").classList.remove('active');

This code will deactivate all sidebar menu items, making them clickable.

menu_items = document.querySelector('.sidebar-menu').getElementsByTagName('li');
menu_items_arr = [...menu_items];
menu_items_arr.map(d => d.classList.remove('active'))

Upvotes: 1

Related Questions