insertion fully implemented

This commit is contained in:
Barbara Borges Ribeiro
2017-07-22 07:53:40 +01:00
parent 0e7c78bae3
commit d57aa33b40
10 changed files with 360 additions and 243 deletions

View File

@@ -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"
}

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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}}

View File

@@ -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{

View File

@@ -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) {