Skip to content

Commit

Permalink
Get insertTab() of a navbarMenu() targetted at a navtreePanel() working
Browse files Browse the repository at this point in the history
  • Loading branch information
cpsievert committed Mar 9, 2021
1 parent 9790b8f commit 6159259
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 24 deletions.
3 changes: 2 additions & 1 deletion R/bootstrap.R
Original file line number Diff line number Diff line change
Expand Up @@ -997,8 +997,9 @@ buildTabItem <- function(index, tabsetId, foundSelected, tabs = NULL,
return(list(liTag = textFilter(divTag), divTag = NULL))
}

# build the child tabset
# TODO: add a warning if depth goes >2 ?
if (isNavbarMenu(divTag)) {
# tabPanelMenu item: build the child tabset
tabset <- buildTabset(
!!!divTag$tabs, ulClass = "dropdown-menu",
textFilter = navbarMenuTextFilter,
Expand Down
10 changes: 5 additions & 5 deletions R/navtreePanel.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
navtreePanel <- function(..., id = NULL,
selected = NULL,
fluid = TRUE,
# Also allow for string to determine padding in a flex layout?
widths = c(3, 9)) {
# TODO: how to incorporate this into a sidebar layout?

Expand All @@ -12,9 +13,6 @@ navtreePanel <- function(..., id = NULL,

row <- if (fluid) fluidRow else fixedRow

# TODO:
# 1. consider using flexbox instead for layout
# 2. how to integrate with a sidebar?
row(
class = "navtree-container",
column(widths[[1]], tabset$navList),
Expand Down Expand Up @@ -70,7 +68,6 @@ buildTreeItem <- function(index, tabsetId, foundSelected, tabs = NULL, divTag =
subMenuPadding <- if (depth > 0) css(padding_left = paste0(depth * 1.25, "rem"))

if (isTabPanelMenu(divTag)) {
# TODO: allow the collapse icon to be configured?
icon <- getIcon(iconClass = divTag$iconClass)
if (!is.null(icon)) {
warning("Configurable icons are not yet supported in navtreePanel().")
Expand Down Expand Up @@ -135,9 +132,12 @@ navtreeCssDependency <- function(theme) {
name <- "navtreePanel"
version <- packageVersion("shiny")
if (!is_bs_theme(theme)) {
# TODO: Should we allow navtreePanel() to be statically rendered?
# Can/should we move away from href="shared/*"?
htmlDependency(
name = name, version = version,
src = c(href = "shared/navtree"),
src = c(href = "shared/navtree", file = "www/shared/navtree"),
package = "shiny",
stylesheet = "navtree.css",
script = "navtree.js"
)
Expand Down
6 changes: 3 additions & 3 deletions inst/www/shared/navtree/navtree.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// nav-navtree is built on a combination of Bootstrap's tab &
// collapse components, but the tab component isn't smart enough to
// know about the deactive when are activated. Note that this logic is
// very similar to shinydashboard's deactivateOtherTabs() (in tab.js)
// know about the deactive when are activated. Note that this logic also
// exists in input_binding_tabinput.js and is repeated here in case this
// component wants to be statically rendered
$(document).on("shown.bs.tab", ".nav-navtree", function(e) {
var tgt = $(e.target);
var nav = tgt.parents(".nav-navtree");
tgt.tab("show");
nav.find("li").not(tgt).removeClass("active"); // BS3
nav.find("li > a").not(tgt).removeClass("active"); // BS4
});
13 changes: 3 additions & 10 deletions inst/www/shared/navtree/navtree.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,14 @@ $link-hover-decoration: underline !default;
$sidebar-collapse-icon-color: rgba($body-color, 0.5) !default;
$sidebar-collapse-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path fill='none' stroke='#{$sidebar-collapse-icon-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/></svg>") !default;

// TODO: make this more configurable from the R side?
$navtree-anchor-color: rgba($body-color, .65) !default;
$navtree-active-bg: rgba($component-active-bg, .3) !default;
$navtree-active-color: rgba($body-color, .85) !default;

.nav-navtree {

// TODO: integrate layout with a larger sidebar layout container?
@include media-breakpoint-up(lg) {
position: sticky;
top: 5rem;
// Override collapse behaviors
display: block !important;
height: subtract(100vh, 7rem);
overflow-y: auto;
}
// Override collapse behaviors
display: block !important;

li {
// Handle both li.active (BS3) and li > a.active (BS4)
Expand Down
40 changes: 38 additions & 2 deletions inst/www/shared/shiny.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion inst/www/shared/shiny.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion inst/www/shared/shiny.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion inst/www/shared/shiny.min.js.map

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions srcjs/input_binding_tabinput.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ $.extend(bootstrapTabInputBinding, {
anchors.each(function() {
if (self._getTabName($(this)) === value) {
$(this).tab('show');
$(this).parents(".collapse").collapse('show');
success = true;
return false; // Break out of each()
}
Expand All @@ -44,7 +45,9 @@ $.extend(bootstrapTabInputBinding, {
$(el).trigger("change");
},
subscribe: function(el, callback) {
var deactivateOtherTabs = this._deactivateOtherTabs;
$(el).on('change shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding', function(event) {
deactivateOtherTabs(event);
callback();
});
},
Expand All @@ -53,6 +56,16 @@ $.extend(bootstrapTabInputBinding, {
},
_getTabName: function(anchor) {
return anchor.attr('data-value') || anchor.text();
},
// nav-navtree is built on a combination of Bootstrap's tab &
// collapse components, but the tab component isn't smart enough to
// know about the deactive when are activated. Note that this logic is
// very similar to shinydashboard's deactivateOtherTabs() (in tab.js)
_deactivateOtherTabs: function(event) {
var tgt = $(event.target);
var nav = tgt.parents(".nav-navtree");
nav.find("li").not(tgt).removeClass("active"); // BS3
nav.find("li > a").not(tgt).removeClass("active"); // BS4
}
});
inputBindings.register(bootstrapTabInputBinding, 'shiny.bootstrapTabInput');
38 changes: 38 additions & 0 deletions srcjs/shinyapp.js
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,44 @@ var ShinyApp = function() {
exports.renderContent(el, el.innerHTML || el.textContent);
});

// If we're inserting a navbarMenu() into a navtreePanel() target, we need
// to transform buildTabset() output (i.e., a .dropdown component) to
// buildTreePanel() output (i.e., a .collapse component), because
// insertTab() et al. doesn't know about the relevant tabset container
if ($tabset.hasClass("nav-navtree") && $liTag.hasClass("dropdown")) {
var collapseId = "collapse-" + tabsetId + "-" + index; // TODO: index is undefined?
$tabset.find(".dropdown").each(function(i, el) {
var $el = $(el).removeClass("dropdown nav-item");

$el.find(".dropdown-toggle")
.removeClass("dropdown-toggle nav-link")
.addClass(message.select ? "" : "collapsed")
.attr("data-toggle", "collapse")
.attr("data-target", "#" + collapseId);

var collapse = $("<div>")
.addClass("collapse" + (message.select ? " show" : ""))
.attr("id", collapseId);

var menu = $el.find(".dropdown-menu")
.removeClass("dropdown-menu")
.addClass("nav nav-navtree")
.wrap(collapse);

var depth = $el.parents(".nav-navtree").length - 1;
if (depth > 0) {
$el.find("a").css("padding-left", depth + "rem");
}

if (menu.find("li").length === 0) {
menu.find("a")
.removeClass("dropdown-item")
.addClass("nav-link")
.wrap("<li class='nav-item'></li>");
}
});
}

if (message.select) {
$liTag.find("a").tab("show");
}
Expand Down

0 comments on commit 6159259

Please sign in to comment.