The updated version of this post is available on my new blog:

2017-04-22 15_09_07-Home - Enhance Data Science.png

Enhance Data Science

Old version:

In this tutorials sequence, we are going to see three tricks to do the following in a Shiny app:

  1. Add Next and Previous buttons to navigate in a tabBox
  2. Build a non completely collapsible sidebar to keep the icon visible on collapse
  3. Add button on a datatable output to delete/modify/ do an action on a given row.

Today, we are going to see ho to build a semi-collapsible sidebar.

2.Semi-collapsible sidebar

We are trying to build a semi-collapsible side which only show the icon instead of completely hiding the sidebar when clicking on the collapse button. You can see an animated example below:

ExempleArrowShiny.gif

a. What you need to build this:

As in the previous tutorial you only need R with its package Shiny and Shiny Dashboard. We’ll use R, some javascript and some CSS to manage to do this.

b. It all starts with a Dashboard

We just need a classic Shiny app, with an ui.R file containing a dashboard and a server.R file.

  1. The sidebar menu will be put in the server since we want to be able to hide it when clicking on the collapse button (and we want to be able to remove text from it when hiding it). In addition to this, putting it in the server can be useful if you want to change the sidebar dynamically in your server.
  2. We are linking the application with a CSS file to change the way the sidebar appearance change on collapse.

ui.R file

library(shiny)
library(shinydashboard)

dashboardPage(
 dashboardHeader(title="Semi-collapsible Sidebar"),
###point 1.
 dashboardSidebar(sidebarMenuOutput("Semi_collapsible_sidebar")),
###point 2.
 dashboardBody(tags$head(tags$link(rel = "stylesheet",
type = "text/css", href = "style.css")))
)

server.R file

library(shiny)
library(shinydashboard)
shinyServer(function(input,output){

 output$Semi_collapsible_sidebar=renderMenu({

 sidebarMenu(
    menuItem("Dashboard", tabName = "dashboard", icon = icon("dashboard")),
    menuItem("Widgets", icon = icon("th"), tabName = "widgets",
      badgeLabel = "new",
      badgeColor = "green"),
    menuItem("Charts", icon = icon("bar-chart-o"),
    menuSubItem("Sub-item 1", tabName = "subitem1"),
    menuSubItem("Sub-item 2", tabName = "subitem2")
 ))

})

c. Creating the CSS file:

The semi-collapsed bar needs:

  • To show only the icons from the uncollapsed sidebar.
  • To be narrower to free space.

When performing the collapse action, Shiny is translating the sidebar by its width in piwel to the left (which hides it).
To achieve the semi-collapsed sidebar, the new CSS will overwrite the previous translation to no translation and change the sidebar width to 40px (this is the icon width).

To do this, create a www/ folder in your working directory and put the following style.css file in it.

.sidebar-collapse .left-side, .sidebar-collapse .main-sidebar{ 
 transform: translate(0,0);
 width: 40px;
}

Start your app again, the sidebar is semi-collapsing ! However the text is still there and the new label is going over the icon. We need to hide all of these on collapse !

d. Hiding the text and badge on collapse:

To hide the text on collapse, Shiny needs to know that the sidebar is collapsed. To do so, we will add a Shiny Input that will respond to a click on the collapse button.

tags$script("$(document).on('click', '.sidebar-toggle',
function () {
Shiny.onInputChange('SideBar_col_react', Math.random())
});")

‘.sidebar-toggle’ is the button class.Hence,if the button is clicked, a random number is generated and assigned to input$SideBar_col_react.
The script above need to be added in the ui.R after the DashboardSidebar().

vals=reactiveValues()
vals$collapsed=FALSE
observeEvent(input$SideBar_col_react,
{ vals$collapsed=!vals$collapsed }
) 

Now, we need to observe this input and to decide whether or not the sidebar is collapsed. So we will create a reactive value with a collapsed attribute which will be True is the sidebar is collapsed, and False otherwise. To do so, you can add the previous script before output$Semi_collapsible_sidebar.

Now that the server know whether or not the sidebar is collapsed. The sidebar will change according to this value using this code:

output$Semi_collapsible_sidebar=renderMenu({
 if (vals$collapsed)
   sidebarMenu(
    menuItem(NULL, tabName = "dashboard", icon = icon("dashboard")),
    menuItem(NULL, icon = icon("th"), tabName = "widgets"),
    menuItem(NULL, icon = icon("bar-chart-o"),
    menuSubItem(span(class="collapsed_text","Sub-item 1"), tabName = "subitem1"),
    menuSubItem(span(class="collapsed_text","Sub-item 2"), tabName = "subitem2")
 ))
 else
   sidebarMenu(
    menuItem("Dashboard", tabName = "dashboard", icon = icon("dashboard")),
    menuItem("Widgets", icon = icon("th"), tabName = "widgets", badgeLabel = "new",
    badgeColor = "green"),
    menuItem("Charts", icon = icon("bar-chart-o"),
    menuSubItem("Sub-item 1", tabName = "subitem1"),
    menuSubItem("Sub-item 2", tabName = "subitem2")
  ))
 })
})

Basically, if vals$collapsed is true, the labels will be set to NULL and the badge will be hidden. Otherwise, the sidebar will be showed as previously.

e. Why can’t I uncollapse the menuSubItem ?

To be frank, I don’t know (If anyone has a clue, please comment) . There is a way to be able to collapse it back using this .

tags$script("$(document).on('click', '.treeview.active', function () {
 $(this).removeClass('active');
 $(this).find( 'ul' ).removeClass('menu-open');
 $(this).find( 'ul' ).css('display', 'none');
});")),

When the menu-item is active, it is getting the class active. So, when clicking on such an element, the script removes all the attribute linked to the menu-item being open and hide it using the CSS display option.

f. Improving the aspect

Right now the collapse is not smooth and when hovering the text in the menu items, it is whitish (over white) which is not great. To improve it, we will just modify the CSS to this:

.sidebar-collapse .left-side, .sidebar-collapse .main-sidebar{ 
 transform: translate(0,0);
 width: 40px;
 transition: transform 1s, width 1s;
}

.collapsed_text { 
 color: black;
}

.main-sidebar, .left-side
{
 transition: transform 1s, width 1s;
}

The .collapsed_text is a custom class we created at step c.

g. Comments

I think all of this could also be done using js and css only.
However, handling the collapse on the server side allow us to do more advanced things like hiding/showing the sidebar programmatically, changing what the server display depending on the sidebar status and even showing different semi-collapsed/not-collapsed sidebar (Which can be useful from a UI/UX point of view).

You can find the code HERE.

Thanks for reading the tutorial,
Antoine

You can follow me on Twitter:

Advertisements