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!

 

 

Advertisements

Adding a HTML editor to Shiny

Hello,

I recently had to implement a form in shiny that required an HTML editor inside. I quickly ran into a handy javascript library called tinyMCE. As described on their website,

TinyMCE enables you to convert HTML textarea fields or other HTML elements to editor instances.

tinyMCE is the same HTML editor that you can find on popular website, such as WordPress, Evernote, LinkedIn, etc.

I looked into the documentation and examples, then implemented it in the shiny app. Here is how:

First of all, the UI is as follows (explanation below).

library(shiny)
library(shinyjs)


shinyUI(
 fluidPage(
 tags$head(
 useShinyjs(),
 tags$script(src='https://cdn.tinymce.com/4/tinymce.min.js')
 ),

 fluidRow(
  titlePanel("tinyMCE Shiny"),
  br(),
  column(width=6,
   tags$form(method="post",
    tags$textarea(id="textHolder")
   ),
   br(),
   actionButton("fetch", "Get Results!", icon=icon("lightbulb-o"),class="btn-primary",
  onclick = "Shiny.onInputChange('typedText', tinyMCE.get('textHolder').getContent());"),
   tags$script("tinymce.init({
              selector:'#textHolder',
              theme: 'modern',
              height: 500,
              plugins: ['advlist autolink link image lists charmap preview hr','wordcount',],
              menubar: true,
              toolbar: 'undo redo | bold italic | bullist | link',
              });")
  ),
  column(width=6,
   tags$style(HTML('pre {height:240px;}')),
   tags$label(`for`="rawText", "Raw String"),
   hr(),
   tags$pre(textOutput("rawText")),
   br(),
   tags$label(`for`="htmlText", "HTML Version"),
   hr(),
   tags$pre(htmlOutput("htmlText"))
  )
 )
 )
)

Sorry for the horrible formating, it seems that WordPress (and tinyMCE!) doesn’t like copy/paste! I read about a plugin that is supposed to solve that issue, I should look into it.

So it’s a pretty simple app. In the head, I load the tinyMCE script from the direct URL for reproducibility but it’s better practice to download the script and put it in your www subfolder in your shiny app, in order to avoid version issue. In the first column, I included a textarea, then the tinyMCE JS function that “activate” the HTML editor. What happens behind the scene is that the textarea is hidden, and the HTMl editor is “iframed” into the app at the same spot.

I added a button that fetches the HTML string from the editor using  “Shiny.onInputChange(‘typedText’, tinyMCE.get(‘textHolder’).getContent());”.  The results can after be accessible through input$typedText, and are then displayed in the 2 boxes on the other column, one is the raw text string, the other is the HTML version of the code. And magic, they match with the editor (it’s a beautiful world, right?).

Another good practice is to put your JS code in a separate file (the onclick event and the tinyMCE “activator”), and then load it at the end of the page with “includeScript()” or “tags$script”.

The server part is very simple, it is just used to fill the boxes on the second column:

shinyServer(function(input, output, session) {

output$htmlText <- renderUI({
req(input$typedText)
HTML(enc2utf8(input$typedText))
})

output$rawText <- renderText({
req(input$typedText)
enc2utf8(input$typedText)
})
})

And that’s it! Pretty straightforward.

You may also want to include the HTML editor as part of a form that the user would fill (in a modal, usually). Modals are not yet included into Shiny, but they should be in version 0.14 (source: RStudio).

I wrote below a quick app to do so. When you click the button, a modal will pop up with the HTML Editor, and when you close it, the HTML string will be typed in the box below the button.

library(shiny)
library(shinyjs)

ui <- shinyUI(
fluidPage(
tags$head(
useShinyjs(),
tags$script(src=’https://cdn.tinymce.com/4/tinymce.min.js&#8217;)
),

# Application title
fluidRow(
titlePanel(“tinyMCE Modal Example”),
br(),
actionButton(“modal”, “Modal Example”, icon=icon(“paper-plane-o”), class=”btn-                               success”, style=”margin-left:15px;”,
`data-toggle`=”modal”, `data-target`=”#modalExample”),
br(),
br(),
tags$pre(htmlOutput(“modalText”)),
### Modal ###
tags$div(class=”modal fade”, id=”modalExample”, tabindex=”-1″, role=”dialog”,              `aria-labelledby`=”modalExample”, `aria-hidden`=”true”,
tags$div(class=”modal-dialog”, role=”document”,
tags$div(class=”modal-content”,
tags$div(class=”modal-header”,
tags$button(type=”button”, class=”close”, `data-dismiss`=”modal”, `aria-                     label`=”Close”,
tags$span(`aria-hidden`=”true”, ‘x’)),
tags$h4(class=”modal-title”, ‘HTML Editor in a modal’)
),
tags$div(class=”modal-body”,
tags$form(method=”post”,
tags$textarea(id=”modalEditor”)
),
tags$script(“tinymce.init({
selector:’#modalEditor’,
theme: ‘modern’,
height: 200,
});”)
),
tags$div(class=”modal-footer”,
tags$button(type=”button”, class=”btn btn-primary”, `data-dismiss`=”modal”,
onclick=”Shiny.onInputChange(‘modalTextArea’,                                                                      tinyMCE.get(‘modalEditor’).getContent());”,
‘Close’))
)
)
)
### End Modal ###
)
)
)

server <- function(input, output, session) {
output$modalText <- renderUI({
req(input$modalTextArea)
HTML(enc2utf8(input$modalTextArea))
})
}

shinyApp(server=server, ui=ui)

Once again, it is good practice to put your JS in a separate file, and then load it.

All the options to customize your tinyMCE editor can be found in the official documentation.

Some thoughts: this would be a good widget to make. I tried to look into it, but I need to learn more about the htmlwidget package to be able to do so! Probably in the future!

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

Rock On!

The Dub World

Hello,

There is a little emotion as I write those lines: this is the first article I write on The Dub World! This blog is mainly going to be dedicated to my work and my passion for R, Shiny and other new fun stuff I recently started learning, like machine learning and neural networks.

I will mainly talk about little tricks and technique I have learnt using this incredible tool that is Shiny. If you’ve never heard of shiny, it is a web application framework written in R, developed mainly by Joe Cheng. You can find more informations about Shiny here.

A little presentation. My name is Richard Dubos and am a French citizen who moved to the USA in August 2012 to finish my master of applied statistics at Oakland University, MI. Once my master finished, I struggled a little bit before finding a job, most companies not willing to hire an alien without experience on a temporary work authorization. But managed to work my way through and, at the time I write those lines, I work in the R&D department of an Insurance company based in Lansing, MI, (almost) home of the Spartans!

I am probably not the best one to say that, but I would say that I’m quite a fun guy. Kid at mind, full of energy, I am always ready to go on an adventure or traveling to new places. I enjoy bike riding, swimming, team sports, like soccer (i would admit that I am pretty terrible at it, but enjoy the workout!), and other outside activities. I own three “pull-up” polaroid cameras from the 70’s (two 360’s and one 450 – I know, they look the same!). I enjoy talking about ideas that “make the world”. It’s a pretty vague term that regroup science, politics, human interactions, etc… To make it simple, I like to talk!

That’s pretty much it for myself, if you have any questions, do not hesitate to ask, i don’t bite!

PS: I’m also quite the dancer!

 

Ricky PT