rstudio / shinydashboard

Shiny Dashboarding framework
https://rstudio.github.io/shinydashboard/
Other
892 stars 300 forks source link

Always set data-value attribute for .sidebarMenuSelectedTabItem #216

Closed bborgesr closed 7 years ago

bborgesr commented 7 years ago

Fixes #214: make sure that the data-value attribute of .sidebarMenuSelectedTabItem is always set in the body of the ensureActivatedTab() function.

Repro

See #214 or here is a more concise one (bug: input$tabs should start out with the value "tab1", but it starts out with NULL and stays that way until you click on the other tab):

library(shinydashboard)
library(shiny)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(
    sidebarMenu(id = "tabs",
      menuItem("tab1", tabName = "tab1", selected = TRUE),
      menuItem("tab2", tabName = "tab2")
  )),
  dashboardBody()
)

server <- function(input, output, session){
  observe({ print(input$tabs) })
}
shinyApp(ui, server)

Postemortem

This bug was hard to spot for a few reasons:

  1. It doesn't exist if you don't have selected = TRUE on any tab (the default). I.e. this has the right behavior:
library(shinydashboard)
library(shiny)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(
    sidebarMenu(id = "tabs",
      menuItem("tab1", tabName = "tab1"),
      menuItem("tab2", tabName = "tab2")
  )),
  dashboardBody()
)

server <- function(input, output, session){
  observe({ print(input$tabs) })
}
shinyApp(ui, server)
  1. It disappears as soon as you move on to another tab.

  2. It's orthogonal to the matching up of menuItems and tabItems. I.e. this works works as expected, even though input$tabs starts out NULL as well:

library(shinydashboard)
library(shiny)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(
    sidebarMenu(id = "tabs",
      menuItem("tab1", tabName = "tab1", selected = TRUE),
      menuItem("tab2", tabName = "tab2")
  )),
  dashboardBody(
      tabItems(
          tabItem("tab1", "This is tab 1"),
          tabItem("tab2", "This is tab 2")
      )
  )
)

server <- function(input, output, session){
  observe({ print(input$tabs) })
}
shinyApp(ui, server)
  1. It doesn't exist for dynamic sidebar menus. I.e. this has the right behavior:
shinyApp(ui, server)library(shinydashboard)
library(shiny)

ui <- dashboardPage(
    dashboardHeader(),
    dashboardSidebar(
        sidebarMenuOutput("menu")),
    dashboardBody()
)

server <- function(input, output, session){
    output$menu <- renderMenu({
        sidebarMenu(id = "tabs",
            menuItem("tab1", tabName = "tab1", selected = TRUE),
            menuItem("tab2", tabName = "tab2")
        )
    })
    observe({ print(input$tabs) })
}
shinyApp(ui, server)
wch commented 7 years ago

I'm having a bit of a hard time following the logic in the code... I could be missing something, but can all that code be reduced to this?

  var $startTab = $tablinks.filter("[data-start-selected='1']");
  if ($startTab.length === 0) {
    // If no tab starts selected, use the first one, if present
    $startTab = $tablinks.first();
  }

  // If there are no tabs, $startTab.length will be 0.
  if ($startTab.length !== 0) {
    $startTab.tab("show");

    $(".sidebarMenuSelectedTabItem").attr("data-value",
      $startTab.attr("data-value"));
  }

The things I'm not sure about are:

bborgesr commented 7 years ago

I've pushed your code, after some testing. I think we're good to go..?

wch commented 7 years ago

Now that I look at it some more, I think that $tablinks.parent("li").hasClass("active") might have something to do with menuSubItems, like when the first menuItem has menuSubItems.

I think this behaves the same as before, but there's some weird behavior with the tab1 menuItem. It doesn't start expanded, and also, when it is expanded and you click on tab1_1, the little arrow on the right changes from pointing down to pointing left. But that's sort of a separate issue, and fixing those things could be a separate PR if you prefer.

library(shinydashboard)
library(shiny)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(
    sidebarMenu(id = "tabs",
      menuItem("tab1",
        menuSubItem("tab1_1", tabName = "tab1_1")
      ),
      menuItem("tab2", tabName = "tab2")
  )),
  dashboardBody(
      tabItems(
          tabItem("tab1", "This is tab 1"),
          tabItem("tab1_1", "This is tab 1_1"),
          tabItem("tab2", "This is tab 2")
      )
  )
)

server <- function(input, output, session){
  observe({ print(input$tabs) })
}
shinyApp(ui, server)