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

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

Old version:

In this tutorials series, 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.

1. Next and previous button for a TabBox

Our goal is to build next and previous buttons to navigate between tabs and to follow the apps flow. Thus, we can avoid repetitive scrollings for the users of the app.

Below is an exemple of what we want to achieve:

exemplearrowshiny

a.What do I need to complete this ?

The app is relying on Shiny and the awesome Shiny Dashboard package. You will just need these two packages to complete the app.

b. It all started with a Shiny Dashboard and a TabBox

Now, we need to create a new Shiny Apps and the skeleton of a Shiny Dashboard with its ui.R and server.R file.

ui.R: the header and sidebar are disabled because we will only need the body later on

library(shiny)
library(shinydashboard)
dashboardPage(
 dashboardHeader(disable = T),
 dashboardSidebar(disable = T),
 dashboardBody()
)

 

server.R: just notice session was added as an input  to the server function

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

 

c. And we are adding the tabBox and the buttons:

Now let’s add the next and previous button and the tabBox to the UI and the server.

For the tabBox, you just need to change the dashboardBody from the last part to:

dashboardBody(box(width=12,
 tabBox(width=12,id="tabBox_next_previous",
   tabPanel("Tab1",p("This is tab 1")),
   tabPanel("Tab2",p("This is tab 2")),
   tabPanel("Tab3",p("This is tab 3")),
   tabPanel("Tab4",p("This is tab 4"))
   ),
   uiOutput("Next_Previous")
 )
)

The uiOutput will contain the Next and Previous button (we will want them to be ractive, you’ll see why). The id is needed to update the tabBox when clicking on a next or previous button and to get its current state.

The button will be created writing the following code in the server.R file:

Previous_Button=tags$div(actionButton("Prev_Tab",HTML('
<div class="col-sm-4"><i class="fa fa-angle-double-left fa-2x"></i></div>
')))
Next_Button=div(actionButton("Next_Tab",HTML('
<div class="col-sm-4"><i class="fa fa-angle-double-right fa-2x"></i></div>
')))

 

We are creating two action buttons with an arrow icon and no label. They now can be incorporated in the server function:

shinyServer(function(input, output,session){
output$Next_Previous=renderUI({
 div(column(1,offset=1,Previous_Button),column(1,offset=8,Next_Button))
 })
})

 

d. I am clicking in the buttons and nothing happened!

Yes, we need to link the button and the tabBox and this is going to be the toughest part!
We need several things:

  • To know which tabs we are on, to be able to move to the next and the previous tab.
  • To send a message to the tabBox when the next or previous button has been clicked for the tabBox to update to the selected tab.
  • To show the next and previous tab only when they correspond to a possible action. For instance no next tab should be displayed when we are on the last tab. That is why the buttons UI needed to be reactive.

e. Getting the list of tabs in the tabBox.

For this we will use a small javascript script:

 $('body').mouseover(function() {
 list_tabs=[];
 $('#tabBox_next_previous li a').each(function(){
 list_tabs.push($(this).html())
 });
 Shiny.onInputChange('List_of_tab', list_tabs);})

 

The script is saying that whenever the mouse is over the body, the shiny input List_of_tab should be assigned the list of the the tab names from the tabBox.

Comments:
The tabBox have the following structure:
   #TabBoxID > li > a
Hence at the beginning of the function we are creating empty list and appending to it the text in each tab.

The last line is used to set the value as a shiny input.
The  $(‘body’).mouseover is not optimal, but using the .load or .ready jquery function would result in an error (Shiny.onInputChange is not a function)

Now, you just have to add the script in your tabBox:

 tabBox(width=12,id="tabBox_next_previous",
   tabPanel("Tab1",p("This is tab 1")),
   tabPanel("Tab2",p("This is tab 2")),
   tabPanel("Tab3",p("This is tab 3")),
   tabPanel("Tab4",p("This is tab 4")),
   tags$script("
     $('body').mouseover(function() {
     list_tabs=[];
     $('#tabBox_next_previous li a').each(function(){
    list_tabs.push($(this).html())
    });
    Shiny.onInputChange('List_of_tab', list_tabs);})
   ")
  )

f. Hiding and showing the next and previous buttons when necessary.

We want to hide:

  • The next button when the current tab is the last tab
  • The previous button when the current tab is the first one
 output$Next_Previous=renderUI({
   tab_list=input$List_of_tab[-length(input$List_of_tab)]
   nb_tab=length(tab_list)
   if (which(tab_list==input$tabBox_next_previous)==nb_tab)
     column(1,offset=1,Previous_Button)
   else if (which(tab_list==input$tabBox_next_previous)==1)
     column(1,offset = 10,Next_Button)
   else
     div(column(1,offset=1,Previous_Button),column(1,offset=8,Next_Button))

 

Line by line from the renderUI:
– We are setting a variable to be the list of the tabs (minus the last one, because this is not a current tab but an empty one due to the HTML structure of the tabBox).
– We are storing the number of tab.
– If the indice of the current tab is the number of tab, this is the last tab and hence the next button should be hidden.
– If the indice is one, this is the first tab, so only the previous button should be shown.
-Otherwise, all the buttons should be shown.

g.Updating the tabBox when a button is clicked

For this, we will use the combination of observeEvent and updateTabsetPanel in the shinyServer function.

 observeEvent(input$Prev_Tab,
 {
  tab_list=input$List_of_tab
  current_tab=which(tab_list==input$tabBox_next_previous)
  updateTabsetPanel(session,"tabBox_next_previous",selected=tab_list[current_tab-1])
 })

 observeEvent(input$Next_Tab,
 {
  tab_list=input$List_of_tab
  current_tab=which(tab_list==input$tabBox_next_previous)
  updateTabsetPanel(session,"tabBox_next_previous",selected=tab_list[current_tab+1])
 })

 

In both the observe event, we are doing similar things:

  1. Observe id to see if the Previous or Next button have been clicked
  2. Saving the the list of the names of the tabs  and checking the index of the active tab in this list.
  3. Updating the tabBox to the next or previous tab (selected=tab_list[current_tab+-1])

h. That is it !

Now the tabBox and the next and previous button should be functional!

You can find the code: HERE

 

Advertisements