Direct link to tabItem with R shiny dashboard

athlonshi picture athlonshi · May 11, 2016 · Viewed 7.4k times · Source

I am using the shiny dashboard template to generate my web UI.

I'd like to dynamically generate an infobox when a computation is completed with a link directed to one of the tabItems in dashboardBody.

For example, I can put this in my tabItem1 output,

renderInfoBox({
  infoBox("Completed", 
    a("Computation Completed", href="#tabItem2"), 
  icon = icon("thumbs-o-up"), color = "green"
  )
})

But the problem is that when I click the link, it does nothing. I would like it jumps to tabItem2. The link href seems valid when I hover on it.

Thanks!


Update: Other than using Javascripts, looks like using actionLink and updateTabItems functions in shinydashboard package will work as well.

Answer

K. Rohde picture K. Rohde · May 11, 2016

I apologize for the lengthy code sample, but I had to copy an example with tabItems from the shinydashboard homepage.

Your approach has only few problems. First, if you would inspect the menuItems, you'd see that the actual tab's id is not tabItem2, but shiny-tab-tabItem2. This, plus the extra attribute data-toggle="tab" within the a tag would suffice to open the desired tab. Snippet:

a("Computation Completed", href="#shiny-tab-tabItem2", "data-toggle" = "tab") 

But, this has its limits. First and most obvious, the state of the menuItem in the sidebar is not set to active. This looks very odd and one might not be convinced, that one has been moved to another tab.

Second, and less obvious, if you listen to tab changes (on the server side), you will not get information about this tab switch. Those are triggered by the menuItem being clicked, and the tab itself will not report if it is visible or hidden.

So, my approach will be to simulate that the corresponding menuItem is clicked, and thus, all the above problems are solved.

Code example:

library(shiny)
library(shinydashboard)

ui <- shinyUI(
  dashboardPage(
    dashboardHeader(title = "Some Header"),
    dashboardSidebar(
      sidebarMenu(
        menuItem("Computations", tabName = "tabItem1", icon = icon("dashboard")),
        menuItem("Results", tabName = "tabItem2", icon = icon("th"))
      )
    ),

    dashboardBody(
      tags$script(HTML("
        var openTab = function(tabName){
          $('a', $('.sidebar')).each(function() {
            if(this.getAttribute('data-value') == tabName) {
              this.click()
            };
          });
        }
      ")),
      tabItems(
        tabItem(tabName = "tabItem1",
          fluidRow(
            box(plotOutput("plot1", height = 250)),

            box(
              title = "Controls",
              sliderInput("slider", "Number of observations:", 1, 100, 50)
            )
          ),
          infoBoxOutput("out1")
        ),

        tabItem(tabName = "tabItem2",
          h2("Widgets tab content")
        )
      )
    )
  )
)

server <- function(input, output){
  histdata <- rnorm(500)

  output$plot1 <- renderPlot({
    data <- histdata[seq_len(input$slider)]
    hist(data)
  })

  output$out1 <- renderInfoBox({
    infoBox("Completed",  
      a("Computation Completed", onclick = "openTab('tabItem2')", href="#"),
      icon = icon("thumbs-o-up"), color = "green"
    )
  })
}

shinyApp(ui, server)

Note, that the only important thing is the onclick property, not an href. This means, that every div or other element can be used to create this link. You could even have just the thumbs-up image with this onclick command.

If you have more questions, please comment.

Best Regards


Edit: Whole infoBox clickable.

This is an answer to a comment by OmaymaS. The point was to make the infoBox a clickable container. To achieve this, one can define a new function that makes a somewhat different infoBox. The custom box will be as follows:

customInfoBox <- function (title, tab = NULL, value = NULL, subtitle = NULL, icon = shiny::icon("bar-chart"), color = "aqua", width = 4, href = NULL, fill = FALSE) {
    validateColor(color)
    tagAssert(icon, type = "i")
    colorClass <- paste0("bg-", color)
    boxContent <- div(class = "info-box", class = if (fill) colorClass, 
        onclick = if(!is.null(tab)) paste0("$('.sidebar a')).filter(function() { return ($(this).attr('data-value') == ", tab, ")}).click()"),
        span(class = "info-box-icon", class = if (!fill) colorClass, icon),  
        div(class = "info-box-content", 
            span(class = "info-box-text", title), 
            if (!is.null(value)) span(class = "info-box-number", value), 
            if (!is.null(subtitle)) p(subtitle)
        )
    )
    if (!is.null(href)) boxContent <- a(href = href, boxContent)
    div(class = if (!is.null(width)) paste0("col-sm-", width), boxContent)
}

This code is copied from the original infoBox function definition and only the line with onclick is new. I also added the openTab function (with some twitches) right inside the container such that you dont need to worry where to put this function inside the view. Might be a bit overloaded i feel. This custom info box can be used exactly like the default one and if you pass the additional tab argument, the link to the sidebar is added.


Edit: Subtitle exploit

As Alex Dometrius mentioned, the use of subtitle crashes this functionality. This is because the script tag that was inserted, on accident, was used as the subtitle argument in order to be rendered with the box. To free up this spot, I edited the main example up top such that the script tag is sitting top level in the dashboardBody (literally anywhere in the ui would be fine).

(To avoid confusion: in Version 1, the tags$script was supplied inside of infobox where it was interpreted as the subtitle parameter.)