Skip to content

Commit

Permalink
initial commit: two versions of multiple scrollspy solutions and upda…
Browse files Browse the repository at this point in the history
…te all inputs solution
  • Loading branch information
daattali committed Jul 23, 2015
0 parents commit c0c59cd
Show file tree
Hide file tree
Showing 16 changed files with 430 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.Rproj.user
.Rhistory
.RData
13 changes: 13 additions & 0 deletions advanced-shiny.Rproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Version: 1.0

RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default

EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8

RnwWeave: Sweave
LaTeX: pdfLaTeX
10 changes: 10 additions & 0 deletions multiple-scrollspy-advanced/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Multiple scrollspy - advanced

*Dean Attali, July 2015*

The Bootstrap scrollspy plugin does not support multiple scrollspy objects per page.
This Shiny app demonstrates how to support scrollspy on multiple tabs by allowing each tab to have its own independent scrollspy control and using JavaScript to ensure only the scrollspy on the current tab is activated.

This approach is very flexible and great to use when you want to define custom scrollspy controls on each tab. Initially I thought it'd be easy to simply "destory" a scrollspy control and initialize a different one, but it seems like destorying scrollspy objects is not possible in Bootstrap 3 (although it sounds like in Bootstrap 4 it will be supported), so a more clever trick has to be used to be able to "reset" the active scrollspy control.

This method also supports having a different offset for the scrollspy on each tab by adding a `data-offset` attribute to the scrollspy HTML tag. This method also uses the jQuery scrollTo plugin to animate the scrolling for a better UX.
103 changes: 103 additions & 0 deletions multiple-scrollspy-advanced/app.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
library(shiny)
library(shinyjs)

ui <- navbarPage(
"Bootstrap scrollspy on multiple tabs",
id = "navbar",
header = div(
useShinyjs(),
extendShinyjs("www/app-shinyjs.js"),
includeCSS("www/app.css"),
includeScript("www/app.js"),
includeScript("https://cdnjs.cloudflare.com/ajax/libs/jquery-scrollTo/1.4.3/jquery.scrollTo.min.js")
),

# tab 1 contains 4 sections and a scrollspy on the left with text
tabPanel(
"tab1",
div(id = "tab1-content",
fluidRow(
column(
4,
div(
id = "tab1-scrollspy",
class = "potential-scrollspy",
tags$ul(
class = "nav nav-pills nav-stacked",
tags$li(tags$a(href = "#section1-1", "Section 1-1")),
tags$li(tags$a(href = "#section1-2", "Section 1-2")),
tags$li(tags$a(href = "#section1-3", "Section 1-3")),
tags$li(tags$a(href = "#section1-4", "Section 1-4"))
)
)
),
column(
8,
div(id = "section1-1",
class = "scrollspy-section",
p('Section 1-1')
),
div(id = "section1-2",
class = "scrollspy-section",
p('Section 1-2')
),
div(id = "section1-3",
class = "scrollspy-section",
p('Section 1-3')
),
div(id = "section1-4",
class = "scrollspy-section",
p('Section 1-4')
)
)
)
)
),

# tab 2 contains 3 sections and a scrollspy on the right without text
tabPanel(
"tab2",
div(id = "tab2-content",
fluidRow(
column(
8,
div(id = "section2-1",
class = "scrollspy-section",
p('Section 2-1')
),
div(id = "section2-2",
class = "scrollspy-section",
p('Section 2-2')
),
div(id = "section2-3",
class = "scrollspy-section",
p('Section 2-3')
)
),
column(
4,
div(
id = "tab2-scrollspy",
class = "potential-scrollspy",
`data-offset` = 50,
tags$ul(
class = "nav nav-pills nav-stacked",
tags$li(tags$a(href = "#section2-1")),
tags$li(tags$a(href = "#section2-2")),
tags$li(tags$a(href = "#section2-3"))
)
)
)
)
)
)
)

server <- function(input, output, session) {
# when changing tabs, update the scrollspy control
observeEvent(input$navbar, {
js$updateScrollspy(input$navbar)
})
}

shinyApp(ui = ui, server = server)
31 changes: 31 additions & 0 deletions multiple-scrollspy-advanced/www/app-shinyjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// update the active scrollspy control and refresh so that it will function
shinyjs.updateScrollspy = function(tab) {
// make all scrollspy controls inactive
$('active-scrollspy').removeClass('active-scrollspy');
// get the content in the current tab
var $tabContent = $('#' + tab + '-content');
if ($tabContent.length) {
// find the scrollspy control in the current tab
var $scrollspy = $tabContent.find('.potential-scrollspy');
if ($scrollspy.length) {
// mark the scrollspy in the current tab as active
$scrollspy.addClass('active-scrollspy');
// figure out the offset for this scrollspy
var offset = 0;
if ($scrollspy.data('offset')) {
offset = $scrollspy.data('offset');
}
// update the scrollspy object
$('body').data('bs.scrollspy').options.offset = offset;
$('body').scrollspy('refresh');
// unbind click events and re-bind clicks to animate scrolling
$scrollspy.unbind('click.scrollto');
$scrollspy.bind('click.scrollto', 'ul li a', function(event) {
event.preventDefault();
$.scrollTo(event.target.hash, 500, {
offset: -offset
});
});
}
}
}
37 changes: 37 additions & 0 deletions multiple-scrollspy-advanced/www/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#tab1-content .scrollspy-section {
height: 500px;
border: 1px solid #DDD;
}
#tab1-scrollspy {
position: fixed;
}
#tab1-scrollspy li > a {
background-color: #FFF;
}
#tab1-scrollspy li > a:hover {
background-color: #EEE;
}
#tab1-scrollspy li.active > a {
background-color: #AAA;
}
#tab2-content .scrollspy-section {
height: 900px;
border: 1px solid #DDD;
}
#tab2-scrollspy {
position: fixed;
top: 300px;
}
#tab2-scrollspy li > a {
padding: 0;
width: 15px;
height: 15px;
background-color: #DDD;
margin-bottom: 5px;
}
#tab2-scrollspy li > a:hover {
background-color: #AAA;
}
#tab2-scrollspy li.active > a {
background-color: #444;
}
4 changes: 4 additions & 0 deletions multiple-scrollspy-advanced/www/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// initialize common scrollspy control
$(function() {
$('body').scrollspy({ target: '.active-scrollspy' })
});
7 changes: 7 additions & 0 deletions multiple-scrollspy-basic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Multiple scrollspy - basic

*Dean Attali, July 2015*

The Bootstrap scrollspy plugin does not support multiple scrollspy objects per page. This Shiny app demonstrates how to support scrollspy on multiple tabs by having one common scrollspy control that gets updated via JavaScript whenever a tab is changed to reflect the contents of the new tab.

This approach is useful if you don't want to have to hardcode the scrollspy code since it will automatically generate the scrollspy control for each tab. However, the major disadvantage of this approach is that if you want different pages to have very different looking scrollspy controls, it will be hard to achieve with this method because only one common control is created.
73 changes: 73 additions & 0 deletions multiple-scrollspy-basic/app.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
library(shiny)
library(shinyjs)

ui <- navbarPage(
"Bootstrap scrollspy on multiple tabs",
id = "navbar",

header = div(
useShinyjs(),
extendShinyjs("www/app-shinyjs.js"),
includeCSS("www/app.css"),
includeScript("www/app.js"),

# create a common scrollspy
div(
id = "myscrollspy",
tags$ul(
class = "nav nav-pills nav-stacked"
)
)
),

# tab 1 has 4 sections
tabPanel(
"tab1",
div(id = "tab1-content",
div(id = "section1-1",
class = "scrollspy-section",
p('Section 1-1')
),
div(id = "section1-2",
class = "scrollspy-section",
p('Section 1-2')
),
div(id = "section1-3",
class = "scrollspy-section",
p('Section 1-3')
),
div(id = "section1-4",
class = "scrollspy-section",
p('Section 1-4')
)
)
),

# tab 2 has 3 sections
tabPanel(
"tab2",
div(id = "tab2-content",
div(id = "section2-1",
class = "scrollspy-section",
p('Section 2-1')
),
div(id = "section2-2",
class = "scrollspy-section",
p('Section 2-2')
),
div(id = "section2-3",
class = "scrollspy-section",
p('Section 2-3')
)
)
)
)

server <- function(input, output, session) {
# when changing tabs, update the scrollspy control
observeEvent(input$navbar, {
js$updateScrollspy(input$navbar)
})
}

shinyApp(ui = ui, server = server)
14 changes: 14 additions & 0 deletions multiple-scrollspy-basic/www/app-shinyjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// update scrollspy control with the contents of the current tab
// and refresh the control to make the changes take effect
shinyjs.updateScrollspy = function(tab) {
if (tab == 'tab1' || tab == 'tab2') {
var $tabContent = $('#' + tab + '-content');
var tabSections = $tabContent.find('.scrollspy-section');
var scrollspyHtml = '';
$.each(tabSections, function(idx, el) {
scrollspyHtml += '<li><a href=\"#' + el.id + '\"></a></li>';
});
$('#myscrollspy').children('ul').html(scrollspyHtml);
$('body').scrollspy('refresh');
}
}
26 changes: 26 additions & 0 deletions multiple-scrollspy-basic/www/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#tab1-content div {
height: 500px;
border: 1px solid #DDD;
}
#tab2-content div {
height: 900px;
border: 1px solid #DDD;
}
#myscrollspy {
position: fixed;
top: 300px;
left: 100px;
}
#myscrollspy li > a {
padding: 0;
width: 15px;
height: 15px;
background-color: #DDD;
margin-bottom: 5px;
}
#myscrollspy li > a:hover {
background-color: #AAA;
}
#myscrollspy li.active > a {
background-color: #444;
}
4 changes: 4 additions & 0 deletions multiple-scrollspy-basic/www/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// initialize common scrollspy control
$(function() {
$('body').scrollspy({ target: '#myscrollspy' })
});
10 changes: 10 additions & 0 deletions update-input/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Update any Shiny input without knowing input type

*Dean Attali, July 2015*

Shiny allows you to update an input element only if you know the type of input it is. Furthermore, Shiny only allows you to update input elements one by one. This Shiny app demonstrates a solution to these two problems.

First, the common Shiny input builder functions are wrapped by a custom function that mimics the Shiny function but also adds the input type information to the input's HTML. Then whenever you want to update an input only based on its ID, we can use JavaScript to determine the type of input, report it back to Shiny, and then call the correct update function. This is done with a call to `updateShinyInput(session, "inputid", "new value")`. You can also use `updateShinyInputs(list(...))` to update many inputs together in one call.

Note that this solution assumes you're using the proper shiny functions to create inputs. More specifically, this won't work if creating input elements manually with HTML (for example, you should always call "textInput" rather than create a
<input type='button'> HTML).
27 changes: 27 additions & 0 deletions update-input/app.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
library(shiny)
library(shinyjs)

source("update-input.R")

ui <- fluidPage(
useShinyjs(),
extendShinyjs("www/app-shinyjs.js"),

textInput("text", "Text input", "some text"),
selectInput("select", "Select input", LETTERS),
numericInput("numeric", "Numeric input", 5),
actionButton("btn", "Reset text to 'new value', select to 'G', and number to '9'", class = "btn-primary")
)

server <- function(input, output, session) {
observeEvent(input$btn, {
newValues <- list("text" = "new value",
"select" = "G",
"numeric" = 9)
updateShinyInputs(session, newValues)
# OR one by one
# updateShinyInput(session, "text", "new value")
})
}

shinyApp(ui = ui, server = server)
Loading

0 comments on commit c0c59cd

Please sign in to comment.