8

It's possible to use anchor links in flat Shiny apps relatively easily - https://stackoverflow.com/a/28605517/1659890.

However, is it possible for an external link to target a specific tabPanel of a navbarPage in a Shiny app?

Consider the following test app: UI:

shinyUI(navbarPage(
  "Anchor Test",
  tabPanel(
    "Panel 1", fluidPage(HTML(
      paste(
        "<p>I'm the first panel</p>","<p><a href='#irisPlotUI'>Link to irisPlotUI in panel 2</a></p>",
        "<a href='#panel2'>Link to panel 2</a>",sep = ""
      )
    ))
  ),
  tabPanel("Panel 2", fluidPage(
    "I'm the second panel", uiOutput("irisPlotUI")
  ),
  value = "#panel2"),
  tabPanel("Panel 3", "I'm a table!!!")

Server:

shinyServer(function(input, output) {
  output$irisPlotUI <- renderUI(plotOutput("irisPlot"))
  output$irisPlot <- renderPlot(plot(iris$Sepal.Length))
  })

Using the method from the linked answer doesn't work, the id of irisPlotUI is correct but is a child of the tabPanel that the plot lives in.

The data-value of the tabPanel is given the value of "panel2" through the use of the argument value, however the Shiny app continues to give the tabPanel a unique id that I don't know how to target.

I've examined the source of deployed Shiny apps, for instance https://martinjhnhadley.shinyapps.io/AnchorLinks, and found the actual link to the tabPanel: https://internal.shinyapps.io/martinjhnhadley/AnchorLinks/?initialWidth=1074&childId=shinyapp#tab-8850-2

However, navigating directly to this link does not direct me to the tab either.

Are anchor links my only option in targeting parts of the app or is there a Shiny specific solution?

If not, how might I insert a script such as shown here https://stackoverflow.com/a/15637801/1659890 to allow javascript to be executed when landing on the Shiny app to select the tabPanel in a similar manner to this https://groups.google.com/d/msg/shiny-discuss/sJlasQf71fY/RW7Xc8F02IoJ

Solution thanks to daattali

Using daattali's answer I was able to find the following also from him - https://github.com/rstudio/shiny/issues/772#issuecomment-112919149

The following achieves exactly my needs, note that I have chosen to keep with the /?url= convention he used:

UI.R

shinyUI(
  navbarPage( "test", id = 'someID',
              tabPanel("tab1", h1("page1")),

              navbarMenu( "menu",
                          tabPanel('tab2a',  value='nested1', h1("page2a")),
                          tabPanel('tab2b',  value='nested2', h1("page2b")),
                          tabPanel('tab_sub',  "foobar")
              ),
              tabPanel("tab3", h1("page3"))
  ))

SERVER.R

url1 <- url2 <- ""

shinyServer(function(input, output, session) {

  values <- reactiveValues(myurl = c(), parent_tab = "")

  observe({
    # make sure this is called on pageload (to look at the query string)
    # and whenever any tab is successfully changed.
    # If you want to stop running this code after the initial load was
    # successful so that further manual tab changes don't run this,
    # maybe just have some boolean flag for that.

    input$someID
    input$tab_sub_tabs
    query <- parseQueryString(session$clientData$url_search)
    url <- query$url
    if (is.null(url)) {
      url <- ""
    }

    # "depth" is how many levels the url in the query string is
    depth <- function(x) length(unlist(strsplit(x,"/")))

    # if we reached the end, done!
    if (length(values$myurl) == depth(url)) {
      return()
    }
    # base case - need to tell it what the first main nav name is
    else if (length(values$myurl) == 0) {
      values$parent_tab <- "someID"
    }
    # if we're waiting for a tab switch but the UI hasn't updated yet
    else if (is.null(input[[values$parent_tab]])) {
      return()
    }
    # same - waiting for a tab switch
    else if (tail(values$myurl, 1) != input[[values$parent_tab]]) {
      return()
    }
    # the UI is on the tab that we last switched to, and there are more
    # tabs to switch inside the current tab
    # make sure the tabs follow the naming scheme
    else {
      values$parent_tab <- paste0(tail(values$myurl, 1), "_tabs")
    }

    # figure out the id/value of the next tab
    new_tab <- unlist(strsplit(url, "/"))[length(values$myurl)+1]

    # easy peasy.
    updateTabsetPanel(session, values$parent_tab, new_tab)
    values$myurl <- c(values$myurl, new_tab)
  })
})
Community
  • 1
  • 1
  • I forgot about that code I wrote :) I think this code is overly complex for your needs, unless you have many tabs and each tab can have nested tabs and you don't know ahead of time the names of the tabs. That solution that I wrote was specifically for such a general case. In your case, if you only have one level of tabs (vs nested tabs) then most of that code is unnecessary – DeanAttali Oct 09 '15 at 01:26
  • @daattali ahh, well that's certainly true for this minimal working example but the real shiny app has many levels. Good violent though. Personally I think this is something others would benefit from and was hard to find, blog worthy? – Charlie Joey Hadley Oct 09 '15 at 04:44
  • 1
    I'm planning on having a blog pots soon about many different intermediate to advanced shiny techniques, which will include many common problems I see often and the snippets of code to solve them along with explanations. I already have this one in there :) – DeanAttali Oct 09 '15 at 07:17

2 Answers2

7

I found the other answer a little difficult to follow so I created the example below. To navigate to the Data Availability tab, place "?a=b" to at the end of your url. For example if hosting the app on port 7436 the following link will take you directly to the Data Availability page. http://127.0.0.1:7436/?a=b

library(shinydashboard)
library(shiny)
ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(
    sidebarMenu(id = "container",
      menuItem("Channels", tabName = "lt_channels"),
      menuItem("Data Availability", tabName = "data_avail")
    )),
  dashboardBody(
    tabItems(
      tabItem(tabName = "lt_channels",
              h1("Test Fail")),
      tabItem(tabName = "data_avail",
              h1("Test Pass"))
    )
  )
)



server <- function(session, input, output){
  observe({
    query <- parseQueryString(session$clientData$url_search)
    query1 <- paste(names(query), query, sep = "=", collapse=", ")
    print(query1)
    if(query1 == "a=b"){
      updateTabItems(session, inputId = "container", selected = "data_avail")
    }
  })
}
shinyApp(ui = ui, server = server)
JBlaz
  • 81
  • 1
  • 4
4

You could add a search query parameter to the URL (eg. href='www.myapp.com?tab=tab2), and in the app that is being linked you would have to add a bit of logic that changes to a specified tab on initialization if the search string is present (eg. look at session$clientData$url_search and if it exists and there's a tab variable, then use updateTabsetPanel() to go to that tab)

DeanAttali
  • 25,268
  • 10
  • 92
  • 118