Ferroao
Ferroao

Reputation: 3056

How to remember last active tabs in a multiple tabset setup?

Is there a more native way of remembering the last active tab, when changing tabsets (here in header / navbar), to return to it upon tabset change in bs4Dash?

library(shiny)
library(bs4Dash)
library(shinyjs)

ui <- bs4DashPage(
  header = bs4DashNavbar(
    title = "Remember Last Tab in bs4Dash",
    tags$div(
      id = "tabset-container",
      style = "display: flex; justify-content: center; gap: 10px; padding: 10px;",
      div(
        id = "tabset1",
        style = "background-color: #0073B7; padding: 10px; color: white; cursor: pointer;", "Tab Set 1"
      ),
      div(
        id = "tabset2",
        style = "background-color: #0073b7; padding: 10px; color: white; cursor: pointer;", "Tab Set 2"
      )
    )
  ),
  sidebar = bs4DashSidebar(
    uiOutput("sidebar_menu")
  ),
  body = bs4DashBody(
    useShinyjs(),
    tags$script(HTML("
        $(document).on('shiny:connected', function (event) {
      Shiny.setInputValue('activeTabSet', 'tabset1')
      })

    Shiny.addCustomMessageHandler('selectTab', function(tabName) {
      var sidebar = document.querySelector('[data-widget=\"treeview\"]');
      var tab = sidebar.querySelector('[data-value=\"' + tabName + '\"]'); 
      if (tab) tab.click(); 
    });

    Shiny.addCustomMessageHandler('tabsetReady', function(tabset) {
      setTimeout(function() {
        Shiny.setInputValue('tabsetReady', tabset, {priority: 'event'});
      }, 100);
    });
  ")),
    bs4TabItems(
      bs4TabItem(tabName = "tab1_1", h2("Content for Tab 1.1")),
      bs4TabItem(tabName = "tab1_2", h2("Content for Tab 1.2")),
      bs4TabItem(tabName = "tab2_1", h2("Content for Tab 2.1")),
      bs4TabItem(tabName = "tab2_2", h2("Content for Tab 2.2"))
    )
  )
)

server <- function(input, output, session) {
  # Reactive values to store the last visited tab for each tabset
  lastTabs <- reactiveValues(tabset1 = "tab1_1", tabset2 = "tab2_1")

  shinyjs::onclick("tabset1", {
    runjs('Shiny.setInputValue("activeTabSet", "tabset1")')
  })

  shinyjs::onclick("tabset2", {
    runjs('Shiny.setInputValue("activeTabSet", "tabset2")')
  })

  # Dynamically render the sidebar menu based on the active tabset
  output$sidebar_menu <- renderUI({
    req(input$activeTabSet)
    if (input$activeTabSet == "tabset1") {
      bs4SidebarMenu(
        id = "sidebar",
        bs4SidebarMenuItem("Tab 1.1", tabName = "tab1_1", icon = icon("dashboard")),
        bs4SidebarMenuItem("Tab 1.2", tabName = "tab1_2", icon = icon("chart-bar"))
      )
    } else if (input$activeTabSet == "tabset2") {
      bs4SidebarMenu(
        id = "sidebar",
        bs4SidebarMenuItem("Tab 2.1", tabName = "tab2_1", icon = icon("globe")),
        bs4SidebarMenuItem("Tab 2.2", tabName = "tab2_2", icon = icon("cogs"))
      )
    }
  })

  observeEvent(input$sidebar, {
    req(input$activeTabSet)

    if (input$activeTabSet == "tabset1") {
      lastTabs$tabset1 <- input$sidebar
    } else if (input$activeTabSet == "tabset2") {
      lastTabs$tabset2 <- input$sidebar
    }
  })

  observeEvent(input$activeTabSet, {
    req(input$activeTabSet)
    session$sendCustomMessage("tabsetReady", input$activeTabSet)
  })

  observeEvent(input$tabsetReady, {
    req(input$tabsetReady)

    if (input$tabsetReady == "tabset1") {
      session$sendCustomMessage("selectTab", lastTabs$tabset1)
    } else if (input$tabsetReady == "tabset2") {
      session$sendCustomMessage("selectTab", lastTabs$tabset2)
    }
  })
}

shinyApp(ui, server)

Upvotes: 2

Views: 213

Answers (2)

Phil
Phil

Reputation: 1219

Answer in "more native way", without extra javascript

library(shiny)
library(bs4Dash)


ui <- bs4DashPage(
  header = bs4DashNavbar(
    title = "Remember Last Tab in bs4Dash",
    tags$div(
      id = "tabset-container",
      style = "display: flex; justify-content: center; gap: 10px; padding: 10px;",
      bs4Dash::actionButton("tabset1",
                            "Tab Set 1", 
                            style = "background-color: #0073b7; padding: 10px; color: white; cursor: pointer;"
      ),
      bs4Dash::actionButton("tabset2",
                            "Tab Set 2", 
                            style = "background-color: #0073b7; padding: 10px; color: white; cursor: pointer;"
      )
    )
  ),
  sidebar = bs4DashSidebar(
    uiOutput("sidebar_menu")
  ),
  body = bs4DashBody(
    bs4TabItems(
      bs4TabItem(tabName = "tab1_1", h2("Content for Tab 1.1")),
      bs4TabItem(tabName = "tab1_2", h2("Content for Tab 1.2")),
      bs4TabItem(tabName = "tab2_1", h2("Content for Tab 2.1")),
      bs4TabItem(tabName = "tab2_2", h2("Content for Tab 2.2"))
    )
  )
)

server <- function(input, output, session) {
  
  lastTabs <- reactiveValues(tabset1 = "tab1_1", 
                             tabset2 = "tab2_1",
                             activeTabSet="tabset1"
  )
  
  updateTab.fct <- function  (param_input, lastTabs_input) {
    if (!(is.null(param_input)) &&   param_input >0 ) {
      if (!is.null(lastTabs_input)) {
        
        
        updatebs4TabItems(
          session,
          inputId = "sidebar",
          selected =lastTabs_input[[lastTabs_input$activeTabSet]]
        )
      }
    }
  }
  observeEvent(input$tabset1, {
    lastTabs$activeTabSet <- "tabset1" 
    lastTabs$tabset1 <- lastTabs[[lastTabs$activeTabSet]]
    updateTab.fct(input$tabset1,lastTabs)
    
    
    
  }, ignoreInit = TRUE)
  
  observeEvent(input$tabset2, {
    lastTabs$activeTabSet <- "tabset2" 
    lastTabs$tabset2 <- lastTabs[[lastTabs$activeTabSet]]
    updateTab.fct(input$tabset2,lastTabs)
    
    
  }, ignoreInit = TRUE)
  
  observeEvent(input$sidebar, {
    
    if (is.null(input$sidebar) ) {
      #  lastTabs$activeTabSet <- "tabset1" 
      lastTabs$tabset1 <- lastTabs[[lastTabs$activeTabSet]]
      updateTab.fct(1,lastTabs)
    } else if (lastTabs$activeTabSet == "tabset1") {
      lastTabs$tabset1 <- input$sidebar
    } else if (lastTabs$activeTabSet == "tabset2") {
      lastTabs$tabset2 <- input$sidebar
    }
  }, ignoreInit = FALSE, ignoreNULL = FALSE)
  
  
  output$sidebar_menu <- renderUI({
    
    if (ifelse (is.null(lastTabs$activeTabSet),"tabset1", lastTabs$activeTabSet) == "tabset1") {
      
      bs4SidebarMenu(
        id = "sidebar",
        bs4SidebarMenuItem("Tab 1.1", tabName = "tab1_1", icon = icon("dashboard")),
        bs4SidebarMenuItem("Tab 1.2", tabName = "tab1_2", icon = icon("chart-bar"))
      )
    } else  {
      
      bs4SidebarMenu(
        id = "sidebar",
        bs4SidebarMenuItem("Tab 2.1", tabName = "tab2_1", icon = icon("globe")),
        bs4SidebarMenuItem("Tab 2.2", tabName = "tab2_2", icon = icon("cogs"))
      )
    }
  })
  
}

shinyApp(ui, server)

Upvotes: 2

Tim G
Tim G

Reputation: 4182

library(shiny)
library(bs4Dash)

ui <- bs4DashPage(
  header = bs4DashNavbar(
    title = "Remember Last Tab in bs4Dash",
    tags$div(
      id = "tabset-container",
      style = "display: flex; justify-content: center; gap: 10px; padding: 10px;",
      lapply(c("tabset1", "tabset2"), function(tabset) {
        div(
          id = tabset,
          style = "background-color: #0073B7; padding: 10px; color: white; cursor: pointer;",
          toupper(tabset)
        )
      })
    )
  ),
  sidebar = bs4DashSidebar(
    uiOutput("sidebar_menu")
  ),
  body = bs4DashBody(
    tags$head(tags$script(HTML("
      $(document).ready(function() {
        // Bind click events for tabsets dynamically
        ['tabset1', 'tabset2'].forEach(function(tabset) {
          $('#' + tabset).on('click', function() {
            Shiny.setInputValue('activeTabSet', tabset);
          });
        });
      });

      // Push tab to URL
      Shiny.addCustomMessageHandler('updateURL', function(tab) {
        history.pushState(null, '', '?tab=' + tab);
      });
    "))),
    bs4TabItems(
      bs4TabItem(tabName = "tab1_1", h2("Content for Tab 1.1")),
      bs4TabItem(tabName = "tab1_2", h2("Content for Tab 1.2")),
      bs4TabItem(tabName = "tab2_1", h2("Content for Tab 2.1")),
      bs4TabItem(tabName = "tab2_2", h2("Content for Tab 2.2"))
    )
  )
)

server <- function(input, output, session) {
  # Reactive values to track last visited tabs
  lastTabs <- reactiveValues(tabset1 = "tab1_1", tabset2 = "tab2_1")
  activeTabSet <- reactiveVal("tabset1")
  
  # Helper: Update sidebar menu based on active tabset
  updateSidebarMenu <- function(tabset) {
    tabs <- if (tabset == "tabset1") {
      list(
        list("Tab 1.1", "tab1_1", "dashboard"),
        list("Tab 1.2", "tab1_2", "chart-bar")
      )
    } else {
      list(
        list("Tab 2.1", "tab2_1", "globe"),
        list("Tab 2.2", "tab2_2", "cogs")
      )
    }
    
    bs4SidebarMenu(
      id = "sidebar",
      lapply(tabs, function(tab) {
        bs4SidebarMenuItem(tab[[1]], tabName = tab[[2]], icon = icon(tab[[3]]))
      })
    )
  }
  
  # Reactive: Determine initial tab from URL
  initialTab <- reactive({
    parseQueryString(session$clientData$url_search)$tab %||% "tab1_1"
  })
  
  # Observe changes in active tabset
  observeEvent(input$activeTabSet, {
    activeTabSet(input$activeTabSet)
    updatebs4TabItems(
      session,
      inputId = "sidebar",
      selected = lastTabs[[input$activeTabSet]]
    )
  })
  
  # Render the sidebar dynamically
  output$sidebar_menu <- renderUI({
    updateSidebarMenu(activeTabSet())
  })
  
  # Track and save the active tab within the current tabset
  observeEvent(input$sidebar, {
    lastTabs[[activeTabSet()]] <- input$sidebar
    session$sendCustomMessage('updateURL', input$sidebar)
  })
  
  # Initialize the app with the correct tab on load
  observeEvent(initialTab(), {
    updatebs4TabItems(session, inputId = "sidebar", selected = initialTab())
  }, once = TRUE)
}

shinyApp(ui, server)

Upvotes: 1

Related Questions