Creating Tabs Dynamically with Shiny

Hello,

To write this article I was inspired by one of the many articles about creating dynamically tabs using angularjs, like this one. Shiny framework is a lot similar to angular, with the reactivity. I was wondering how easy it would be to do it using Shiny. Pretty easily as it turns out!

I will use renderUI to do so, destroying and rendering all the tabs every time.

The UI is as follows:

library(shiny)

shinyUI(fluidPage(
tags$head(
tags$link(rel=”stylesheet”, href=”https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css”)
),
titlePanel(“Dynamic tabs with Shiny”),
br(),
fluidRow(style=”margin:10px;”,
uiOutput(“tabsonthefly”)
)
)
)

I’m loading the font-awesome library to be able to use some icons. This is purely esthethic and not needed.

The server part is:

library(shiny)
library(stringi)

shinyServer(function(input, output) {

# original tab structure
tab <- reactiveValues(structure = data.frame(ID=paste0(“tab”, sprintf(“%07i”, as.integer(stats::runif(3, 1, 1e+06)))),
Name = paste(‘Tab’, 1:3),
Content = c(stri_rand_lipsum(1),stri_rand_lipsum(1),stri_rand_lipsum(1)),
Active = c(”,’active’,”))
)

output$tabsonthefly &lt;- renderUI({
HTML(
enc2utf8(
paste0(‘&lt;div class=&quot;tabbable&quot;&gt;
&lt;ul class=&quot;nav nav-pills nav-stacked col-md-2&quot;&gt;’,
paste0(
sprintf(‘&lt;li class=&quot;%s&quot;&gt;&lt;a href=&quot;#%s&quot; data-toggle=&quot;tab&quot;&gt;%s
&lt;i class=&quot;fa fa-times-circle-o&quot; style = &quot;float:right;&quot; onclick = &quot;Shiny.onInputChange(\’closeTab\’, \’%s\’);&quot;&gt;&lt;/i&gt;
&lt;/a&gt;&lt;/li&gt;’,
tab$structure$Active,tab$structure$ID,tab$structure$Name,tab$structure$ID),
collapse=”),
‘&lt;button id =&quot;addTab&quot; type=&quot;button&quot; class=&quot;btn btn-default action-button&quot; style=&quot;float:right; margin-top:10px;&quot;&gt;&lt;i class=&quot;fa fa-plus-circle&quot;&gt;&lt;/i&gt;&lt;/button&gt;
&lt;/ul&gt;
&lt;div class=&quot;tab-content col-md-10&quot;&gt;’,
paste0(
sprintf(‘&lt;div class=&quot;tab-pane %s&quot; id=&quot;%s&quot;&gt;%s&lt;/div&gt;’,
tab$structure$Active,tab$structure$ID,tab$structure$Content),
collapse=”),
‘&lt;/div&gt;
&lt;/div&gt;’
)
)
)
})

observeEvent(input$addTab,{
# make sure it’s not factors. Weirdly it keeps changing back to factor…
tab$structure <- data.frame(lapply(tab$structure, as.character), stringsAsFactors=FALSE)

# get the number of open tabs
numTab <- nrow(tab$structure)

# make the new tab active
tab$structure$Active <- rep(”, numTab)

# creating ID, name, etc based on already existing tabs
tabID <- paste0(“tab”, sprintf(“%07i”, as.integer(stats::runif(1, 1, 1e+06))))
tabName <- paste(‘Tab’, numTab+1)
tabActive <- ‘active’
tabContent <- stri_rand_lipsum(1)

# updating the reactive object containing the tabs
tab$structure[numTab+1,] <- c(tabID, tabName, tabContent, tabActive)
})

observeEvent(input$closeTab, {
tab$structure <- data.frame(lapply(tab$structure, as.character), stringsAsFactors=FALSE)

# using Shiny.onInputChange; we grab the ID of the tab we want to delete
# removing the tab that has been deleting
tab$structure <- tab$structure[-which(tab$structure$ID == input$closeTab),]

# updating the tabs name and IDs
numTab <- nrow(tab$structure)
if (numTab != 0)
{tab$structure$Name <- paste(‘Tab’, 1:numTab)}
})

})

here too, the stringi library is only used to generate the Lorem Ipsum content of the tabs, and is in no way relevant to the code.

The idea is that you have a reactiveValue containing a dataframe that holds your tab structure. Every time you click on the “+” button to add a tab, you create a new row in the dataframe contained in the reactiveValue, which triggers the renderUI, and generate a new tab.

Same, when you delete a tab, you just simply remove that row from the dataframe.

Here the names of the tabs are created following the order 1,2,3 etc, so I had to add some logic to take care of that. But a lot of that logic can be removed by adding a textInput asking for the name of the Tab, for example.

If your tab content is complex, you can call to a modal when the “+” button is clicked, asking the user for options and other cool stuff inside. Then when you close the modal, yourender the new tab (I should try to take a crack at that).

The main idea of course is to save that dataframe somewhere (SQL server for example) when you close the session or each time you create/delete a new tab, so you can keep the structure for other users, or when you come back.

If  you have any suggestions/questions, don’t hesitate!

Rock On!