shiny: start the app with hidden tabs, with NO delay
Asked Answered
B

4

11

I would like to build an application and some of the tabs will be hidden to the user until he types the right password. I know how to do this with shinyjs::hideTab:

library(shiny);library(shinyjs)
ui <- fluidPage(useShinyjs(),
  navbarPage("hello", id="hello",
             tabPanel("home", br(), h3("this is home"),passwordInput("pass", "enter 'password' to see the tabs: "),actionButton("enter", "enter")),
             tabPanel("tab2", br(), h4("this is tab2")),
             tabPanel("tab3 with a lot of stuff in it", br(), h4("this is tab3"))))
server <- function(input, output, session) {
  hideTab("hello", "tab2"); hideTab("hello", "tab3 with a lot of stuff in it")
  observeEvent(input$enter, {
    if (input$pass == "password"){showTab("hello", "tab2"); showTab("hello", "tab3 with a lot of stuff in it")}})}
shinyApp(ui, server)

However there is a little "thing". In my application, the hidden tabs have a lot of stuff, like widgets, uiOutputs, plots, images, file reading in global.R, etc. The consequence is that the loading time is higher and during this loading time of the application (before the hideTab instruction gets run) the user actually sees the hidden tab and can even click on them and see what's inside. They stay "visible" for like 1 second and then get hidden.

Is there a way to make them immediately hidden, before the UI gets built? I'd prefer a solution without having to put all my ui code into the server.R script...

Thanks

Beslobber answered 4/1, 2018 at 13:42 Comment(2)
It doesn't look like there is a great solution unfortunately (github.com/daattali/shinyjs/issues/43). As mentioned in the issue, one idea could be to use CSS but that might be more effort than its worthShizue
@MikeH. Just a comment on the procedure here: Hiding a tab until a password is entered is very insecure. The style="display: none;" can simply be removed by the user via rightclick in every common browser. It might be a better idea to use appendTab to create the tab after the user entered the correct password.Initial
S
13

You could use javascript with extendShinyjs() to hide the tabs you want on page load:

Javascript code:

shinyjs.init = function(){
  $('#hello li a[data-value="tab3_val"]').hide();
  $('#hello li a[data-value="tab2_val"]').hide();
}

R code:

ui <- fluidPage(useShinyjs(),
                #Added this js
                extendShinyjs(script = path_to_javascript_file),
                navbarPage("hello", id="hello",
                           tabPanel("home", br(), h3("this is home"),passwordInput("pass", "enter 'password' to see the tabs: "),actionButton("enter", "enter")),
                           tabPanel("tab2", value = "tab2_val", br(), h4("this is tab2")),
                           tabPanel("tab3 with a lot of stuff in it", value = "tab3_val", br(), h4("this is tab3"))))

server <- function(input, output, session) {

  observeEvent(input$enter, {
    if (input$pass == "password"){
      show(selector = '#hello li a[data-value="tab3_val"]')
      show(selector = '#hello li a[data-value="tab2_val"]')
      }})}
shinyApp(ui, server)

Alternatively the CSS actually isn't too complicated. If you wanted to go that route you could simply replace the extendShinyjs() call in the above with:

tags$head(tags$style(HTML("#hello li a[data-value = 'tab2_val'], #hello li a[data-value = 'tab3_val'] {
                             display: none;
 }")))

The downside to this is that the formatting of the tabs appears to be off after un-hiding them.

Shizue answered 4/1, 2018 at 15:10 Comment(7)
Hi @Mike H. thanks for this great answer. This weird thing happens like the name of the tab reappearing after password input has a top vertical alignment...different from the original tabs... how to correct this?Beslobber
@agenis, it has to do this the display: none; property. I'll update my answer with a better (simple) javascript functionShizue
Hmm, doesn't work for me. It hides it right away, but the show function doesn't seem to work. Triple-checked everythingTaeniasis
@Taeniasis hard to say what the problem is then without seeing the code. My only guess would be to check the selector, but I'm assuming you already checked thatShizue
When using tags$, the code should be tags$head(tags$script( instead of tags$head(tags$style(.Banka
Mea culpa, you are indeed correct. What I was referring to was the inclusion of the js function code (which works better) into the fluidPage. Somehow I lost track of the changes that I made to the code when I made my comment. My accept my apologies.Banka
@MikeBadescu no problem!Shizue
I
11

I'd go with renderUI (see @BertilBaron's answer) or appendTab because it's easy to bypass the password when hiding the tabs:

Bypass password

Here is how to achive the desired behaviour via appendTab avoiding the above with basic shiny, no additional JS:

library(shiny)

ui <- fluidPage(navbarPage("hello", id = "hello",
  tabPanel(
    "home",
    br(),
    h3("this is home"),
    passwordInput("pass", "enter 'password' to see the tabs: "),
    actionButton("enter", "enter")
  )
))

server <- function(input, output, session) {
  observeEvent(input$enter, {
    if (input$pass == "password") {
      appendTab(inputId = "hello", tab = tabPanel("tab2", value = "tab2_val", br(), h4("this is tab2")))
      appendTab(inputId = "hello", tab = tabPanel("tab3 with a lot of stuff in it",value = "tab3_val", br(), h4("this is tab3")))
    }
  })
}

shinyApp(ui, server)
Initial answered 14/2, 2020 at 15:37 Comment(2)
thanks for this great answer. I get it, using this method rather than the accepted answer would be a choice between security/convenience for hidingBeslobber
For future readers: Here is how to use this with a sidebarPanel instead of a navbarPage.Initial
I
3

How about this

library(shiny);library(shinyjs)
ui <- fluidPage(useShinyjs(),
                navbarPage("hello", id="hello",
                           tabPanel("home", br(), h3("this is home"),passwordInput("pass", "enter 'password' to see the tabs: "),actionButton("enter", "enter")),
                           tabPanel("tab2",uiOutput("tab2Content")),
                           tabPanel("tab3 with a lot of stuff in it", uiOutput("tab3Content"))))
server <- function(input, output, session) {
  output$tab2Content <- renderUI({
    req(input$pass == "password")
    tagList(
      br(), 
      h4("this is tab2")
    )
  })
  output$tab3Content <- renderUI({
    req(input$pass == "password")
    tagList(
      br(), 
      h4("this is tab3")
    )
  })}
shinyApp(ui, server)

hope this helps!

Interlace answered 4/1, 2018 at 15:0 Comment(1)
thanks, indeed it works. However passing all the tabs into the server script is kind of breaking a little the structure and understandability of the app, that's the reason of my last remark in my question.Beslobber
R
0

I solved this problem creating a new function (above the UI definition) to include the style="display:none; on the right li tags:

tabsethidepanels<-function(tag, indexes = NULL) {
  if (class(tag) == "shiny.tag" && tag$name == "div" && tag$attribs$class == "tabbable") {
    if (is.null(indexes)) indexes<-seq_along(tag$children[[1]]$children[[1]])
    for (i in indexes) tag$children[[1]]$children[[1]][[i]]$attribs<-c(tag$children[[1]]$children[[1]][[i]]$attribs, list(style="display:none;"))
    tag
  } else stop("tag must be a tabsetPanel!", call. = F)
}

And define the tabset accordingly:

tabsethidepanels(
  tabsetPanel(
    id = "mytab",
    selected = "tab7-not2hide",
    tabPanel("tab1"),
    tabPanel("tab2"),
    tabPanel("tab3"),
    tabPanel("tab4"),
    tabPanel("tab5"),
    tabPanel("tab6"),
    tabPanel("tab7-not2hide")
  ),
  indexes = 1:6
)

The "indexes" field are usefull to select only the right tabs to hide:

<div class="tabbable">
  <ul class="nav nav-tabs shiny-tab-input" id="mytab" data-tabsetid="2818">
    <li style="display:none;">
      <a href="#tab-2818-1" data-toggle="tab" data-value="tab1">tab1</a>
    </li>
    <li style="display:none;">
      <a href="#tab-2818-2" data-toggle="tab" data-value="tab2">tab2</a>
    </li>
    <li style="display:none;">
      <a href="#tab-2818-3" data-toggle="tab" data-value="tab3">tab3</a>
    </li>
    <li style="display:none;">
      <a href="#tab-2818-4" data-toggle="tab" data-value="tab4">tab4</a>
    </li>
    <li style="display:none;">
      <a href="#tab-2818-5" data-toggle="tab" data-value="tab5">tab5</a>
    </li>
    <li style="display:none;">
      <a href="#tab-2818-6" data-toggle="tab" data-value="tab6">tab6</a>
    </li>
    <li class="active">
      <a href="#tab-2818-7" data-toggle="tab" data-value="tab7-not2hide">tab7-not2hide</a>
    </li>
  </ul>
  <div class="tab-content" data-tabsetid="2818">
    <div class="tab-pane" data-value="tab1" id="tab-2818-1"></div>
    <div class="tab-pane" data-value="tab2" id="tab-2818-2"></div>
    <div class="tab-pane" data-value="tab3" id="tab-2818-3"></div>
    <div class="tab-pane" data-value="tab4" id="tab-2818-4"></div>
    <div class="tab-pane" data-value="tab5" id="tab-2818-5"></div>
    <div class="tab-pane" data-value="tab6" id="tab-2818-6"></div>
    <div class="tab-pane active" data-value="tab7-not2hide" id="tab-2818-7"></div>
  </div>
</div>

Remember to select a tab not to hide as active selected = "tab7-not2hide".

If you need to hide all tabs I sugest to create a "blank" tabPanel, select his one as active and hide all tabs with indexes = NULL.

Raseta answered 14/2, 2020 at 14:36 Comment(2)
Please see my comment under the question. You might be better off using appendTab.Initial
@Initial you are correct. I use this code only for "cosmetic/styling" problems when I don´t want the user to view the tabs. If the programer has "security" reasons I agree that appendTab or uiOutput/renderUI are the better answers. Thanks.Raseta

© 2022 - 2024 — McMap. All rights reserved.