rstudio / shinydashboard

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

Body Content Fails to Render When Using Widgets in Sidebar #28

Closed phillc73 closed 9 years ago

phillc73 commented 9 years ago

If using widgets as nested menu or sub-menu items, content in the dashboardBody fails to render.

Examples

This displays the box in dashboardBody() correctly:

library("shinydashboard")

header <- dashboardHeader()

sidebar <- dashboardSidebar(
  sidebarMenu(
    menuItem("Inputs", icon = icon("bar-chart-o"), tabName = "tabOne"
    )
  )
)

body <- dashboardBody(
  tabItems(
    tabItem("tabOne",
            box(title = "Box One", 
                width = 12,
                height = "500px",
                status = "success", 
                solidHeader = TRUE, 
                collapsible = TRUE,
                verbatimTextOutput("boxOneText")
                )
            )
    )
  )

shinyApp(
  ui = dashboardPage(header, sidebar, body),
  server = function(input, output) {

    output$boxOneText <- renderText({
      paste("A test designed to provoke an emotional response")
    })

  }
)

However, the following code with correctly displaying widget inputs added as menu items, results in the dashboardBody() box disappearing.

library("shinydashboard")

header <- dashboardHeader()

sidebar <- dashboardSidebar(
  sidebarMenu(
    menuItem("Inputs", icon = icon("bar-chart-o"), tabName = "tabOne",
             # Input directly under menuItem
             selectInput("inputTest", "Input Test",
                         choices = c("a", "b", "c", "d"), multiple=TRUE, selectize=TRUE,
                         width = '98%'),

             # Input inside of menuSubItem
             menuSubItem(icon = NULL,
                         sliderInput("inputTest2", "Input test 2", min=0, max=10, value=5,
                                     width = '95%')
                         )
             )
    )
)

body <- dashboardBody(
  tabItems(
    tabItem("tabOne",
            box(title = "Box One", 
                width = 12,
                height = "500px",
                status = "success", 
                solidHeader = TRUE, 
                collapsible = TRUE,
                verbatimTextOutput("boxOneText")
                )
            )
    )
  )

shinyApp(
  ui = dashboardPage(header, sidebar, body),
  server = function(input, output) {

    output$boxOneText <- renderText({
      paste("A test designed to provoke an emotional response")
    })

  }
)
phillc73 commented 9 years ago

After a bit more digging around - I tried sidebar menuItems with datatable content in the body, rather than box() content - I think the issue is not so much the content disappearing, but rather the tabItem is not being loaded. This was not obvious with just one tabItem. However, if adding a second tabItem, and the corresponding menuItem contains input widgets, the tabItem will not load.

Here's some example code:

library("shinydashboard")

header <- dashboardHeader()

sidebar <- dashboardSidebar(
  sidebarMenu(
    menuItem("Inputs One", icon = icon("bar-chart-o"), tabName = "tabOne"),
    menuItem("Inputs Two", icon = icon("line-chart"), tabName = "tabTwo",
             # Input directly under menuItem
             selectInput("inputTest", "Input Test",
                         choices = c("a", "b", "c", "d"), multiple=TRUE, selectize=TRUE,
                         width = '98%'),
             # Input inside of menuSubItem
             menuSubItem(icon = NULL,
                         sliderInput("inputTest2", "Input test 2", min=0, max=10, value=5,
                                     width = '95%')
                         )
             )
    )
)

body <- dashboardBody(
  tabItems(
    tabItem("tabOne",
            box(title = "Box One", 
                width = 12,
                height = "500px",
                status = "success", 
                solidHeader = TRUE, 
                collapsible = TRUE,
                verbatimTextOutput("boxOneText")
            )
    ),
    tabItem("tabTwo",
            box(title = "Box Two", 
                width = 12,
                height = "500px",
                status = "success", 
                solidHeader = TRUE, 
                collapsible = TRUE,
                verbatimTextOutput("boxTwoText")
            )
    )
  )
)

shinyApp(
  ui = dashboardPage(header, sidebar, body),
  server = function(input, output) {

    output$boxOneText <- renderText({
      paste("A test designed to provoke an emotional response")
    })
    output$boxTwoText <- renderText({
      paste("Nothing is worse than having an itch you can never scratch.")
    })

  }
)
wch commented 9 years ago

Thanks for the example code - it's very helpful for tracking down the issue.

The root of the problem is that any menuItem that has child elements, like menuSubItems or shiny inputs, can't be used to control tabItems. It's a little confusing because with AdminLTE 2.x, the menuItem will be highlighted (and expand) when you click on it, but I believe that with AdminLTE 1.x, it was different -- the item would only expand, and not be highlighted.

I'm not sure that it makes sense to allow a menu item to both expand and control tabs, since it makes for some confusing interactions. Right now, it's pretty simple: an expandable menu item can only expand.

Say you have two menu items, X and Y, where just Y has sub-items. Also, they have corresponding tabNames, Xtab and Ytab. Imagine the following:

The trouble is that expanding/collapsing is a toggle sort of event, whereas selecting a tab and making its content visible is an idempotent sort of event. And making one menuItem do both tasks doesn't quite seem right.

If, however, you have thoughts a way to make it work, and make sense, let me know.

phillc73 commented 9 years ago

I can see how it currently could be confusing.

My use case is that certain inputs, or filters, only apply to certain tab content. To save space within dashboardPage, I'd much rather locate the filters in the sidebar. Xtab shows a table. I want to filter this table using a selectInput widget. This widget only applies to the table content in Xtab and therefore could happily live in the X menuItem sidebar. This is a direct extension of how I'd write a Shiny app without shinydashboard. I really like the added features of shinydashboard, but it feels like a small step backwards to force inputs into dashboardPage, when there are multiple menuItems.

(As an aside, global inputs/filters in the dashboardHeader would also be nice!)

Working through your scenarios:

  1. All makes sense. No change.
  2. What if there was an option to not allow any menuItem to collapse unless another menuItem was selected? Y would never collapse until X was selected. I think this makes logical sense and can't specifically think of a reason why one would want Y collapsed, without moving to another menuItem
  3. Force X to expand and Y to collapse. Only one menuItem can be expanded at any one time.
  4. I don't think this can happen if ideas from points 2 and 3 are enforced.

The key thing is to only allow one menuItem to expand at any one time.

garthtarr commented 9 years ago

I'm in the same situation, where I have a number of menuItems each of which should display a different tab and each having their own set of controls. Ideally the tab choice would be triggered when I click on the menuItem.

I agree with @phillc73 in that it's a small step backwards to force inputs into dashboardPage.

My workaround at the moment is to a (superfluous) menuSubItem in each menu item that then needs to be clicked to trigger the tab:

menuItem(text="Tab 1",
    menuSubItem("Show tab",tabName = "tab1", selected=TRUE),
    # controls here
),
menuItem(text="Tab2",
    menuSubItem("Show tab",tabName = "tab2", selected=FALSE),
    # controls here
)

This is OK, but not ideal because it requires two clicks to get the plot displayed. Perhaps an invisible menuSubItem could be implemented that can (optionally) be added to the menuItem (as above) but is triggered automatically when the menuItem is displayed?

wch commented 9 years ago

I just realized it should be possible to use conditionalPanels to get the desired effect. Here's an example (this requires the latest dev version of shinydashboard):

library(shiny)
library(shinydashboard)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(
    sidebarMenu(id = "sidebarmenu",
      menuItem("A", tabName = "a",  icon = icon("group", lib="font-awesome")),
      menuItem("B", tabName = "b", icon = icon("check-circle", lib = "font-awesome")),
      conditionalPanel("input.sidebarmenu === 'b'",
        sliderInput("b", "Under sidebarMenu", 1, 100, 50)
      )
    ),
    sliderInput("x", "Outside of menu", 1, 100, 50)
  ),
  dashboardBody()
)

server <- function(input, output) {}

shinyApp(ui, server)

I have a feeling that other potential solutions would require too much modification of the AdminLTE code and lead to maintenance problems in the future.

garthtarr commented 9 years ago

Thanks for the update @wch, that's a close enough approximation for my purposes. Any idea when the latest dev version will be pushed to CRAN?

wch commented 9 years ago

@garthtarr Great, glad that works for you. I'm going to be out for a little while, so it'll probably be a couple of weeks before the new version goes to CRAN.

manoj8385 commented 9 years ago

thanks, garthtarr.

Mageshpoondi commented 9 years ago

Thank you @phillc73 and @garthtarr, nice work around, though the second click is not as intuitive

ismirsehregal commented 2 years ago

For future readers: Here you can find another workaround to link a tabItem to a childfull menuItem.