mirror of
https://github.com/rstudio/shiny.git
synced 2026-04-07 03:00:20 -04:00
insertion fully implemented
This commit is contained in:
148
R/bootstrap.R
148
R/bootstrap.R
@@ -760,46 +760,60 @@ navlistPanel <- function(...,
|
||||
fixedRow(columns)
|
||||
}
|
||||
|
||||
|
||||
# -- Helpers to build tabsetPanels (& Co.) and their elements
|
||||
markTabAsSelected <- function(x) {
|
||||
attr(x, "selected") <- TRUE
|
||||
x
|
||||
}
|
||||
isTabSelected <- function(x) isTRUE(attr(x, "selected", exact = TRUE))
|
||||
containsSelectedTab <- function(tabs) any(vapply(tabs, isTabSelected, logical(1)))
|
||||
|
||||
findAndMarkSelectedTab <- function(tabs, selected, foundSelectedItem) {
|
||||
tabs <- lapply(tabs, function(divTag) {
|
||||
if (foundSelectedItem || is.character(divTag)) { # strings are not selectable items
|
||||
isTabSelected <- function(x) {
|
||||
isTRUE(attr(x, "selected", exact = TRUE))
|
||||
}
|
||||
|
||||
} else if (inherits(divTag, "shiny.navbarmenu")) {
|
||||
res <- findAndMarkSelectedTab(divTag$tabs, selected, foundSelectedItem)
|
||||
divTag$tabs <- res$tabs
|
||||
foundSelectedItem <<- res$foundSelectedItem
|
||||
containsSelectedTab <- function(tabs) {
|
||||
any(vapply(tabs, isTabSelected, logical(1)))
|
||||
}
|
||||
|
||||
} else { # Regular tab item
|
||||
findAndMarkSelectedTab <- function(tabs, selected, foundSelected) {
|
||||
tabs <- lapply(tabs, function(div) {
|
||||
if (foundSelected || is.character(div)) {
|
||||
# Strings are not selectable items
|
||||
|
||||
} else if (inherits(div, "shiny.navbarmenu")) {
|
||||
# Recur for navbarMenus
|
||||
res <- findAndMarkSelectedTab(div$tabs, selected, foundSelected)
|
||||
div$tabs <- res$tabs
|
||||
foundSelected <<- res$foundSelected
|
||||
|
||||
} else {
|
||||
# Base case: regular tab item. If the `selected` argument is
|
||||
# provided, check for a match in the existing tabs; else,
|
||||
# mark first available item as selected
|
||||
if (is.null(selected)) {
|
||||
foundSelectedItem <<- TRUE
|
||||
divTag <- markTabAsSelected(divTag) # mark first available item
|
||||
|
||||
foundSelected <<- TRUE
|
||||
div <- markTabAsSelected(div)
|
||||
} else {
|
||||
tabValue <- divTag$attribs$`data-value` %OR% divTag$attribs$title
|
||||
if (identical(selected, tabValue)) { # If selected tab is specified, check for match
|
||||
foundSelectedItem <<- TRUE
|
||||
divTag <- markTabAsSelected(divTag)
|
||||
tabValue <- div$attribs$`data-value` %OR% div$attribs$title
|
||||
if (identical(selected, tabValue)) {
|
||||
foundSelected <<- TRUE
|
||||
div <- markTabAsSelected(div)
|
||||
}
|
||||
}
|
||||
}
|
||||
return(divTag)
|
||||
return(div)
|
||||
})
|
||||
return(list(tabs = tabs, foundSelectedItem = foundSelectedItem))
|
||||
return(list(tabs = tabs, foundSelected = foundSelected))
|
||||
}
|
||||
|
||||
# Returns the icon object (or NULL if none) provided either a tabPanel, or the icon class
|
||||
getIcon <- function(tab = NULL, iconClass = tab$attribs$`data-icon-class`) {
|
||||
# Returns the icon object (or NULL if none), provided either a
|
||||
# tabPanel, OR the icon class
|
||||
getIcon <- function(tab = NULL, iconClass = NULL) {
|
||||
if (!is.null(tab)) iconClass <- tab$attribs$`data-icon-class`
|
||||
if (!is.null(iconClass)) {
|
||||
# for font-awesome we specify fixed-width
|
||||
if (grepl("fa-", iconClass, fixed = TRUE)) iconClass <- paste(iconClass, "fa-fw")
|
||||
if (grepl("fa-", iconClass, fixed = TRUE)) {
|
||||
# for font-awesome we specify fixed-width
|
||||
iconClass <- paste(iconClass, "fa-fw")
|
||||
}
|
||||
icon(name = NULL, class = iconClass)
|
||||
} else NULL
|
||||
}
|
||||
@@ -810,61 +824,87 @@ navbarMenuTextFilter <- function(text) {
|
||||
else tags$li(class = "dropdown-header", text)
|
||||
}
|
||||
|
||||
# This function is called internally by navbarPage, tabsetPanel and navlistPanel
|
||||
buildTabset <- function(tabs, ulClass, textFilter = NULL, id = NULL, selected = NULL, foundSelectedItem = FALSE) {
|
||||
res <- findAndMarkSelectedTab(tabs, selected, foundSelectedItem)
|
||||
tabs <- res$tabs
|
||||
foundSelectedItem <- res$foundSelectedItem
|
||||
# This function is called internally by navbarPage, tabsetPanel
|
||||
# and navlistPanel
|
||||
buildTabset <- function(tabs, ulClass, textFilter = NULL, id = NULL,
|
||||
selected = NULL, foundSelected = FALSE) {
|
||||
|
||||
res <- findAndMarkSelectedTab(tabs, selected, foundSelected)
|
||||
tabs <- res$tabs
|
||||
foundSelected <- res$foundSelected
|
||||
|
||||
# add input class if we have an id
|
||||
if (!is.null(id)) ulClass <- paste(ulClass, "shiny-tab-input")
|
||||
|
||||
if (!is.null(id)) ulClass <- paste(ulClass, "shiny-tab-input") # add input class if we have an id
|
||||
if (anyNamed(tabs)) {
|
||||
nms <- names(tabs)
|
||||
nms <- nms[nzchar(nms)]
|
||||
stop("Tabs should all be unnamed arguments, but some are named: ", paste(nms, collapse = ", "))
|
||||
stop("Tabs should all be unnamed arguments, but some are named: ",
|
||||
paste(nms, collapse = ", "))
|
||||
}
|
||||
|
||||
tabsetId <- p_randomInt(1000, 10000)
|
||||
tabs <- lapply(seq_len(length(tabs)), buildTabItem,
|
||||
tabsetId = tabsetId, foundSelectedItem = foundSelectedItem, tabs = tabs, textFilter = textFilter)
|
||||
tabsetId = tabsetId, foundSelected = foundSelected,
|
||||
tabs = tabs, textFilter = textFilter)
|
||||
|
||||
tabNavList <- tags$ul(class = ulClass, id = id,
|
||||
`data-tabsetid` = tabsetId, lapply(tabs, "[[", 1))
|
||||
|
||||
tabContent <- tags$div(class = "tab-content",
|
||||
`data-tabsetid` = tabsetId, lapply(tabs, "[[", 2))
|
||||
|
||||
tabNavList <- tags$ul( class = ulClass, id = id, `data-tabsetid` = tabsetId, lapply(tabs, "[[", 1))
|
||||
tabContent <- tags$div(class = "tab-content", `data-tabsetid` = tabsetId, lapply(tabs, "[[", 2))
|
||||
list(navList = tabNavList, content = tabContent)
|
||||
}
|
||||
|
||||
# Builds tabPanel/navbarMenu items (this function used to be declared inside the buildTabset()
|
||||
# function and it's been refactored for clarity and reusability). Called internally by buildTabset.
|
||||
buildTabItem <- function(index, tabsetId, foundSelectedItem, tabs = NULL, divTag = NULL, textFilter = NULL) {
|
||||
# Builds tabPanel/navbarMenu items (this function used to be
|
||||
# declared inside the buildTabset() function and it's been
|
||||
# refactored for clarity and reusability). Called internally
|
||||
# by buildTabset.
|
||||
buildTabItem <- function(index, tabsetId, foundSelected, tabs = NULL,
|
||||
divTag = NULL, textFilter = NULL) {
|
||||
|
||||
divTag <- if (!is.null(divTag)) divTag else tabs[[index]]
|
||||
|
||||
# check for text; pass it to the textFilter or skip it if there is none
|
||||
if (is.character(divTag) && !is.null(textFilter)) {
|
||||
# text item: pass it to the textFilter if it exists
|
||||
liTag <- textFilter(divTag)
|
||||
divTag <- NULL
|
||||
|
||||
} else if (inherits(divTag, "shiny.navbarmenu")) {
|
||||
# build the child tabset
|
||||
tabset <- buildTabset(divTag$tabs, "dropdown-menu", navbarMenuTextFilter, foundSelectedItem = foundSelectedItem)
|
||||
# navbarMenu item: build the child tabset
|
||||
tabset <- buildTabset(divTag$tabs, "dropdown-menu",
|
||||
navbarMenuTextFilter, foundSelected = foundSelected)
|
||||
|
||||
# if this navbarMenu contains a selected item, mark it active
|
||||
containsSelected <- containsSelectedTab(divTag$tabs)
|
||||
liTag <- tags$li(
|
||||
# If this navbar menu contains a selected item, mark it as active
|
||||
class = paste0("dropdown", if (containsSelectedTab(divTag$tabs)) " active"),
|
||||
tags$a(href = "#", class = "dropdown-toggle", `data-toggle` = "dropdown",
|
||||
`data-menuName` = divTag$menuName,
|
||||
class = paste0("dropdown", if (containsSelected) " active"),
|
||||
tags$a(href = "#",
|
||||
class = "dropdown-toggle", `data-toggle` = "dropdown",
|
||||
`data-value` = divTag$menuName,
|
||||
divTag$title, tags$b(class = "caret"),
|
||||
getIcon(iconClass = divTag$iconClass)
|
||||
),
|
||||
tabset$navList
|
||||
tabset$navList # inner tabPanels items
|
||||
)
|
||||
divTag <- tabset$content$children # list of tab content divs from the child tabset
|
||||
# list of tab content divs from the child tabset
|
||||
divTag <- tabset$content$children
|
||||
|
||||
} else { # Standard tabPanel item
|
||||
} else {
|
||||
# tabPanel item: create the tab's liTag and divTag
|
||||
tabId <- paste("tab", tabsetId, index, sep = "-")
|
||||
liTag <- tags$li(tags$a(
|
||||
href = paste("#", tabId, sep = ""), `data-toggle` = "tab",
|
||||
`data-value` = divTag$attribs$`data-value`,
|
||||
divTag$attribs$title,
|
||||
getIcon(iconClass = divTag$attribs$`data-icon-class`)
|
||||
))
|
||||
if (isTabSelected(divTag)) { # If this tab is selected, mark both tags as active
|
||||
liTag <- tags$li(
|
||||
tags$a(
|
||||
href = paste("#", tabId, sep = ""),
|
||||
`data-toggle` = "tab",
|
||||
`data-value` = divTag$attribs$`data-value`,
|
||||
divTag$attribs$title,
|
||||
getIcon(iconClass = divTag$attribs$`data-icon-class`)
|
||||
)
|
||||
)
|
||||
# if this tabPanel is selected item, mark it active
|
||||
if (isTabSelected(divTag)) {
|
||||
liTag$attribs$class <- "active"
|
||||
divTag$attribs$class <- "tab-pane active"
|
||||
}
|
||||
|
||||
@@ -66,11 +66,13 @@
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' # TODO: add example usage for inserting `navbarMenu`
|
||||
#'
|
||||
#' @export
|
||||
insertTab <- function(inputId, tab, target,
|
||||
position = c("before", "after"),
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
position = c("before", "after"),
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(inputId)
|
||||
force(target)
|
||||
position <- match.arg(position)
|
||||
@@ -80,9 +82,9 @@ insertTab <- function(inputId, tab, target,
|
||||
# Note: until now, the number of tabs in a tabsetPanel (or navbarPage
|
||||
# or navlistPanel) was always fixed. So, an easy way to give an id to
|
||||
# a tab was simply incrementing a counter. (Just like it was easy to
|
||||
# give a random 4-digit number to identify the tabsetPanel). Now that
|
||||
# we're introducing dynamic tabs, we have to retrive these numbers.
|
||||
|
||||
# give a random 4-digit number to identify the tabsetPanel). Since we
|
||||
# can only know this in the client side, we'll just pass `id` and
|
||||
# `tsid` (TabSetID) as dummy values that will be fixed in the JS code.
|
||||
item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
|
||||
textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
|
||||
|
||||
@@ -91,12 +93,12 @@ insertTab <- function(inputId, tab, target,
|
||||
inputId = inputId,
|
||||
liTag = processDeps(item$liTag, session),
|
||||
divTag = processDeps(item$divTag, session),
|
||||
menuName = NULL,
|
||||
target = target,
|
||||
prepend = FALSE,
|
||||
append = FALSE,
|
||||
position = position)
|
||||
}
|
||||
|
||||
session$onFlushed(callback, once = TRUE)
|
||||
}
|
||||
|
||||
@@ -115,8 +117,7 @@ insertTab <- function(inputId, tab, target,
|
||||
#' @rdname insertTab
|
||||
#' @export
|
||||
prependTab <- function(inputId, tab, menuName = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(inputId)
|
||||
force(tab)
|
||||
force(menuName)
|
||||
@@ -130,20 +131,19 @@ prependTab <- function(inputId, tab, menuName = NULL,
|
||||
inputId = inputId,
|
||||
liTag = processDeps(item$liTag, session),
|
||||
divTag = processDeps(item$divTag, session),
|
||||
menuName = menuName,
|
||||
target = NULL,
|
||||
prepend = TRUE,
|
||||
append = FALSE,
|
||||
position = NULL)
|
||||
}
|
||||
|
||||
session$onFlushed(callback, once = TRUE)
|
||||
}
|
||||
|
||||
#' @rdname insertTab
|
||||
#' @export
|
||||
appendTab <- function(inputId, tab, menuName = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(inputId)
|
||||
force(tab)
|
||||
force(menuName)
|
||||
@@ -157,20 +157,19 @@ appendTab <- function(inputId, tab, menuName = NULL,
|
||||
inputId = inputId,
|
||||
liTag = processDeps(item$liTag, session),
|
||||
divTag = processDeps(item$divTag, session),
|
||||
menuName = menuName,
|
||||
target = NULL,
|
||||
prepend = FALSE,
|
||||
append = TRUE,
|
||||
position = NULL)
|
||||
}
|
||||
|
||||
session$onFlushed(callback, once = TRUE)
|
||||
}
|
||||
|
||||
#' @rdname insertTab
|
||||
#' @export
|
||||
removeTab <- function(inputId, target,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(inputId)
|
||||
force(target)
|
||||
force(session)
|
||||
@@ -180,7 +179,6 @@ removeTab <- function(inputId, target,
|
||||
inputId = inputId,
|
||||
target = target)
|
||||
}
|
||||
|
||||
session$onFlushed(callback, once = TRUE)
|
||||
}
|
||||
|
||||
@@ -242,8 +240,7 @@ removeTab <- function(inputId, target,
|
||||
#'
|
||||
#' @export
|
||||
showTab <- function(inputId, target,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(inputId)
|
||||
force(target)
|
||||
force(session)
|
||||
@@ -255,15 +252,13 @@ showTab <- function(inputId, target,
|
||||
type = "show"
|
||||
)
|
||||
}
|
||||
|
||||
session$onFlushed(callback, once = TRUE)
|
||||
}
|
||||
|
||||
#' @rdname showTab
|
||||
#' @export
|
||||
hideTab <- function(inputId, target,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(inputId)
|
||||
force(target)
|
||||
force(session)
|
||||
@@ -275,6 +270,5 @@ hideTab <- function(inputId, target,
|
||||
type = "hide"
|
||||
)
|
||||
}
|
||||
|
||||
session$onFlushed(callback, once = TRUE)
|
||||
}
|
||||
|
||||
@@ -1492,16 +1492,18 @@ ShinySession <- R6Class(
|
||||
)
|
||||
)
|
||||
},
|
||||
sendInsertTab = function(inputId, liTag, divTag, target, prepend, append, position) {
|
||||
sendInsertTab = function(inputId, liTag, divTag, menuName,
|
||||
target, prepend, append, position) {
|
||||
if (is.null(target) && prepend == append) {
|
||||
stop("If target is NULL, either `prepend` or `append` must be TRUE.",
|
||||
"Both cannot be TRUE, however.")
|
||||
stop("If target is NULL, either `prepend` or `append` ",
|
||||
"must be TRUE. Both cannot be TRUE, however.")
|
||||
}
|
||||
private$sendMessage(
|
||||
`shiny-insert-tab` = list(
|
||||
inputId = inputId,
|
||||
liTag = liTag,
|
||||
divTag = divTag,
|
||||
menuName = menuName,
|
||||
target = target,
|
||||
prepend = prepend,
|
||||
append = append,
|
||||
|
||||
@@ -1333,51 +1333,56 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
|
||||
});
|
||||
});
|
||||
|
||||
addMessageHandler('shiny-insert-tab', function (message) {
|
||||
var $tabsetPanel = $("#" + message.inputId);
|
||||
if ($tabsetPanel.length === 0) {
|
||||
throw 'There is no tabsetPanel with id ' + message.inputId;
|
||||
};
|
||||
var tabsetNumericId = $tabsetPanel.attr("data-tabsetid");
|
||||
var $tabContent = $("div.tab-content[data-tabsetid='" + tabsetNumericId + "']");
|
||||
addMessageHandler("shiny-insert-tab", function (message) {
|
||||
var $parentTabset = $("#" + message.inputId);
|
||||
if ($parentTabset.length === 0) throw "There is no tabsetPanel (or navbarPage or " + "navlistPanel) with id " + message.inputId;
|
||||
|
||||
var $tabset = $parentTabset;
|
||||
var tabsetId = $parentTabset.attr("data-tabsetid");
|
||||
var $tabContent = $("div.tab-content[data-tabsetid='" + tabsetId + "']");
|
||||
|
||||
var $divTag = $(message.divTag.html);
|
||||
var $liTag = $(message.liTag.html);
|
||||
var $liChild = $liTag.find("> a");
|
||||
var $aTag = $liTag.find("> a");
|
||||
|
||||
if ($liChild.attr("data-toggle") === "tab") {
|
||||
// for regular tab, construct the correct tabId for both the li and the div tags
|
||||
var tabId = "tab-" + tabsetNumericId + "-" + getTabIndex();
|
||||
$liTag.find('> a[data-toggle="tab"]').attr("href", "#" + tabId);
|
||||
// Unless the item is being prepended/appended, the target tab
|
||||
// must be provided
|
||||
if (message.target !== null) {
|
||||
var selector = "a" + "[data-value='" + message.target + "']";
|
||||
var $targetLiTag = $parentTabset.find(selector).parent();
|
||||
if ($targetLiTag.length === 0) {
|
||||
throw "There is no tabPanel (or navbarMenu) with value" + " (or menuName) equal to '" + message.target + "'";
|
||||
}
|
||||
}
|
||||
|
||||
// If the item is to be placed inside a navbarMenu (dropdown),
|
||||
// change the value of $tabset from the parent's ul tag to the
|
||||
// dropdown's ul tag
|
||||
var dropdown = getDropdown();
|
||||
if (dropdown !== null) {
|
||||
if ($aTag.attr("data-toggle") === "dropdown") throw "Cannot insert a navbarMenu inside another one";
|
||||
$tabset = dropdown.$tabset;
|
||||
tabsetId = dropdown.id;
|
||||
}
|
||||
|
||||
// For regular tab items, fix the href (of the li > a tag)
|
||||
// and the id (of the div tag). This does not apply to plain
|
||||
// text items (which function as dividers and headers inside
|
||||
// navbarMenus) and whole navbarMenus (since those get
|
||||
// constructed from scratch on the R side and therefore
|
||||
// there are no ids that need matching)
|
||||
if ($aTag.attr("data-toggle") === "tab") {
|
||||
var index = getTabIndex($tabset, tabsetId);
|
||||
var tabId = "tab-" + tabsetId + "-" + index;
|
||||
$liTag.find("> a").attr("href", "#" + tabId);
|
||||
$divTag.attr("id", tabId);
|
||||
}
|
||||
|
||||
if (message.prepend || message.append) {
|
||||
if (message.prepend) {
|
||||
$tabsetPanel.prepend($liTag);
|
||||
$tabContent.prepend($divTag);
|
||||
} else if (message.append) {
|
||||
$tabsetPanel.append($liTag);
|
||||
$tabContent.append($divTag);
|
||||
}
|
||||
} else {
|
||||
var dataValue = "[data-value='" + message.target + "']";
|
||||
var $targetTabsetPanel = $tabsetPanel.find("a" + dataValue).parent();
|
||||
var $targetTabContent = $tabContent.find("div" + dataValue);
|
||||
// actually insert the item into the right place
|
||||
if (message.prepend) $tabset.prepend($liTag);else if (message.append) $tabset.append($liTag);else if (message.position === "before") $targetLiTag.before($liTag);else if (message.position === "after") $targetLiTag.after($liTag);
|
||||
$tabContent.append($divTag);
|
||||
|
||||
if ($targetTabsetPanel.length === 0) {
|
||||
throw "There is no tabPanel with value " + message.target;
|
||||
} else {
|
||||
if (message.position === "before") {
|
||||
$targetTabsetPanel.before($liTag);
|
||||
$targetTabContent.before($divTag);
|
||||
} else if (message.position === "after") {
|
||||
$targetTabsetPanel.after($liTag);
|
||||
$targetTabContent.after($divTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.renderContent($tabsetPanel[0], $tabsetPanel.html());
|
||||
exports.renderContent($parentTabset[0], $parentTabset.html());
|
||||
exports.renderContent($tabContent[0], $tabContent.html());
|
||||
|
||||
/* Barbara -- August 2017
|
||||
@@ -1385,59 +1390,88 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
|
||||
or navlistPanel) was always fixed. So, an easy way to give an id to
|
||||
a tab was simply incrementing a counter. (Just like it was easy to
|
||||
give a random 4-digit number to identify the tabsetPanel). Now that
|
||||
we're introducing dynamic tabs, we have to retrive these numbers and
|
||||
we're introducing dynamic tabs, we must retrieve these numbers and
|
||||
fix the dummy id given to the tab in the R side -- there, we always
|
||||
set the tab id (counter dummy) to "id" and the tabset id to "tsid") */
|
||||
function getTabIndex() {
|
||||
var prevTabIds = [];
|
||||
var leadingHref = "#tab-" + tabsetNumericId + "-";
|
||||
// loop through all existing tabs, find the one with highest id (since
|
||||
// this is based on a numeric counter) and add 1 to get the new tab's id
|
||||
$tabsetPanel.find("> li").each(function () {
|
||||
var $prevTabs = $(this).find('> a[data-toggle="tab"]');
|
||||
if ($prevTabs.length > 0) prevTabIds.push($prevTabs.attr('href').replace(leadingHref, ''));
|
||||
set the tab id (counter dummy) to "id" and the tabset id to "tsid")
|
||||
*/
|
||||
function getTabIndex($tabset, tabsetId) {
|
||||
var existingTabIds = [];
|
||||
var leadingHref = "#tab-" + tabsetId + "-";
|
||||
// loop through all existing tabs, find the one with highest id
|
||||
// (since this is based on a numeric counter), and increment
|
||||
$tabset.find("> li").each(function () {
|
||||
var $tab = $(this).find("> a[data-toggle='tab']");
|
||||
if ($tab.length > 0) {
|
||||
var index = $tab.attr("href").replace(leadingHref, "");
|
||||
existingTabIds.push(Number(index));
|
||||
}
|
||||
});
|
||||
prevTabIds = prevTabIds.map(Number); // convert strings to numbers
|
||||
return Math.max.apply(null, prevTabIds) + 1;
|
||||
return Math.max.apply(null, existingTabIds) + 1;
|
||||
}
|
||||
|
||||
// Finds out if the item will be placed inside a navbarMenu
|
||||
// (dropdown). If so, returns the dropdown tabset (ul tag)
|
||||
// and the dropdown tabsetid (to be used to fix the tab ID)
|
||||
function getDropdown() {
|
||||
if (message.menuName !== null) {
|
||||
// menuName is only provided if the user wants to prepend
|
||||
// or append an item inside a navbarMenu (dropdown)
|
||||
var $dropdownATag = $("a.dropdown-toggle[data-value='" + message.menuName + "']");
|
||||
if ($dropdownATag.length === 0) {
|
||||
throw "There is no navbarMenu with menuName equal to '" + message.menuName + "'";
|
||||
}
|
||||
var $dropdownTabset = $dropdownATag.find("+ ul.dropdown-menu");
|
||||
var dropdownId = $dropdownTabset.attr("data-tabsetid");
|
||||
return { $tabset: $dropdownTabset, id: dropdownId };
|
||||
} else if (message.target !== null) {
|
||||
// if our item is to be placed next to a tab that is inside
|
||||
// a navbarMenu, our item will also be inside
|
||||
var $uncleTabset = $targetLiTag.parent("ul");
|
||||
if ($uncleTabset.hasClass("dropdown-menu")) {
|
||||
var uncleId = $uncleTabset.attr("data-tabsetid");
|
||||
return { $tabset: $uncleTabset, id: uncleId };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
addMessageHandler('shiny-remove-tab', function (message) {
|
||||
var $tabsetPanel = $("#" + message.inputId);
|
||||
if ($tabsetPanel.length === 0) {
|
||||
throw 'There is no tabsetPanel with id ' + message.inputId;
|
||||
addMessageHandler("shiny-remove-tab", function (message) {
|
||||
var $parentTabset = $("#" + message.inputId);
|
||||
if ($parentTabset.length === 0) {
|
||||
throw "There is no tabsetPanel with id " + message.inputId;
|
||||
};
|
||||
var $tabContent = $tabsetPanel.find("+ .tab-content");
|
||||
var $tabContent = $parentTabset.find("+ .tab-content");
|
||||
var dataValue = "[data-value='" + message.target + "']";
|
||||
|
||||
var $targetTabsetPanel = $tabsetPanel.find("a" + dataValue).parent();
|
||||
var $targetTabContent = $tabContent.find("div" + dataValue);
|
||||
var $targetLiTag = $parentTabset.find("a" + dataValue).parent();
|
||||
var $targetDivTag = $tabContent.find("div" + dataValue);
|
||||
|
||||
if ($targetTabsetPanel.length === 0) {
|
||||
throw 'There is no tabPanel with value ' + message.target;
|
||||
if ($targetLiTag.length === 0) {
|
||||
throw "There is no tabPanel with value " + message.target;
|
||||
}
|
||||
|
||||
var els = [$targetTabsetPanel, $targetTabContent];
|
||||
var els = [$targetLiTag, $targetDivTag];
|
||||
$(els).each(function (i, el) {
|
||||
exports.unbindAll(el, true);
|
||||
$(el).remove();
|
||||
});
|
||||
});
|
||||
|
||||
addMessageHandler('shiny-change-tab-visibility', function (message) {
|
||||
var $tabsetPanel = $("#" + message.inputId);
|
||||
if ($tabsetPanel.length === 0) {
|
||||
throw 'There is no tabsetPanel with id ' + message.inputId;
|
||||
addMessageHandler("shiny-change-tab-visibility", function (message) {
|
||||
var $parentTabset = $("#" + message.inputId);
|
||||
if ($parentTabset.length === 0) {
|
||||
throw "There is no tabsetPanel with id " + message.inputId;
|
||||
};
|
||||
|
||||
var dataValue = "[data-value='" + message.target + "']";
|
||||
var $targetTabsetPanel = $tabsetPanel.find("a" + dataValue).parent();
|
||||
var $targetLiTag = $parentTabset.find("a" + dataValue).parent();
|
||||
|
||||
if ($targetTabsetPanel.length === 0) {
|
||||
throw 'There is no tabPanel with value ' + message.target;
|
||||
if ($targetLiTag.length === 0) {
|
||||
throw "There is no tabPanel with value " + message.target;
|
||||
}
|
||||
|
||||
if (message.type === "show") $targetTabsetPanel.show();else if (message.type === "hide") $targetTabsetPanel.hide();
|
||||
if (message.type === "show") $targetLiTag.show();else if (message.type === "hide") $targetLiTag.hide();
|
||||
});
|
||||
|
||||
addMessageHandler('updateQueryString', function (message) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
6
inst/www/shared/shiny.min.js
vendored
6
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -19,8 +19,8 @@ appendTab(inputId, tab, menuName = NULL,
|
||||
removeTab(inputId, target, session = getDefaultReactiveDomain())
|
||||
}
|
||||
\arguments{
|
||||
\item{inputId}{The \code{id} of the \code{tabsetPanel} (or
|
||||
\code{navlistPanel} or \code{navbarPage})into which \code{tab} will
|
||||
\item{inputId}{The \code{id} of the \code{tabsetPanel} (or
|
||||
\code{navlistPanel} or \code{navbarPage})into which \code{tab} will
|
||||
be inserted/removed.}
|
||||
|
||||
\item{tab}{The tab element to be added (must be created with
|
||||
@@ -29,40 +29,40 @@ be inserted/removed.}
|
||||
\item{target}{The \code{value} of an existing \code{tabPanel}, next to
|
||||
which \code{tab} will be added.}
|
||||
|
||||
\item{position}{Should \code{tab} be added before or after the
|
||||
\item{position}{Should \code{tab} be added before or after the
|
||||
\code{target} tab?}
|
||||
|
||||
\item{session}{The shiny session within which to call \code{insertTab}.}
|
||||
|
||||
\item{menuName}{This argument should only be used when you want to
|
||||
prepend (or append) \code{tab} to the beginning (or end) of an
|
||||
prepend (or append) \code{tab} to the beginning (or end) of an
|
||||
existing \code{\link{navbarMenu}} (which must itself be part of
|
||||
an existing \code{\link{navbarPage}}). In this case, this argument
|
||||
should be the \code{menuName} that you gave your \code{navbarMenu}
|
||||
when you first created it (by default, this is equal to the value
|
||||
an existing \code{\link{navbarPage}}). In this case, this argument
|
||||
should be the \code{menuName} that you gave your \code{navbarMenu}
|
||||
when you first created it (by default, this is equal to the value
|
||||
of the \code{title} argument). Note that you still need to set the
|
||||
\code{inputId} argument to whatever the \code{id} of the parent
|
||||
\code{navbarPage} is. If \code{menuName} is left as \code{NULL},
|
||||
\code{tab} will be prepended (or appended) to whatever
|
||||
\code{tab} will be prepended (or appended) to whatever
|
||||
\code{inputId} is.}
|
||||
}
|
||||
\description{
|
||||
Dynamically insert or remove a \code{\link{tabPanel}} from an existing
|
||||
\code{\link{tabsetPanel}}, \code{\link{navlistPanel}} or
|
||||
\code{\link{tabsetPanel}}, \code{\link{navlistPanel}} or
|
||||
\code{\link{navbarPage}}.
|
||||
}
|
||||
\details{
|
||||
When you want to insert a new tab before of after an existing tab, you
|
||||
When you want to insert a new tab before of after an existing tab, you
|
||||
should use \code{insertTab}. When you want to prepend a tab (i.e. add a
|
||||
tab to the beginning of the \code{tabsetPanel}), use \code{prependTab}.
|
||||
When you want to append a tab (i.e. add a tab to the end of the
|
||||
When you want to append a tab (i.e. add a tab to the end of the
|
||||
\code{tabsetPanel}), use \code{appendTab}.
|
||||
|
||||
For \code{navbarPage}, you can insert/remove conventional
|
||||
For \code{navbarPage}, you can insert/remove conventional
|
||||
\code{tabPanel}s (whether at the top level or nested inside a
|
||||
\code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
|
||||
For the latter case, \code{target} should be the \code{menuName} that
|
||||
you gave your \code{navbarMenu} when you first created it (by default,
|
||||
\code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
|
||||
For the latter case, \code{target} should be the \code{menuName} that
|
||||
you gave your \code{navbarMenu} when you first created it (by default,
|
||||
this is equal to the value of the \code{title} argument).
|
||||
}
|
||||
\examples{
|
||||
@@ -97,6 +97,9 @@ server <- function(input, output, session) {
|
||||
|
||||
shinyApp(ui, server)
|
||||
}
|
||||
|
||||
# TODO: add example usage for inserting `navbarMenu`
|
||||
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{showTab}}
|
||||
|
||||
@@ -10,28 +10,28 @@ showTab(inputId, target, session = getDefaultReactiveDomain())
|
||||
hideTab(inputId, target, session = getDefaultReactiveDomain())
|
||||
}
|
||||
\arguments{
|
||||
\item{inputId}{The \code{id} of the \code{tabsetPanel} (or
|
||||
\code{navlistPanel} or \code{navbarPage})into which \code{tab} will
|
||||
\item{inputId}{The \code{id} of the \code{tabsetPanel} (or
|
||||
\code{navlistPanel} or \code{navbarPage})into which \code{tab} will
|
||||
be inserted/removed.}
|
||||
|
||||
\item{target}{The \code{value} of the \code{tabPanel} to be
|
||||
hidden/shown. See Details if you want to hide/show an entire
|
||||
\code{navbarMenu} instead.}
|
||||
|
||||
\item{session}{The shiny session within which to call this
|
||||
\item{session}{The shiny session within which to call this
|
||||
function.}
|
||||
}
|
||||
\description{
|
||||
Dynamically hide or show a \code{\link{tabPanel}} from an existing
|
||||
\code{\link{tabsetPanel}}, \code{\link{navlistPanel}} or
|
||||
\code{\link{tabsetPanel}}, \code{\link{navlistPanel}} or
|
||||
\code{\link{navbarPage}}.
|
||||
}
|
||||
\details{
|
||||
For \code{navbarPage}, you can hide/show conventional
|
||||
For \code{navbarPage}, you can hide/show conventional
|
||||
\code{tabPanel}s (whether at the top level or nested inside a
|
||||
\code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
|
||||
For the latter case, \code{target} should be the \code{menuName} that
|
||||
you gave your \code{navbarMenu} when you first created it (by default,
|
||||
\code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
|
||||
For the latter case, \code{target} should be the \code{menuName} that
|
||||
you gave your \code{navbarMenu} when you first created it (by default,
|
||||
this is equal to the value of the \code{title} argument).
|
||||
}
|
||||
\examples{
|
||||
|
||||
@@ -714,51 +714,64 @@ var ShinyApp = function() {
|
||||
});
|
||||
});
|
||||
|
||||
addMessageHandler('shiny-insert-tab', function(message) {
|
||||
var $tabsetPanel = $("#" + message.inputId);
|
||||
if ($tabsetPanel.length === 0) {
|
||||
throw 'There is no tabsetPanel with id ' + message.inputId;
|
||||
};
|
||||
var tabsetNumericId = $tabsetPanel.attr("data-tabsetid");
|
||||
var $tabContent = $("div.tab-content[data-tabsetid='" + tabsetNumericId + "']");
|
||||
addMessageHandler("shiny-insert-tab", function(message) {
|
||||
var $parentTabset = $("#" + message.inputId);
|
||||
if ($parentTabset.length === 0)
|
||||
throw "There is no tabsetPanel (or navbarPage or " +
|
||||
"navlistPanel) with id " + message.inputId;
|
||||
|
||||
var $tabset = $parentTabset;
|
||||
var tabsetId = $parentTabset.attr("data-tabsetid");
|
||||
var $tabContent = $("div.tab-content[data-tabsetid='" +
|
||||
tabsetId + "']");
|
||||
|
||||
var $divTag = $(message.divTag.html);
|
||||
var $liTag = $(message.liTag.html);
|
||||
var $liChild = $liTag.find("> a");
|
||||
var $aTag = $liTag.find("> a");
|
||||
|
||||
if ($liChild.attr("data-toggle") === "tab") {
|
||||
// for regular tab, construct the correct tabId for both the li and the div tags
|
||||
var tabId = "tab-" + tabsetNumericId + "-" + getTabIndex();
|
||||
$liTag.find('> a[data-toggle="tab"]').attr("href", "#" + tabId);
|
||||
// Unless the item is being prepended/appended, the target tab
|
||||
// must be provided
|
||||
if (message.target !== null) {
|
||||
let selector = "a" + "[data-value='" + message.target + "']";
|
||||
var $targetLiTag = $parentTabset.find(selector).parent();
|
||||
if ($targetLiTag.length === 0) {
|
||||
throw "There is no tabPanel (or navbarMenu) with value" +
|
||||
" (or menuName) equal to '" + message.target + "'";
|
||||
}
|
||||
}
|
||||
|
||||
// If the item is to be placed inside a navbarMenu (dropdown),
|
||||
// change the value of $tabset from the parent's ul tag to the
|
||||
// dropdown's ul tag
|
||||
var dropdown = getDropdown();
|
||||
if (dropdown !== null) {
|
||||
if ($aTag.attr("data-toggle") === "dropdown")
|
||||
throw "Cannot insert a navbarMenu inside another one";
|
||||
$tabset = dropdown.$tabset;
|
||||
tabsetId = dropdown.id;
|
||||
}
|
||||
|
||||
// For regular tab items, fix the href (of the li > a tag)
|
||||
// and the id (of the div tag). This does not apply to plain
|
||||
// text items (which function as dividers and headers inside
|
||||
// navbarMenus) and whole navbarMenus (since those get
|
||||
// constructed from scratch on the R side and therefore
|
||||
// there are no ids that need matching)
|
||||
if ($aTag.attr("data-toggle") === "tab") {
|
||||
var index = getTabIndex($tabset, tabsetId);
|
||||
var tabId = "tab-" + tabsetId + "-" + index;
|
||||
$liTag.find("> a").attr("href", "#" + tabId);
|
||||
$divTag.attr("id", tabId);
|
||||
}
|
||||
|
||||
if (message.prepend || message.append) {
|
||||
if (message.prepend) {
|
||||
$tabsetPanel.prepend($liTag);
|
||||
$tabContent.prepend($divTag);
|
||||
} else if (message.append) {
|
||||
$tabsetPanel.append($liTag);
|
||||
$tabContent.append($divTag);
|
||||
}
|
||||
} else {
|
||||
var dataValue = "[data-value='" + message.target + "']";
|
||||
var $targetTabsetPanel = $tabsetPanel.find("a" + dataValue).parent();
|
||||
var $targetTabContent = $tabContent.find("div" + dataValue);
|
||||
// actually insert the item into the right place
|
||||
if (message.prepend) $tabset.prepend($liTag);
|
||||
else if (message.append) $tabset.append($liTag);
|
||||
else if (message.position === "before") $targetLiTag.before($liTag);
|
||||
else if (message.position === "after") $targetLiTag.after($liTag);
|
||||
$tabContent.append($divTag);
|
||||
|
||||
if ($targetTabsetPanel.length === 0) {
|
||||
throw "There is no tabPanel with value " + message.target;
|
||||
} else {
|
||||
if (message.position === "before") {
|
||||
$targetTabsetPanel.before($liTag);
|
||||
$targetTabContent.before($divTag);
|
||||
} else if (message.position === "after") {
|
||||
$targetTabsetPanel.after($liTag);
|
||||
$targetTabContent.after($divTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.renderContent($tabsetPanel[0], $tabsetPanel.html());
|
||||
exports.renderContent($parentTabset[0], $parentTabset.html());
|
||||
exports.renderContent($tabContent[0], $tabContent.html());
|
||||
|
||||
/* Barbara -- August 2017
|
||||
@@ -766,61 +779,92 @@ var ShinyApp = function() {
|
||||
or navlistPanel) was always fixed. So, an easy way to give an id to
|
||||
a tab was simply incrementing a counter. (Just like it was easy to
|
||||
give a random 4-digit number to identify the tabsetPanel). Now that
|
||||
we're introducing dynamic tabs, we have to retrive these numbers and
|
||||
we're introducing dynamic tabs, we must retrieve these numbers and
|
||||
fix the dummy id given to the tab in the R side -- there, we always
|
||||
set the tab id (counter dummy) to "id" and the tabset id to "tsid") */
|
||||
function getTabIndex() {
|
||||
var prevTabIds = [];
|
||||
var leadingHref = "#tab-" + tabsetNumericId + "-";
|
||||
// loop through all existing tabs, find the one with highest id (since
|
||||
// this is based on a numeric counter) and add 1 to get the new tab's id
|
||||
$tabsetPanel.find("> li").each(function(){
|
||||
var $prevTabs = $(this).find('> a[data-toggle="tab"]');
|
||||
if ($prevTabs.length > 0)
|
||||
prevTabIds.push($prevTabs.attr('href').replace(leadingHref,''));
|
||||
set the tab id (counter dummy) to "id" and the tabset id to "tsid")
|
||||
*/
|
||||
function getTabIndex($tabset, tabsetId) {
|
||||
var existingTabIds = [];
|
||||
var leadingHref = "#tab-" + tabsetId + "-";
|
||||
// loop through all existing tabs, find the one with highest id
|
||||
// (since this is based on a numeric counter), and increment
|
||||
$tabset.find("> li").each(function() {
|
||||
var $tab = $(this).find("> a[data-toggle='tab']");
|
||||
if ($tab.length > 0) {
|
||||
var index = $tab.attr("href").replace(leadingHref, "");
|
||||
existingTabIds.push(Number(index));
|
||||
}
|
||||
});
|
||||
prevTabIds = prevTabIds.map(Number); // convert strings to numbers
|
||||
return(Math.max.apply(null, prevTabIds) + 1);
|
||||
return (Math.max.apply(null, existingTabIds) + 1);
|
||||
}
|
||||
|
||||
// Finds out if the item will be placed inside a navbarMenu
|
||||
// (dropdown). If so, returns the dropdown tabset (ul tag)
|
||||
// and the dropdown tabsetid (to be used to fix the tab ID)
|
||||
function getDropdown() {
|
||||
if (message.menuName !== null) {
|
||||
// menuName is only provided if the user wants to prepend
|
||||
// or append an item inside a navbarMenu (dropdown)
|
||||
var $dropdownATag = $("a.dropdown-toggle[data-value='" +
|
||||
message.menuName + "']");
|
||||
if ($dropdownATag.length === 0) {
|
||||
throw "There is no navbarMenu with menuName equal to '" +
|
||||
message.menuName + "'";
|
||||
}
|
||||
var $dropdownTabset = $dropdownATag.find("+ ul.dropdown-menu");
|
||||
var dropdownId = $dropdownTabset.attr("data-tabsetid");
|
||||
return { $tabset: $dropdownTabset, id: dropdownId };
|
||||
|
||||
} else if (message.target !== null) {
|
||||
// if our item is to be placed next to a tab that is inside
|
||||
// a navbarMenu, our item will also be inside
|
||||
var $uncleTabset = $targetLiTag.parent("ul");
|
||||
if ($uncleTabset.hasClass("dropdown-menu")) {
|
||||
var uncleId = $uncleTabset.attr("data-tabsetid");
|
||||
return { $tabset: $uncleTabset, id: uncleId };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
addMessageHandler('shiny-remove-tab', function(message) {
|
||||
var $tabsetPanel = $("#" + message.inputId);
|
||||
if ($tabsetPanel.length === 0) {
|
||||
throw 'There is no tabsetPanel with id ' + message.inputId;
|
||||
addMessageHandler("shiny-remove-tab", function(message) {
|
||||
var $parentTabset = $("#" + message.inputId);
|
||||
if ($parentTabset.length === 0) {
|
||||
throw "There is no tabsetPanel with id " + message.inputId;
|
||||
};
|
||||
var $tabContent = $tabsetPanel.find("+ .tab-content");
|
||||
var $tabContent = $parentTabset.find("+ .tab-content");
|
||||
var dataValue = "[data-value='" + message.target + "']";
|
||||
|
||||
var $targetTabsetPanel = $tabsetPanel.find("a" + dataValue).parent();
|
||||
var $targetTabContent = $tabContent.find("div" + dataValue);
|
||||
var $targetLiTag = $parentTabset.find("a" + dataValue).parent();
|
||||
var $targetDivTag = $tabContent.find("div" + dataValue);
|
||||
|
||||
if ($targetTabsetPanel.length === 0) {
|
||||
throw 'There is no tabPanel with value ' + message.target;
|
||||
if ($targetLiTag.length === 0) {
|
||||
throw "There is no tabPanel with value " + message.target;
|
||||
}
|
||||
|
||||
var els = [$targetTabsetPanel, $targetTabContent];
|
||||
var els = [$targetLiTag, $targetDivTag];
|
||||
$(els).each(function (i, el) {
|
||||
exports.unbindAll(el, true);
|
||||
$(el).remove();
|
||||
});
|
||||
});
|
||||
|
||||
addMessageHandler('shiny-change-tab-visibility', function(message) {
|
||||
var $tabsetPanel = $("#" + message.inputId);
|
||||
if ($tabsetPanel.length === 0) {
|
||||
throw 'There is no tabsetPanel with id ' + message.inputId;
|
||||
addMessageHandler("shiny-change-tab-visibility", function(message) {
|
||||
var $parentTabset = $("#" + message.inputId);
|
||||
if ($parentTabset.length === 0) {
|
||||
throw "There is no tabsetPanel with id " + message.inputId;
|
||||
};
|
||||
|
||||
var dataValue = "[data-value='" + message.target + "']";
|
||||
var $targetTabsetPanel = $tabsetPanel.find("a" + dataValue).parent();
|
||||
var $targetLiTag = $parentTabset.find("a" + dataValue).parent();
|
||||
|
||||
if ($targetTabsetPanel.length === 0) {
|
||||
throw 'There is no tabPanel with value ' + message.target;
|
||||
if ($targetLiTag.length === 0) {
|
||||
throw "There is no tabPanel with value " + message.target;
|
||||
}
|
||||
|
||||
if (message.type === "show") $targetTabsetPanel.show();
|
||||
else if (message.type === "hide") $targetTabsetPanel.hide();
|
||||
if (message.type === "show") $targetLiTag.show();
|
||||
else if (message.type === "hide") $targetLiTag.hide();
|
||||
});
|
||||
|
||||
addMessageHandler('updateQueryString', function(message) {
|
||||
|
||||
Reference in New Issue
Block a user