diff --git a/CRAN-SUBMISSION b/CRAN-SUBMISSION index 2b408937..c8861df0 100644 --- a/CRAN-SUBMISSION +++ b/CRAN-SUBMISSION @@ -1,3 +1,3 @@ -Version: 2.2.1 -Date: 2022-12-19 15:51:44 UTC -SHA: 1119ebead95ee388e684a36791707ad63b6e2785 \ No newline at end of file +Version: 2.3.0 +Date: 2023-06-15 20:15:12 UTC +SHA: f0452c4292e86a20aeabe440ec587c263e1811b4 diff --git a/DESCRIPTION b/DESCRIPTION index c087ebea..d77de64a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: bs4Dash Type: Package Title: A 'Bootstrap 4' Version of 'shinydashboard' -Version: 2.3.0 +Version: 2.3.3 Authors@R: c( person("David", "Granjon", email = "dgranjon@ymail.com", role = c("aut", "cre")), person(family = "RinteRface", role = "cph"), diff --git a/NEWS.md b/NEWS.md index c9c6001b..37d54db3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,16 @@ +# bs4Dash 2.3.3 + +## Breaking change (potential) + +- Fix #302: both `dashboardSidebar()` and `dashboardControlbar()` default __skin__ value is NULL. +This allows them to inherit from the parent `dashboardPage()` __dark__ parameter and have either +a full light or full dark skin. If not NULL, the corresponding skin is applied, regardless of the +parent `dashboardPage()`. + +## Bug fixes +- Remove unused `headTitles` parameter from `bs4Table()`. +- Fix #315: alert title is not added to alert body and if Alert is not closable the header contains "undefined" key word. Thanks @MohammedFCIS. + # bs4Dash 2.3.0 ## New feature diff --git a/R/dashboardControlbar.R b/R/dashboardControlbar.R index bad92aa4..fd4b8fca 100644 --- a/R/dashboardControlbar.R +++ b/R/dashboardControlbar.R @@ -10,7 +10,8 @@ #' in pixels, or a string that specifies the width in CSS units. 250 px by default. #' @param collapsed Whether the control bar on the right side is collapsed or not at start. TRUE by default. #' @param overlay Whether the sidebar covers the content when expanded. Default to TRUE. -#' @param skin Controlbar skin. "dark" or "light". +#' @param skin Controlbar skin. "dark" or "light". Matches the \link{dashboardPage} dark parameter +#' value. #' @param pinned Whether to block the controlbar state (TRUE or FALSE). Default to NULL. #' #' @author David Granjon, \email{dgranjon@@ymail.com} @@ -19,9 +20,11 @@ #' #' @export bs4DashControlbar <- function(..., id = NULL, disable = FALSE, width = 250, - collapsed = TRUE, overlay = TRUE, skin = "dark", + collapsed = TRUE, overlay = TRUE, skin = NULL, pinned = NULL) { if (is.null(id)) id <- "controlbarId" + + skin <- set_sidebar_skin(skin) controlbarTag <- shiny::tags$aside( class = paste0("control-sidebar control-sidebar-", skin), diff --git a/R/dashboardSidebar.R b/R/dashboardSidebar.R index 99e6e5ef..f35b93f0 100644 --- a/R/dashboardSidebar.R +++ b/R/dashboardSidebar.R @@ -8,7 +8,8 @@ #' @param width The width of the sidebar. This must either be a number which #' specifies the width in pixels, or a string that specifies the width in CSS #' units. -#' @param skin Sidebar skin. "dark" or "light". +#' @param skin Sidebar skin. "dark" or "light". Matches the \link{dashboardPage} dark parameter +#' value. #' @param status Sidebar status. Valid statuses are defined as follows: #' \itemize{ #' \item \code{primary}: \Sexpr[results=rd, stage=render]{bs4Dash:::rd_color_tag("#007bff")}. @@ -47,10 +48,14 @@ #' #' @export bs4DashSidebar <- function(..., disable = FALSE, width = NULL, - skin = "dark", status = "primary", + skin = NULL, status = "primary", elevation = 4, collapsed = FALSE, minified = TRUE, expandOnHover = TRUE, fixed = TRUE, id = NULL, customArea = NULL) { + # When no skin is specified, sidebar color must match the dashboard skin color + # by default which is set in the dashboardPage function. + skin <- set_sidebar_skin(skin) + if (is.null(id)) id <- "sidebarId" # If we're restoring a bookmarked app, this holds the value of whether or not the # sidebar was collapsed. If this is not the case, the default is whatever the user @@ -151,6 +156,14 @@ bs4DashSidebar <- function(..., disable = FALSE, width = NULL, } +#' @keywords internal +set_sidebar_skin <- function(skin) { + is_dark_skin <- get_parent_args()$dark + if (is.null(skin)) { + skin <- if (!is_dark_skin) "light" else "dark" + } + skin +} #' Toggle sidebar state diff --git a/R/useful-items.R b/R/useful-items.R index c0dd8add..1798da3e 100644 --- a/R/useful-items.R +++ b/R/useful-items.R @@ -2667,8 +2667,6 @@ bs4Sortable <- function(..., width = 12) { #' #' @param data Expect dataframe, tibble or list of shiny tags... See examples. #' @param cardWrap Whether to wrap the table in a card. FALSE by default. -#' @param headTitles Table header names. Must have the same length as the number of -#' \link{bs4TableItem} in \link{bs4TableItems}. Set "" to have an empty title field. #' @param bordered Whether to display border between elements. FALSE by default. #' @param striped Whether to displayed striped in elements. FALSE by default. #' @param width Table width. 12 by default. diff --git a/R/utils.R b/R/utils.R index 464f7af2..256a4423 100644 --- a/R/utils.R +++ b/R/utils.R @@ -742,3 +742,11 @@ app_container <- function(url, deps = FALSE) { } } } + +# Get parent function arguments +get_parent_args <- function() { + cl <- sys.call(-3) + f <- get(as.character(cl[[1]]), mode = "function", sys.frame(-2)) + cl <- match.call(definition = f, call = cl) + as.list(cl)[-1] +} diff --git a/_pkgdown.yml b/_pkgdown.yml index f8641995..94b212f8 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -252,7 +252,7 @@ articles: news: releases: - - text: "bs4Dash 2.3.0" + - text: "bs4Dash 2.4.0.9000" - text: "bs4Dash 2.2.1" - text: "bs4Dash 2.1.0" - text: "bs4Dash 2.0.3" diff --git a/cran-comments.md b/cran-comments.md index 9eca9ef6..5c2e2fdb 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,5 +1,5 @@ ## Test environments -* local OS X install, R 4.1.3. +* local OS X install, R 4.3.1. * RStudio Server on linux Ubuntu 20.04.3 LTS R 4.2.1. * `rhub::check_for_cran`. * Github actions. @@ -9,5 +9,4 @@ There were no ERRORs or WARNINGs or NOTEs. ## Note -I remove the svg file from /man and replaced it by a smaller png, which fixes the NOTES -on https://cran.r-project.org/web/checks/check_results_bs4Dash.html. +This release will fixe the NOTES on https://cran.r-project.org/web/checks/check_results_bs4Dash.html. diff --git a/inst/bs4Dash-2.3.0/bs4Dash.min.js b/inst/bs4Dash-2.3.0/bs4Dash.min.js deleted file mode 100644 index 7b2c7bda..00000000 --- a/inst/bs4Dash-2.3.0/bs4Dash.min.js +++ /dev/null @@ -1,2 +0,0 @@ -var accordionBinding=new Shiny.InputBinding;$.extend(accordionBinding,{find:function(a){return $(a).find(".accordion")},getValue:function(a){var e=$(a).children(".card.active").index()+1;if(0!==e)return e},setValue:function(a,e){$(a).children(".card.active").removeClass("active"),$(a).children().eq(e-1).addClass("active"),$(a).children().eq(e-1).find('[data-toggle="collapse"]').click(),$(a).trigger("change")},receiveMessage:function(a,e){this.setValue(a,e)},subscribe:function(a,e){$(a).on("change",(function(a){e()})),$(a).find('[data-toggle="collapse"]').on("click",(function(t){$(this).closest(".card").hasClass("active")||$(a).children(".card.active").removeClass("active"),$(this).closest(".card").addClass("active"),e()}))},unsubscribe:function(a){$(a).off(".accordionBinding")}}),Shiny.inputBindings.register(accordionBinding,"accordion-input");const validStatuses=["primary","secondary","success","info","warning","danger"],validStatusesPlus=["dark","white","lightblue","navy","orange","fuchsia","purple","indigo","gray","gray-dark","pink","maroon","teal","lime","olive","green","yellow","red","blue"];var cardBinding=new Shiny.InputBinding;$.extend(cardBinding,{find:function(a){return $(a).find(".card.bs4Dash")},getValue:function(a){var e=$(a).parent().find("script[data-for='"+a.id+"']");e=JSON.parse(e.html());var t,i=$(a).hasClass("collapsed-card"),n=$(a).css("display"),s=$(a).hasClass("maximized-card");return t="none"!==n,s?$(a).find("[data-card-widget = 'collapse']").hide():$(a).find("[data-card-widget = 'collapse']").show(),{collapsible:e.collapsible,collapsed:i,closable:e.closable,visible:t,maximizable:e.maximizable,maximized:s,status:e.status,solidHeader:e.solidHeader,background:e.background,width:e.width,height:e.height}},_updateWidth:function(a,e,t){$(a).parent().toggleClass("col-sm-"+e),$(a).parent().addClass("col-sm-"+t),$(a).trigger("resize")},setValue:function(a,e){var t=$(a).parent().find("script[data-for='"+a.id+"']");if(t=JSON.parse(t.html()),"update"===e.action){var i=$(a).hasClass("user-card"),n=$(a).hasClass("social-card");if(e.options.hasOwnProperty("title")&&e.options.title!==t.title){var s;s="string"!=typeof e.options.title?$.parseHTML(e.options.title[0]):$.parseHTML(e.options.title);var r=$(a).find(".card-tools");n?$(a).find(".user-block").replaceWith($(s)):i?("string"==typeof e.options.title?(s=[s[0],s[2]],$(a).removeClass("widget-user-2").addClass("widget-user"),$(a).find(".widget-user-header").replaceWith($(s[0])),$(s[1]).insertAfter($(a).find(".widget-user-header"))):($(a).removeClass("widget-user").addClass("widget-user-2"),$(a).find(".widget-user-image").remove(),$(a).find(".widget-user-header").replaceWith($(s)),null!==e.options.status&&(e.options.gradient?$(a).find(".widget-user-header").addClass("bg-gradient-",c):$(a).find(".widget-user-header").addClass("bg-",c))),$(a).find(".widget-user-header").prepend($(r))):($(s).hasClass("card-title")||$(s).addClass("card-title"),$(a).find(".card-title").replaceWith($(s))),t.title=e.options.title}if(e.options.hasOwnProperty("collapsible")&&e.options.collapsible!==t.collapsible&&(e.options.collapsible?0===$(a).find('[data-card-widget = "collapse"]').length&&($(a).find(".card-tools.float-right").prepend($('')),t.collapsible=!0):($(a).find('[data-card-widget = "collapse"]').remove(),t.collapsible=!1)),e.options.hasOwnProperty("closable")&&e.options.closable!==t.closable&&(e.options.closable?0===$(a).find('[data-card-widget = "remove"]').length&&(0===$(a).find('[data-card-widget = "maximize"]').length?$(a).find(".card-tools.float-right").append($('')):$('').insertBefore($(a).find('[data-card-widget = "maximize"]')),t.closable=!0):($(a).find('[data-card-widget = "remove"]').remove(),t.closable=!1)),e.options.hasOwnProperty("maximizable")&&e.options.maximizable!==t.maximizable&&(e.options.maximizable?0===$(a).find('[data-card-widget = "maximize"]').length&&($(a).find(".card-tools.float-right").append($('')),t.maximizable=!0):($(a).find('[data-card-widget = "maximize"]').remove(),t.maximizable=!1)),e.options.hasOwnProperty("solidHeader")&&!n&&!i)if(e.options.solidHeader!==t.solidHeader&&$(a).hasClass("card-outline"))$(a).removeClass("card-outline"),t.solidHeader=!0;else if($(a).hasClass("card-outline")||e.options.solidHeader){if($(a).hasClass("card-outline")){o=t.status||e.options.status;e.options.background&&o?($(a).removeClass("card-outline"),t.solidHeader=!0):t.background&&o&&($(a).removeClass("card-outline"),t.solidHeader=!1)}}else{var o=t.status||e.options.status;e.options.background&&o&&(null!==e.options.background||t.background&&o)||($(a).addClass("card-outline"),t.solidHeader=!1)}if(e.options.hasOwnProperty("status")&&!n&&e.options.status!==t.status){var d,l,c;if(null===e.options.status&&null!==t.status){if(i||$(a).removeClass("card-"+t.status),$(a).hasClass("card-outline")&&!i&&$(a).addClass("card-outline"),e.options.background){var u=e.options.background;validStatusesPlus.indexOf(u)>-1?$(a).find(".btn-tool").addClass("bg-"+u):validStatuses.indexOf(u)>-1&&$(a).find(".btn-tool").addClass("btn-"+u)}}else e.options.status&&(i?(l="bg-",e.options.gradient&&(l+="gradient-"),l+=e.options.status,$(a).find(".widget-user-header").addClass(l)):(l="card-"+e.options.status,$(a).addClass(l)),t.status&&(i?(d="bg-",t.gradient&&(d+="gradient-"),d+=t.status,$(a).find(".widget-user-header").removeClass(d)):(d="card-"+t.status,$(a).removeClass(d))),$(a).hasClass("card-outline")&&!i||(validStatusesPlus.indexOf(e.options.status)>-1?$(a).find(".btn-tool").addClass("bg-"+e.options.status):validStatuses.indexOf(e.options.status)>-1&&$(a).find(".btn-tool").addClass("btn-"+e.options.status)));(t.status||t.background)&&(t.status?c=t.status:t.background&&(c=t.background),validStatusesPlus.indexOf(c)>-1?$(a).find(".btn-tool").removeClass("bg-"+c):validStatuses.indexOf(c)>-1&&$(a).find(".btn-tool").removeClass("btn-"+c)),t.status=e.options.status}if(e.options.hasOwnProperty("background")&&e.options.background!==t.background){var b="bg-";if(newBgClass=b,t.background){if(t.gradient&&(b+="gradient-"),b+=t.background,i&&!t.status&&!e.options.status){var h=$(a).find(".widget-user-header");$(h).removeClass(b)}$(a).removeClass(b)}if(e.options.background){if((t.gradient||e.options.gradient)&&(newBgClass+="gradient-"),newBgClass+=e.options.background,i&&!t.status&&!e.options.status){h=$(a).find(".widget-user-header");$(h).addClass(newBgClass)}$(a).addClass(newBgClass)}t.gradient!==e.options.gradient&&void 0!==e.options.gradient&&(t.gradient=e.options.gradient),t.background=e.options.background}e.options.hasOwnProperty("width")&&e.options.width!==t.width&&(this._updateWidth(a,t.width,e.options.width),t.width=e.options.width),e.options.hasOwnProperty("height")&&e.options.height!==t.height&&(null===e.options.height?$(a).find(".card-body").css("height",""):$(a).find(".card-body").css("height",e.options.height),t.height=e.options.height),$(a).parent().find("script[data-for='"+a.id+"']").replaceWith('\"\n );\n } else {\n if (value != \"restore\") {\n if ($(el).css(\"display\") != \"none\") {\n $(el).CardWidget(value);\n }\n } else {\n $(el).show();\n // this is needed so that the last event handler is considered\n // in the subscribe method.\n $(el).trigger(\"shown\");\n }\n }\n },\n receiveMessage: function(el, data) {\n this.setValue(el, data);\n $(el).trigger(\"change\");\n },\n\n subscribe: function(el, callback) {\n $(el).on(\"expanded.lte.cardwidget collapsed.lte.cardwidget\", function(e) {\n // set a delay so that SHiny get the input value when the collapse animation\n // is finished.\n setTimeout(function() {\n callback();\n }, 500);\n });\n\n $(el).on(\"maximized.lte.cardwidget minimized.lte.cardwidget\", function(e) {\n callback();\n });\n\n $(el).on(\"removed.lte.cardwidget\", function(e) {\n setTimeout(function() {\n callback();\n }, 500);\n });\n // we need to split removed and shown event since shown is immediate whereas close\n // takes some time\n $(el).on(\"shown.cardBinding\", function(e) {\n callback();\n });\n\n // handle change event triggered in the setValue method\n $(el).on(\"change.cardBinding\", function(event) {\n setTimeout(function() {\n callback();\n }, 500);\n });\n },\n\n unsubscribe: function(el) {\n $(el).off(\".cardBinding\");\n }\n});\n\nShiny.inputBindings.register(cardBinding);\n\n// Card sidebar input binding\nvar cardSidebarBinding = new Shiny.InputBinding();\n$.extend(cardSidebarBinding, {\n initialize: function(el) {\n // erase default to avoid seeing moving sidebars on initialization\n $(\".direct-chat-contacts, .direct-chat-messages\").css({\n transition: \"transform .0s ease-in-out\"\n });\n\n var background = $(el).attr(\"data-background\")\n ? $(el).attr(\"data-background\")\n : \"#343a40\";\n var width = $(el).attr(\"data-width\")\n ? parseInt($(el).attr(\"data-width\"))\n : 100;\n var closeTranslationRate = (100 * 100) / width;\n var contacts = $(el)\n .closest(\".direct-chat\")\n .find(\".direct-chat-contacts\");\n\n // apply width and background\n $(contacts).css({\n background: `${background}`,\n width: `${width}%`\n });\n\n // If start open, apply openTranslationRate else apply closeTranslationRate ...\n if ($(el).attr(\"data-start-open\") === \"true\") {\n var openTranslationRate = closeTranslationRate - 100;\n $(contacts).css({ transform: `translate(${openTranslationRate}%, 0)` });\n } else {\n $(contacts).css({ transform: `translate(${closeTranslationRate}%, 0)` });\n }\n\n // Restore for better transitions\n setTimeout(function() {\n $(\".direct-chat-contacts, .direct-chat-messages\").css({\n transition: \"transform .5s ease-in-out\"\n });\n }, 300);\n \n // Easyclose feature\n if ($(el).attr(\"data-easy-close\") === \"true\") {\n $(document).mouseup(function(e) {\n var container = $(\".direct-chat-contacts\");\n var openContainer = $(\".direct-chat-contacts-open\");\n // if the target of the click isn't the container nor a descendant of the container and also not if the filter symbol was clicke d\n if (!container.is(e.target) && \n container.has(e.target).length === 0 && \n $(e.target).parents('.card-tools').length !== 1) {\n openContainer\n .find(\"[data-widget='chat-pane-toggle']\")\n .click();\n }\n }); \n }\n },\n\n find: function(scope) {\n return $(scope).find('[data-widget=\"chat-pane-toggle\"]');\n },\n\n // Given the DOM element for the input, return the value\n getValue: function(el) {\n var cardWrapper = $(el).closest(\".card\");\n return $(cardWrapper).hasClass(\"direct-chat-contacts-open\");\n },\n\n // see updatebs4Card\n receiveMessage: function(el, data) {\n // In theory, adminLTE3 has a builtin function\n // we could use $(el).DirectChat('toggle');\n // However, it does not update the related input.\n // The toggled.lte.directchat event seems to be broken.\n $(el).trigger(\"click\");\n $(el).trigger(\"shown\");\n },\n\n subscribe: function(el, callback) {\n var self = this;\n $(el).on(\"click\", function(e) {\n var width = $(el).attr(\"data-width\")\n ? parseInt($(el).attr(\"data-width\"))\n : 100;\n var closeTranslationRate = (100 * 100) / width;\n var openTranslationRate = closeTranslationRate - 100;\n // set a delay so that Shiny get the input value when the collapse animation\n // is finished.\n var target = e.currentTarget;\n setTimeout(function(e = target) {\n // apply correct translation rate depending on current state\n var contacts = $(e)\n .closest(\".direct-chat\")\n .find(\".direct-chat-contacts\");\n if (self.getValue(el)) {\n $(contacts).css({\n transform: `translate(${openTranslationRate}%, 0)`\n });\n } else {\n $(contacts).css({\n transform: `translate(${closeTranslationRate}%, 0)`\n });\n }\n callback();\n }, 10);\n });\n },\n\n unsubscribe: function(el) {\n $(el).off(\".cardSidebarBinding\");\n }\n});\n\nShiny.inputBindings.register(cardSidebarBinding);\n","$(function() {\n \n // required to show a toast when the controlbar is pinned \n // for the first time. Show once since it may be annoying ...\n var showToast = true;\n const controlbarToast = () => {\n if (showToast) {\n $(document).Toasts('create', {\n title: 'Controlbar is pinned',\n close: false,\n autohide: true,\n delay: 2000\n });\n showToast = false; \n }\n };\n\n // This prevent box content from going outside their container \n // when the control-bar is on push mode\n $(\"#controlbar-toggle\").on(\"click\",\n function() {\n if ($(\"body\").hasClass(\"control-sidebar-push-slide\")) {\n $(window).trigger(\"resize\"); \n }\n });\n \n \n // The code below hande the click out of the right control bar\n $(window).click(function(e) { \n // There is a potential conflict. This function detect any click outside\n // the controlbar and close if if it is not pinned. Yet, if we click on an action // button controlling the controlbar state (see updatebs4Controlbar), it is also outside the controlbar so the toggle event will be triggered twice. The controlbar will never close as shown in https://github.com/RinteRface/bs4Dash/issues/110. Below we make sure to leave the function as soon as a click on a button holding the class action button. This is not really a fix but a reasonable workaround.\n var isActionButton = $(e.target).hasClass(\"action-button\");\n if (isActionButton) return null;\n \n if($(\"aside.control-sidebar\").find(e.target).length === 0) {\n var pinned = $(\".control-sidebar\").attr(\"data-pin\");\n if (pinned === \"false\" || pinned === undefined) {\n $(\"body\").removeClass(\"control-sidebar-slide-open\"); \n // don't forget to refresh the input binding\n $(\"#controlbar-toggle\").trigger('collapsed.lte.controlsidebar');\n }\n } \n });\n \n // handle the pin button: toggle data-pin state\n $(\"#controlbarPin\").on('click', function() {\n var $pinIcon = $(this).children();\n $pinIcon.toggleClass(\"fa-rotate-90 fa-lg\");\n \n $(\".control-sidebar\").attr(\"data-pin\",\n $(\".control-sidebar\").attr(\"data-pin\") == \"false\" ? \"true\" : \"false\");\n // toggle right sidebar control depending on the datapin\n if ($(\".control-sidebar\").attr(\"data-pin\") === \"true\") {\n $pinIcon.css(\"color\", \"#007bff\");\n $(\"#controlbar-toggle\").addClass(\"disabled\");\n controlbarToast();\n } else {\n $(\"#controlbar-toggle\").removeClass(\"disabled\");\n $pinIcon.css(\"color\", \"\");\n }\n });\n\n\nvar init = true;\n\n // Input binding\n var controlbarBinding = new Shiny.InputBinding();\n \n $.extend(controlbarBinding, {\n \n find: function(scope) {\n return $(scope).find(\".control-sidebar\");\n },\n \n // Given the DOM element for the input, return the value\n getValue: function(el) {\n // Handles the pin \n var controlbarOpen = $(\"body\").hasClass(\"control-sidebar-slide-open\");\n var pinned = $(el).attr(\"data-pin\") === \"true\";\n if (controlbarOpen && pinned && init) {\n $(\"#controlbar-toggle\").addClass(\"disabled\");\n $(\"#controlbarPin\")\n .children()\n .css(\"color\", \"#007bff\");\n controlbarToast();\n init = false;\n }\n \n // this handles the case where the controlbar is not collapsed at start\n var controlbarCollapsed = $(el).attr('data-collapsed');\n if (controlbarCollapsed === \"false\") {\n $(\"#controlbar-toggle\").ControlSidebar('toggle');\n $(el).attr('data-collapsed', \"true\");\n return true;\n } else {\n return $(\"body\").hasClass(\"control-sidebar-slide-open\");\n }\n },\n // see updatebs4Controlbar\n receiveMessage: function(el, data) {\n $(\"#controlbar-toggle\").ControlSidebar('toggle');\n },\n \n subscribe: function(el, callback) {\n $(\"#controlbar-toggle\").on(\"collapsed.lte.controlsidebar expanded.lte.controlsidebar\", function(e) {\n $(el).trigger('shown');\n // add a delay so that Shiny get the input value \n // after the AdminLTE3 animation is finished!\n setTimeout(\n function() {\n callback();\n }, 10);\n });\n },\n \n unsubscribe: function(el) {\n $(el).off(\".controlbarBinding\");\n }\n });\n \n Shiny.inputBindings.register(controlbarBinding, \"bs4Dash.controlbarBinding\");\n \n // handle controlbar overlay\n var controlbarOverlay = $('.control-sidebar').attr('data-overlay');\n if (controlbarOverlay === \"false\") {\n $('body').addClass('control-sidebar-push-slide');\n }\n\n});","$(function () {\n // handle tooltip from the server side\n Shiny.addCustomMessageHandler('create-tooltip', function (message) {\n var tooltipTarget;\n if (message.id) {\n tooltipTarget = '#' + message.id;\n } else {\n if (message.selector) {\n tooltipTarget = message.selector;\n }\n }\n $(tooltipTarget)\n .addClass('has-tooltip')\n .tooltip(message.options);\n console.log(`'Tooltip created for ${tooltipTarget}'`);\n });\n\n Shiny.addCustomMessageHandler('remove-tooltip', function (message) {\n var tooltipTarget = '#' + message;\n\n // only destroys if popover exists\n if ($(tooltipTarget).hasClass('has-tooltip')) {\n $(tooltipTarget)\n .removeClass('has-tooltip')\n .tooltip('dispose');\n console.log(`'Tooltip destroyed for ${tooltipTarget}'`);\n }\n });\n\n // handle popover from the server side\n Shiny.addCustomMessageHandler('create-popover', function (message) {\n var popoverTarget;\n if (message.id) {\n popoverTarget = '#' + message.id;\n } else {\n if (message.selector) {\n popoverTarget = message.selector;\n }\n }\n // indicate target has popover. This is for removePopover to know\n // whether the popover exists\n $(popoverTarget)\n .addClass('has-popover')\n .popover(message.options);\n console.log(`'Popover created for ${popoverTarget}'`);\n });\n\n\n Shiny.addCustomMessageHandler('remove-popover', function (message) {\n var popoverTarget = '#' + message;\n\n // only destroys if popover exists\n if ($(popoverTarget).hasClass('has-popover')) {\n $(popoverTarget)\n .removeClass('has-popover')\n .popover('dispose');\n console.log(`'Popover destroyed for ${popoverTarget}'`);\n }\n });\n\n\n // handle builtin toasts\n Shiny.addCustomMessageHandler('toast', function (message) {\n $(document).Toasts('create', message);\n });\n\n // Create an alert\n Shiny.addCustomMessageHandler('create-alert', function (message) {\n // setup target\n var alertTarget;\n if (message.id) {\n alertTarget = `#${message.id}`;\n } else {\n if (message.selector) {\n alertTarget = message.selector;\n }\n }\n\n // build the tag from options\n var config = message.options, alertCl, alertTag, iconType, closeButton, titleTag, contentTag;\n alertCl = 'alert alert-dismissible';\n if (config.status !== undefined) {\n alertCl = `${alertCl} alert-${config.status}`;\n }\n if (config.elevation !== undefined) {\n alertCl = `${alertCl} elevation-${config.elevation}`;\n }\n\n switch (config.status) {\n case 'primary': iconType = 'info';\n break;\n case 'danger': iconType = 'ban';\n break;\n case 'info': iconType = 'info';\n break;\n case 'warning': iconType = 'warning';\n break;\n case 'success': iconType = 'check';\n break;\n default: console.warn(`${config.status} does not belong to allowed statuses!`)\n }\n\n if (config.closable) {\n closeButton = ''\n }\n\n titleTag = `
`\n contentTag = config.content;\n\n alertTag = `
\n ${closeButton}${titleTag}${contentTag}\n
`\n if (config.width !== undefined) {\n alertTag = `
${alertTag}
`\n }\n\n // add it to the DOM if no existing alert is found in the anchor\n if ($(`#${message.id}-alert`).length === 0) {\n $(alertTarget).append(alertTag);\n Shiny.setInputValue(message.id, true, { priority: 'event' });\n\n // add events only after element is inserted\n\n // callback -> give ability to perform more actions on the Shiny side\n // once the alert is closed\n $(`#${message.id}-alert`).on('closed.bs.alert', function () {\n Shiny.setInputValue(message.id, false, { priority: 'event' });\n });\n // Clicking on close button does not trigger any event.\n // Trigger the closed.bs.alert event.\n $('[data-dismiss=\"alert\"]').on('click', function () {\n var alertId = $(this).parent.attr('id');\n $(`#${alertId}.`).trigger('closed.bs.alert');\n });\n\n } else {\n console.warn(`${alertTarget} already has an alert!`);\n }\n });\n\n\n Shiny.addCustomMessageHandler('close-alert', function (message) {\n // only closes if element exists\n if ($(`#${message}-alert`).length > 0) {\n $(`#${message}-alert`).alert('close');\n } else {\n console.warn('Nothing to delete!');\n }\n });\n});","// When document is ready, if there is a sidebar menu with no activated tabs,\n// activate the one specified by `data-start-selected`, or if that's not\n// present, the first one.\nvar ensureActivatedTab = function() {\n // get the selected tabs\n var $tablinks = $(\".sidebar-menu a[data-toggle='tab']\");\n\n // If there are no tabs, $startTab.length will be 0.\n var $startTab = $tablinks.filter(\"[data-start-selected='1']\");\n if ($startTab.length === 0) {\n // If no tab starts selected, use the first one, if present\n $startTab = $tablinks.first();\n }\n\n // If there's a `data-start-selected` attribute and we can find a tab with\n // that name, activate it.\n if ($startTab.length !== 0) {\n // This is just in case the user renders the tabs in a renderUI that does not\n // print immediately in the DOM. We need a bit of a delay before telling which\n // tab to show ...\n if ($(\".sidebar-menu\").hasClass(\"bs4Dash-menu-output\")) {\n setTimeout(function() {\n // we need to initialize any treeview elements that were not inserted\n // in the DOM when adminlte was first initialized!\n adminlte.Treeview._jQueryInterface.call($('[data-widget=\"treeview\"]'), 'init');\n\n $startTab.tab(\"show\");\n }, 10);\n } else {\n $startTab.tab(\"show\");\n }\n \n\n // This is indirectly setting the value of the Shiny input by setting\n // an attribute on the html element it is bound to. We cannot use the\n // inputBinding's setValue() method here because this is called too\n // early (before Shiny has fully initialized)\n $(\".sidebarMenuSelectedTabItem\").attr(\n \"data-value\",\n $startTab.attr(\"data-value\")\n );\n }\n};\n\n// This function handles a special case in the AdminLTE sidebar: when there\n// is a sidebar-menu with items, and one of those items has sub-items, and\n// they are used for tab navigation. Normally, if one of the items is\n// selected and then a sub-item is clicked, both the item and sub-item will\n// retain the \"active\" class, so they will both be highlighted. This happens\n// because they're not designed to be used together for tab panels. This\n// code ensures that only one item will have the \"active\" class.\nvar deactivateOtherTabs = function() {\n // Find all tab links under sidebar-menu even if they don't have a\n // tabName (which is why the second selector is necessary)\n var $tablinks = $(\n \".sidebar-menu a[data-toggle='tab'],\" + \".sidebar-menu li.has-treeview > a\"\n );\n\n // If any other items are active, deactivate them\n $tablinks.not($(this)).removeClass(\"active\");\n\n // also manually activate the parent link when the selected item\n // is part of a treeview. For some reason, this is not done by AdminLTE3...\n if ($(this).hasClass(\"treeview-link\")) {\n $(this)\n .parents(\".has-treeview\")\n .children()\n .eq(0)\n .addClass(\"active\");\n }\n\n // Trigger event for the tabItemInputBinding\n var $obj = $(\".sidebarMenuSelectedTabItem\");\n var inputBinding = $obj.data(\"shiny-input-binding\");\n if (typeof inputBinding !== \"undefined\") {\n inputBinding.setValue($obj, $(this).attr(\"data-value\"));\n $obj.trigger(\"change\");\n }\n};\n\n$(function() {\n // Whenever the sidebar finishes a transition (which it does every time it\n // changes from collapsed to expanded and vice versa), trigger resize,\n // so that all outputs are resized.\n $(\".main-sidebar\").on(\n \"webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend\",\n function() {\n $(window).trigger(\"resize\");\n }\n );\n\n $(document).on(\n \"shown.bs.tab\",\n '.sidebar-menu a[data-toggle=\"tab\"]',\n deactivateOtherTabs\n );\n\n ensureActivatedTab();\n\n // Whenever we expand a menuItem (to be expandable, it must have children),\n // update the value for the expandedItem's input binding (this is the\n // tabName of the fist subMenuItem inside the menuItem that is currently\n // expanded)\n $(document).on(\"click\", \".has-treeview\", function() {\n var $menu = $(this);\n // If this menuItem was already open, then clicking on it again,\n // should trigger the \"hidden\" event, so Shiny doesn't worry about\n // it while it's hidden (and vice versa).\n if ($menu.hasClass(\"menu-open\")) $menu.trigger(\"collapsed.lte.treeview\");\n else if ($menu.hasClass(\".has-treeview\"))\n $menu.trigger(\"expanded.lte.treeview\");\n\n // need to set timeout to account for the slideUp/slideDown animation\n var $obj = $(\".sidebar.shiny-bound-input\");\n setTimeout(function() {\n $obj.trigger(\"change\");\n }, 600);\n });\n\n //---------------------------------------------------------------------\n // tabItemInputBinding\n // ------------------------------------------------------------------\n // Based on Shiny.tabItemInputBinding, but customized for tabItems in\n // bs4Dash, which have a slightly different structure.\n var tabItemInputBinding = new Shiny.InputBinding();\n $.extend(tabItemInputBinding, {\n find: function(scope) {\n return $(scope).find(\".sidebarMenuSelectedTabItem\");\n },\n getValue: function(el) {\n var value = $(el).attr(\"data-value\");\n if (value === \"null\") return null;\n return value;\n },\n setValue: function(el, value) {\n var self = this;\n var anchors = $(el)\n .parent(\".sidebar-menu\")\n .find(\"li:not(.treeview)\")\n .children(\"a\");\n anchors.each(function() {\n // eslint-disable-line consistent-return\n if (self._getTabName($(this)) === value) {\n $(this).tab(\"show\");\n // this make sure that treeview items are open when we\n // use the updatebs4TabItems function on the server side\n if ($(this).hasClass(\"treeview-link\")) {\n if (\n !$(this)\n .parents(\".has-treeview\")\n .hasClass(\"menu-open\")\n ) {\n $(this)\n .parents(\".has-treeview\")\n .children()\n .eq(0)\n .trigger(\"click\");\n }\n }\n $(el).attr(\"data-value\", self._getTabName($(this)));\n return false;\n }\n });\n },\n receiveMessage: function(el, data) {\n if (data.hasOwnProperty(\"value\")) this.setValue(el, data.value);\n },\n subscribe: function(el, callback) {\n // This event is triggered by deactivateOtherTabs, which is triggered by\n // shown. The deactivation of other tabs must occur before Shiny gets the\n // input value.\n $(el).on(\"change.tabItemInputBinding\", function() {\n callback();\n });\n },\n unsubscribe: function(el) {\n $(el).off(\".tabItemInputBinding\");\n },\n _getTabName: function(anchor) {\n return anchor.attr(\"data-value\");\n }\n });\n\n Shiny.inputBindings.register(tabItemInputBinding, \"bs4Dash.tabItemInput\");\n\n //---------------------------------------------------------------------\n // sidebarInputBinding\n // ------------------------------------------------------------------\n // similar to controlbarInputBinding\n var sidebarBinding = new Shiny.InputBinding();\n\n $.extend(sidebarBinding, {\n find: function(scope) {\n return $(scope).find(\".main-sidebar\");\n },\n\n // Given the DOM element for the input, return the value\n getValue: function(el) {\n // Warning: we can't look for sidebar-open since this\n // class is only generated on mobile devices\n return !$(\"body\").hasClass(\"sidebar-collapse\");\n },\n\n // see updatebs4Controlbar\n receiveMessage: function(el, data) {\n $(\"[data-widget='pushmenu']\").PushMenu(\"toggle\");\n },\n\n subscribe: function(el, callback) {\n $(\"[data-widget='pushmenu']\").on(\n \"collapsed.lte.pushmenu.sidebarBinding shown.lte.pushmenu.sidebarBinding\",\n function(e) {\n callback();\n }\n );\n },\n\n unsubscribe: function(el) {\n $(el).off(\".sidebarBinding\");\n }\n });\n\n Shiny.inputBindings.register(sidebarBinding, \"bs4Dash.sidebarInput\");\n\n // sidebarmenuExpandedInputBinding\n // ------------------------------------------------------------------\n // This keeps tracks of what menuItem (if any) is expanded\n var sidebarmenuExpandedInputBinding = new Shiny.InputBinding();\n $.extend(sidebarmenuExpandedInputBinding, {\n find: function(scope) {\n // This will also have id=\"sidebarItemExpanded\"\n return $(scope).find(\".sidebar\");\n },\n getValue: function(el) {\n var $open = $(el)\n .find(\"li\")\n .filter(\".menu-open\")\n .find(\"ul\");\n if ($open.length === 1) return $open.attr(\"data-expanded\");\n else return null;\n },\n setValue: function(el, value) {\n // does not work (nothing is printed)\n var $menuItem = $(el).find(\"[data-expanded='\" + value + \"']\");\n // This will trigger actions defined by AdminLTE, as well as actions\n // defined in sidebar.js.\n $menuItem.prev().trigger(\"click\");\n },\n subscribe: function(el, callback) {\n $(el).on(\"change.sidebarmenuExpandedInputBinding\", function() {\n callback();\n });\n },\n unsubscribe: function(el) {\n $(el).off(\".sidebarmenuExpandedInputBinding\");\n }\n });\n Shiny.inputBindings.register(\n sidebarmenuExpandedInputBinding,\n \"bs4Dash.sidebarmenuExpandedInputBinding\"\n );\n\n // handle fixed sidebar\n if ($(\".main-sidebar\").attr(\"data-fixed\") === \"true\") {\n $(\"body\").addClass(\"layout-fixed\");\n //$('body').Layout('fixLayoutHeight');\n }\n});\n","$(function () {\n\n $navbar = $('.main-header.navbar');\n\n // Modify the shiny tabsetpanel binding to follow BS4 rules\n $(document).on('shiny:connected', function (event) {\n Shiny.unbindAll();\n $.extend(Shiny\n .inputBindings\n .bindingNames['shiny.bootstrapTabInput']\n .binding, {\n // do whathever you want to edit existing methods\n getValue: function (el) {\n var anchor = $(el).find('li:not(.dropdown)').children('a.active');\n if (anchor.length === 1)\n return this._getTabName(anchor);\n\n return null;\n }\n });\n Shiny.bindAll();\n });\n\n // footer has fixed layout?\n if ($(\".main-footer\").attr(\"data-fixed\") === \"true\") {\n $(\"body\").addClass(\"layout-footer-fixed\");\n }\n\n // add dropdown-menu-right class to correctly open the dropdown to all \n // navbar rightUi elements\n var navbarRight = $('.navbar-right').find('.dropdown-menu');\n $(navbarRight).each(function () {\n if (!$(this).hasClass('dropdown-menu-right')) {\n $(this).addClass('dropdown-menu-right');\n }\n });\n\n\n // data toggle collapse icon update\n $('.user-block [data-toggle=\"collapse\"]').on('click', function () {\n if ($(this).children('i').hasClass('fa-plus')) {\n $(this).children('i').attr('class', 'fa fa-minus');\n } else {\n $(this).children('i').attr('class', 'fa fa-plus');\n }\n });\n\n // fullscreen toggle\n if ($('body').attr('data-fullscreen') == 1) {\n var fullScreenToggle = `
  • \n \n \n \n
  • `;\n $(fullScreenToggle).insertBefore($('[data-widget=\"control-sidebar\"]').parent());\n }\n\n // slide to top button\n if ($('body').attr('data-scrollToTop') == 1) {\n var $slideToTop = $('
    ');\n\n $slideToTop.html('');\n\n $slideToTop.css({\n position: 'fixed',\n bottom: '20px',\n right: '25px',\n width: '40px',\n height: '40px',\n color: '#eee',\n 'font-size': '',\n 'line-height': '40px',\n 'text-align': 'center',\n 'background-color': '#222d32',\n cursor: 'pointer',\n 'border-radius': '5px',\n 'z-index': '99999',\n opacity: '.7',\n 'display': 'none'\n });\n\n $slideToTop.on('mouseenter', function () {\n $(this).css('opacity', '1');\n });\n\n $slideToTop.on('mouseout', function () {\n $(this).css('opacity', '.7');\n });\n\n $('.wrapper').append($slideToTop);\n\n $(window).scroll(function () {\n if ($(window).scrollTop() >= 150) {\n if (!$($slideToTop).is(':visible')) {\n $($slideToTop).fadeIn(500);\n }\n } else {\n $($slideToTop).fadeOut(500);\n }\n });\n\n $($slideToTop).click(function () {\n $('html, body').animate({\n scrollTop: 0\n }, 500);\n });\n }\n \n // nav item click also triggers scroll to top\n $('.main-sidebar .nav-item').on('click', function () {\n $('html, body').animate({\n scrollTop: 0\n }, 0);\n }); \n\n\n // tooltip/popover toggle\n if ($('body').attr('data-help') == 2 || \n $('body').attr('data-help') == 1) {\n var $help_switch_checkbox = $('', {\n type: 'checkbox',\n id: 'help_switch',\n class: 'custom-control-input'\n }).on('click', function () {\n if ($(this).is(':checked')) {\n $('[data-toggle=\"tooltip\"]').tooltip('enable');\n $('[data-toggle=\"popover\"]').popover({\n trigger: 'hover'\n });\n $('[data-toggle=\"popover\"]').popover('enable');\n } else {\n $('[data-toggle=\"tooltip\"]').tooltip('disable');\n $('[data-toggle=\"popover\"]').popover('disable');\n }\n });\n\n var $help_switch_container = $('
    ', { class: 'custom-control custom-switch mx-2 mt-2' }).append($help_switch_checkbox).append(``);\n \n // insert before $('#controlbar-toggle') whenever possible ...\n if ($('.nav-item #controlbar-toggle')) {\n $help_switch_container.insertBefore($('#controlbar-toggle').parent());\n } else {\n $navbar.append($help_switch_container);\n }\n\n // trigger first click, if necessary\n $(document).on('shiny:connected', function() {\n if ($('body').attr('data-help') == 2 || $('body').attr('data-help') == 1) {\n $help_switch_checkbox.click(); \n // Click again if option is set to FALSE\n if ($('body').attr('data-help') == 1) {\n $help_switch_checkbox.click(); \n }\n }\n }); \n }\n\n // dark mode input\n $(document).one('shiny:connected', function () {\n if ($('body').hasClass('dark-mode')) {\n Shiny.setInputValue('dark_mode', true, { priority: 'event' });\n } else {\n Shiny.setInputValue('dark_mode', false, { priority: 'event' });\n }\n });\n\n\n // Navbar colors\n getNavbarColor = function () {\n for (let color of navbar_all_colors) {\n if ($('.main-header').attr('class').search(color) > -1) {\n return color;\n }\n }\n };\n\n var navbar_dark_skins = [\n 'navbar-primary',\n 'navbar-secondary',\n 'navbar-info',\n 'navbar-success',\n 'navbar-danger',\n 'navbar-indigo',\n 'navbar-purple',\n 'navbar-pink',\n 'navbar-maroon',\n 'navbar-fuchsia',\n 'navbar-navy',\n 'navbar-lightblue',\n 'navbar-lime',\n 'navbar-teal',\n 'navbar-olive',\n 'navbar-gray-dark',\n 'navbar-gray'\n ];\n\n var navbar_light_skins = [\n 'navbar-warning',\n 'navbar-white',\n 'navbar-orange'\n ];\n\n var navbar_all_colors = navbar_dark_skins.concat(navbar_light_skins);\n \n /**\n * Update color theme to navbar tag\n *\n * @param String color Color to apply.\n * @returns void\n */\n updateNavbarTheme = function (color) {\n var $main_header = $('.main-header');\n $main_header.removeClass('navbar-dark').removeClass('navbar-light');\n navbar_all_colors.forEach(function (color) {\n $main_header.removeClass(color);\n });\n\n if (navbar_dark_skins.indexOf(color) > -1) {\n $main_header.addClass('navbar-dark');\n } else {\n $main_header.addClass('navbar-light');\n }\n\n $main_header.addClass(color);\n };\n \n /**\n * Update icon color style based on navbar color.\n *\n * @param String color Current navbar color.\n * @returns void\n */\n updateNavbarIconColor = function(color) {\n var iconThemeColor = navbar_dark_skins.indexOf(color) > -1 ? \"white\" : \"rgba(0,0,0,.5)\";\n $(\".dark-theme-icon\").css(\"color\", iconThemeColor);\n $('[for=\"help_switch\"] i').css(\"color\", iconThemeColor);\n };\n \n // automatic global theme switcher\n if ($('body').attr('data-dark') == 2 || \n $('body').attr('data-dark') == 1) {\n var $dark_mode_checkbox = $('', {\n type: 'checkbox',\n id: 'theme_switch',\n class: 'custom-control-input'\n }).on('click', function () {\n\n // get any selected navbar skin in the navbar themer\n var newNavbarColor;\n // If there is not themer, we keep the navbar current color.\n // Otherwise, we replace it by the new color.\n if ($('.navbar-themer-chip').length > 0) {\n $('.navbar-themer-chip').filter(function () {\n if ($(this).css('border-style') === 'solid') {\n newNavbarColor = 'navbar-' +\n $(this)\n .attr('class')\n .split('elevation-2')[0]\n .trim()\n .replace('bg-', '');\n }\n });\n } else {\n newNavbarColor = getNavbarColor();\n }\n\n if ($(this).is(':checked')) {\n $('body').addClass('dark-mode');\n\n // use updateNavbarTheme to correctly setup the skin as depending\n // on the required color. If no color is chosen, we use gray-dark for dark mode\n if (newNavbarColor === undefined || newNavbarColor === 'navbar-white') {\n newNavbarColor = \"navbar-gray-dark\";\n }\n updateNavbarTheme(newNavbarColor);\n\n // sidebar update \n if ($('.main-sidebar').length > 0) {\n $('.main-sidebar').attr('class', $('.main-sidebar')\n .attr('class')\n .replace('light', 'dark'));\n $('#sidebar-skin').prop(\"checked\", true);\n\n $('.sidebar-themer-icon')\n .removeClass('fa-sun')\n .addClass('fa-moon');\n }\n\n // controlbar update\n if ($('.control-sidebar').length > 0) {\n $('.control-sidebar').attr('class', $('.control-sidebar')\n .attr('class')\n .replace('light', 'dark'));\n $('#controlbar-skin').prop(\"checked\", true);\n\n $('.controlbar-themer-icon')\n .removeClass('fa-sun')\n .addClass('fa-moon');\n }\n\n\n $('.dark-theme-icon')\n .removeClass('fa-sun')\n .addClass('fa-moon');\n\n // refresh shiny input value \n Shiny.setInputValue('dark_mode', true, { priority: 'event' });\n\n } else {\n $('body').removeClass('dark-mode');\n\n // use updateNavbarTheme to correctly setup the skin as depending\n // on the required color. If no color is chosen, we use white for light mode\n if (newNavbarColor === undefined || newNavbarColor === 'navbar-gray-dark') {\n newNavbarColor = \"navbar-white\";\n }\n updateNavbarTheme(newNavbarColor);\n\n // sidebar update\n if ($('.main-sidebar').length > 0) {\n $('.main-sidebar').attr('class', $('.main-sidebar')\n .attr('class')\n .replace('dark', 'light'));\n $('#sidebar-skin').prop(\"checked\", false);\n\n $('.sidebar-themer-icon')\n .removeClass('fa-moon')\n .addClass('fa-sun');\n }\n\n // controlbar update\n if ($('.control-sidebar').length > 0) {\n $('.control-sidebar').attr('class', $('.control-sidebar')\n .attr('class')\n .replace('dark', 'light'));\n $('#controlbar-skin').prop(\"checked\", false);\n\n $('.controlbar-themer-icon')\n .removeClass('fa-moon')\n .addClass('fa-sun');\n }\n\n $('.dark-theme-icon')\n .removeClass('fa-moon')\n .addClass('fa-sun');\n\n // refresh shiny input value \n Shiny.setInputValue('dark_mode', false, { priority: 'event' });\n }\n \n // update navbar icon colors\n updateNavbarIconColor(newNavbarColor);\n });\n\n var $dark_mode_icon = $('body').hasClass('dark-mode') ? '' : '';\n var $dark_mode_container = $('
    ', { class: 'custom-control custom-switch mx-2 mt-2' }).append($dark_mode_checkbox).append(``);\n \n // insert before $('#controlbar-toggle') whenever possible ...\n if ($('.nav-item #controlbar-toggle')) {\n $dark_mode_container.insertBefore($('#controlbar-toggle').parent());\n } else {\n $navbar.append($dark_mode_container);\n }\n \n \n // Trigger dark mode\n if ($('body').attr('data-dark') == 2) {\n $(document).on('shiny:connected', function() {\n $dark_mode_checkbox.click();\n }); \n }\n }\n \n // apply correct navbar class depending on selected color\n if (getNavbarColor() !== undefined) {\n updateNavbarTheme(getNavbarColor());\n updateNavbarIconColor(getNavbarColor());\n }\n \n\n // Themer chips\n\n // Better style on hover\n $('.themer-chip').hover(function () {\n $(this).css({ opacity: 1 }).removeClass('elevation-2').addClass('elevation-4');\n }, function () {\n $(this).css({ opacity: 0.8 }).removeClass('elevation-4').addClass('elevation-2');\n });\n\n // \n $('.navbar-themer-chip').on('click', function () {\n $(this).css({ 'border-color': 'yellow', 'border-style': 'solid' });\n $('.navbar-themer-chip').not(this).css({ 'border-color': '', 'border-style': '' });\n });\n\n $('.accents-themer-chip').on('click', function () {\n $(this).css({ 'border-color': 'yellow', 'border-style': 'solid' });\n $('.accents-themer-chip').not(this).css({ 'border-color': '', 'border-style': '' });\n });\n\n $('.sidebar-themer-chip').on('click', function () {\n $(this).css({ 'border-color': 'yellow', 'border-style': 'solid' });\n $('.sidebar-themer-chip').not(this).css({ 'border-color': '', 'border-style': '' });\n });\n\n\n // Sidebar themer\n\n // detect global sidebar theme and select or not the toggle\n if ($('.main-sidebar').length > 0) {\n if ($('.main-sidebar').attr('class').match('dark')) {\n $('#sidebar-skin').prop(\"checked\", true);\n }\n }\n\n // clicking on dark/light switch changes:\n // - icon style\n // - sidebar class \n $('#sidebar-skin').on('click', function () {\n var sidebarCl;\n if ($(this).is(':checked')) {\n sidebarCl = $('.main-sidebar')\n .attr('class')\n .replace('light', 'dark');\n $('.main-sidebar').attr('class', sidebarCl);\n\n $('.sidebar-themer-icon')\n .removeClass('fa-sun')\n .addClass('fa-moon');\n } else {\n sidebarCl = $('.main-sidebar')\n .attr('class')\n .replace('dark', 'light');\n $('.main-sidebar').attr('class', sidebarCl);\n\n $('.sidebar-themer-icon')\n .removeClass('fa-moon')\n .addClass('fa-sun');\n }\n });\n\n var sidebar_colors = [\n 'bg-primary',\n 'bg-secondary',\n 'bg-info',\n 'bg-success',\n 'bg-danger',\n 'bg-indigo',\n 'bg-purple',\n 'bg-pink',\n 'bg-maroon',\n 'bg-fuchsia',\n 'bg-navy',\n 'bg-lightblue',\n 'bg-lime',\n 'bg-teal',\n 'bg-olive',\n 'bg-gray-dark',\n 'bg-gray',\n 'bg-light',\n 'bg-warning',\n 'bg-white',\n 'bg-orange'\n ];\n\n\n var sidebar_skins = [\n 'sidebar-dark-primary',\n 'sidebar-dark-secondary',\n 'sidebar-dark-info',\n 'sidebar-dark-success',\n 'sidebar-dark-danger',\n 'sidebar-dark-indigo',\n 'sidebar-dark-purple',\n 'sidebar-dark-pink',\n 'sidebar-dark-maroon',\n 'sidebar-dark-fuchsia',\n 'sidebar-dark-navy',\n 'sidebar-dark-lightblue',\n 'sidebar-dark-lime',\n 'sidebar-dark-teal',\n 'sidebar-dark-olive',\n 'sidebar-dark-gray-dark',\n 'sidebar-dark-gray',\n 'sidebar-dark-light',\n 'sidebar-dark-warning',\n 'sidebar-dark-white',\n 'sidebar-dark-orange',\n 'sidebar-light-primary',\n 'sidebar-light-secondary',\n 'sidebar-light-info',\n 'sidebar-light-success',\n 'sidebar-light-danger',\n 'sidebar-light-indigo',\n 'sidebar-light-purple',\n 'sidebar-light-pink',\n 'sidebar-light-maroon',\n 'sidebar-light-fuchsia',\n 'sidebar-light-navy',\n 'sidebar-light-lightblue',\n 'sidebar-light-lime',\n 'sidebar-light-teal',\n 'sidebar-light-olive',\n 'sidebar-light-gray-dark',\n 'sidebar-light-gray',\n 'sidebar-light-light',\n 'sidebar-light-warning',\n 'sidebar-light-white',\n 'sidebar-light-orange'\n ];\n\n\n updateSidebarTheme = function (color) {\n var sidebarCl;\n if ($('#sidebar-skin').is(':checked')) {\n sidebarCl = 'sidebar-dark-';\n } else {\n sidebarCl = 'sidebar-light-';\n }\n\n var sidebar_class = sidebarCl + color.replace('bg-', '');\n var $sidebar = $('.main-sidebar');\n sidebar_skins.forEach(function (skin) {\n $sidebar.removeClass(skin);\n });\n\n $sidebar.addClass(sidebar_class);\n };\n\n\n // Accents themer\n var accent_colors = [\n 'accent-primary',\n 'accent-secondary',\n 'accent-info',\n 'accent-success',\n 'accent-danger',\n 'accent-indigo',\n 'accent-purple',\n 'accent-pink',\n 'accent-maroon',\n 'accent-fuchsia',\n 'accent-navy',\n 'accent-lightblue',\n 'accent-lime',\n 'accent-teal',\n 'accent-olive',\n 'accent-gray-dark',\n 'accent-gray',\n 'accent-light',\n 'accent-warning',\n 'accent-white',\n 'accent-orange'\n ];\n\n\n updateAccentsTheme = function (color) {\n var accent_class = color;\n var $body = $('body');\n accent_colors.forEach(function (skin) {\n $body.removeClass(skin);\n });\n\n $body.addClass(accent_class);\n };\n\n\n // Controlbar themer\n\n // detect global controlbar theme and select or not the toggle\n if ($('.control-sidebar').length > 0) {\n if ($('.control-sidebar').attr('class').match('dark')) {\n $('#controlbar-skin').prop(\"checked\", true);\n }\n }\n\n // clicking on dark/light switch changes:\n // - icon style\n // - sidebar class \n $('#controlbar-skin').on('click', function () {\n var controlbarCl;\n if ($(this).is(':checked')) {\n controlbarCl = $('.control-sidebar')\n .attr('class')\n .replace('light', 'dark');\n $('.control-sidebar').attr('class', controlbarCl);\n\n $('.controlbar-themer-icon')\n .removeClass('fa-sun')\n .addClass('fa-moon');\n } else {\n controlbarCl = $('.control-sidebar')\n .attr('class')\n .replace('dark', 'light');\n $('.control-sidebar').attr('class', controlbarCl);\n\n $('.controlbar-themer-icon')\n .removeClass('fa-moon')\n .addClass('fa-sun');\n }\n });\n\n});","$(function () {\n // hide the right sidebar toggle \n // if no right sidebar is specified\n noControlbar = ($(\".control-sidebar\").length === 0);\n if (noControlbar) {\n $(\"#controlbar-toggle\").hide();\n }\n\n // hide the right sidebar toggle if the controlbar is disable\n disableControlbar = ($(\".control-sidebar\").attr(\"data-show\"));\n if (!disableControlbar) {\n $(\"#controlbar-toggle\").hide();\n }\n\n // controlbar slide\n controlbarSlide = ($(\".control-sidebar\").attr(\"data-slide\"));\n if (controlbarSlide) {\n $(\"#controlbar-toggle\").attr('data-controlsidebar-slide', controlbarSlide);\n }\n\n // when the sidebar is disabled, hide the sidebar toggle\n disableSidebar = ($(\".main-sidebar\").length === 0);\n if (disableSidebar) {\n $(\".nav-item > a[data-widget='pushmenu']\").css(\"visibility\", \"hidden\");\n }\n\n // handle fixed navbar\n if ($(\".navbar\").attr(\"data-fixed\") === \"true\") {\n $(\"body\").addClass(\"layout-navbar-fixed\");\n }\n\n});","var menuOutputBinding = new Shiny.OutputBinding();\n$.extend(menuOutputBinding, {\n find: function (scope) {\n return $(scope).find('.bs4Dash-menu-output');\n },\n onValueError: function (el, err) {\n Shiny.unbindAll(el);\n this.renderError(el, err);\n },\n renderValue: function (el, data) {\n Shiny.unbindAll(el);\n\n var html;\n var dependencies = [];\n if (data === null) {\n return;\n } else if (typeof (data) === 'string') {\n html = data;\n } else if (typeof (data) === 'object') {\n html = data.html;\n dependencies = data.deps;\n }\n\n var $html = $($.parseHTML(html));\n\n // Convert the inner contents to HTML, and pass to renderHtml\n Shiny.renderHtml($html.html(), el, dependencies);\n\n // Extract class of wrapper, and add them to the wrapper element\n el.className = 'bs4Dash-menu-output shiny-bound-output ' +\n $html.attr('class');\n \n // need this to activate adminLTE3 plugin for treeview \n $(el)\n .attr(\"data-widget\", \"treeview\")\n .attr(\"role\", \"menu\")\n .attr(\"data-accordion\", \"true\");\n\n Shiny.initializeInputs(el);\n Shiny.bindAll(el);\n if ($(el).hasClass(\"sidebar-menu\")) ensureActivatedTab(); // eslint-disable-line\n }\n});\nShiny.outputBindings.register(menuOutputBinding, \"bs4Dash.menuOutputBinding\");","$( document ).ready(function() {\n \n function findActivePage() {\n return $('ul.pagination').find('li.active');\n };\n \n function toggleNavigationItems(activeItem) {\n // hide pagination previous if we are at the first item\n if ($(activeItem).prev().find('a').hasClass('pagination-previous')) {\n $('.pagination-previous').parent('li').css('display', 'none');\n } else {\n $('.pagination-previous').parent('li').css('display', 'block');\n }\n \n // hide pagination next if we are at the last item\n if ($(activeItem).next().find('a').hasClass('pagination-next')) {\n $('.pagination-next').parent('li').css('display', 'none');\n } else {\n $('.pagination-next').parent('li').css('display', 'block');\n }\n }\n \n // Must run at start\n toggleNavigationItems(findActivePage());\n \n // Toggle active item button state\n $('ul.pagination a:not(.disabled, .pagination-previous, .pagination-next)')\n .on('click', function() {\n var activeItem = findActivePage();\n $(activeItem).removeClass('active');\n $(this).parent().addClass('active');\n activeItem = $(this).parent(); // store new active item\n // Also run dynamically\n toggleNavigationItems(activeItem);\n });\n \n // Previous click\n $('ul.pagination .pagination-previous').on('click', function() {\n var activeItem = findActivePage();\n \n var previousSibling = $(activeItem).prev();\n // jump back when we find a disabled sibling\n while ($(previousSibling).hasClass('disabled')) {\n previousSibling = $(previousSibling).prev();\n }\n // Only if active item has a previous sibling that is not \n // the pagination-previous itself.\n if ($(previousSibling).find('a').hasClass('pagination-previous') == false) {\n $(activeItem).removeClass('active');\n $(previousSibling).find('a').click(); \n }\n });\n \n // Next click\n $('ul.pagination .pagination-next').on('click', function() {\n var activeItem = findActivePage();\n \n var nextSibling = $(activeItem).next();\n // jump back when we find a disabled sibling\n while ($(nextSibling).hasClass('disabled')) {\n nextSibling = $(nextSibling).next();\n }\n // Only if active item has a previous sibling that is not \n // the pagination-previous itself.\n if ($(nextSibling).find('a').hasClass('pagination-next') == false) {\n $(activeItem).removeClass('active');\n $(nextSibling).find('a').click(); \n }\n });\n \n var paginationBinding = new Shiny.InputBinding();\n $.extend(paginationBinding, {\n \n initialize: function(el) {\n \n },\n \n find: function (scope) {\n return $(scope).find('ul.pagination');\n },\n \n // Given the DOM element for the input, return the value\n getValue: function (el) {\n return $(el).find('li.active a').attr('data-value');\n },\n \n setValue: function(el, value) {\n \n },\n // internal\n _disableTab: function(el, value) {\n $(el)\n .find('a[data-value=\"' + value + '\"]')\n .parent()\n .removeClass('active')\n .addClass('disabled')\n .attr('tabindex', '-1')\n },\n // see updatePagination\n receiveMessage: function (el, data) {\n // Activate new element\n if (data.hasOwnProperty('selected')) {\n // Disable active element\n $(el)\n .find('.active')\n .removeClass('active');\n // Activate new element\n var selectedItem = $(el)\n .find('a[data-value=\"' + data.selected + '\"]');\n // If the element was disabled before\n if ($(selectedItem).parent('li').hasClass('disabled')) {\n $(selectedItem)\n .parent('li')\n .removeClass('disabled')\n .removeAttr('tabindex');\n }\n $(selectedItem).click();\n }\n \n // Disable elements\n if (data.hasOwnProperty('disabled')) {\n // loop over all elements\n if (typeof data.disabled == 'string') {\n this._disableTab(el, data.disabled)\n } else {\n for (i of data.disabled) {\n // disable element \n this._disableTab(el, i)\n }\n }\n \n // Activate next or previous not disabled li\n var hasActiveItem = $(el).find('li.active').length\n if (hasActiveItem == 0) {\n // Activate first element found (maybe discussed ...)\n var newActive = $(el)\n .find('a:not(.pagination-previous, .pagination-next)')\n .parent('li:not(.disabled)');\n if (newActive.length > 0) {\n $(newActive[0]).find('a').click();\n }\n }\n }\n \n // Trigger the callback to update the input value \n // on the server side.\n $(el).trigger('change');\n },\n \n subscribe: function (el, callback) {\n $(el).find('a').on('click', function() {\n callback();\n });\n \n // Necessry for updatePagination\n $(el).on('change', function() {\n callback();\n })\n },\n \n unsubscribe: function (el) {\n $(el).off('.paginationBinding');\n }\n });\n \n Shiny.inputBindings.register(paginationBinding);\n});","var bootstrapTabInputBinding = new Shiny.InputBinding();\n$.extend(bootstrapTabInputBinding, {\n find: function(scope) {\n return $(scope).find('ul.nav.shiny-tab-input');\n },\n getValue: function(el) {\n // for Bootstrap 4, li element does not hold the active class anymore!\n // We need to look at a.active.\n var anchor = $(el).find('li:not(.dropdown)').children('a.active');\n if (anchor.length === 1)\n return this._getTabName(anchor);\n\n return null;\n },\n setValue: function(el, value) {\n let self = this;\n let success = false;\n if (value) {\n let anchors = $(el).find('li:not(.dropdown)').children('a');\n anchors.each(function() {\n if (self._getTabName($(this)) === value) {\n $(this).tab('show');\n success = true;\n return false; // Break out of each()\n }\n return true;\n });\n }\n if (!success) {\n // This is to handle the case where nothing is selected, e.g. the last tab\n // was removed using removeTab.\n $(el).trigger(\"change\");\n }\n },\n getState: function(el) {\n return { value: this.getValue(el) };\n },\n receiveMessage: function(el, data) {\n if (data.hasOwnProperty('value'))\n this.setValue(el, data.value);\n },\n subscribe: function(el, callback) {\n $(el).on('change shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding', function(event) {\n callback();\n });\n },\n unsubscribe: function(el) {\n $(el).off('.bootstrapTabInputBinding');\n },\n _getTabName: function(anchor) {\n return anchor.attr('data-value') || anchor.text();\n }\n});\n\nShiny.inputBindings.register(bootstrapTabInputBinding, 'shiny.bootstrapTabInput');","// This code creates acustom handler for userMessages\nShiny.addCustomMessageHandler(\"user-messages\", function(message) {\n var id = message.id, action = message.action, content = message.body, index = message.index;\n \n // message text\n // We use Shiny.renderHtml to handle the case where the user pass input/outputs in the updated content that require a new dependency not available in the \n // page at startup. \n if (content.hasOwnProperty(\"text\")) {\n var text;\n if (content.text.html === undefined) {\n text = content.text;\n } else {\n text = Shiny.renderHtml(content.text.html, $([]), content.text.dependencies).html;\n } \n }\n \n // unbind all\n Shiny.unbindAll();\n \n if (action === \"remove\") {\n $(\"#\" + id).find(\".direct-chat-msg\").eq(index - 1).remove();\n } else if (action === \"add\") {\n var author = content.author, date = content.date, image = content.image, type = content.type;\n \n // build the new message \n var authorWrapper, dateWrapper;\n if (type === \"sent\") {\n authorWrapper = '' + author + '';\n dateWrapper = '' + date + '';\n } else {\n authorWrapper = '' + author + '';\n dateWrapper = '' + date + '';\n }\n\n var newMessage = `
    ${authorWrapper}${dateWrapper}
    ${text}
    `;\n \n // build wrapper\n var newMessageWrapper;\n if (type === \"sent\") {\n newMessageWrapper = '
    ' + newMessage + '
    ';\n } else {\n newMessageWrapper = '
    ' + newMessage + '
    ';\n }\n \n // append message\n $(\"#\" + id).find(\".direct-chat-messages\").append(newMessageWrapper);\n } else if (action === \"update\") {\n \n // today's date\n var d = new Date();\n var month = d.getMonth() + 1;\n var day = d.getDate();\n var today = d.getFullYear() + '/' +\n ((''+month).length<2 ? '0' : '') + month + '/' +\n ((''+day).length<2 ? '0' : '') + day;\n \n // we assume only text may be updated. Does not make sense to modify author/date\n \n $(\"#\" + id)\n .find(\".direct-chat-text\")\n .eq(index - 1)\n .replaceWith('
    (modified: ' + today +')
    ' + text + '
    ');\n }\n \n // Calls .initialize() for all of the input objects in all input bindings,\n // in the given scope (document)\n Shiny.initializeInputs();\n Shiny.bindAll(); // bind all inputs/outputs\n});"]} diff --git a/inst/bs4Dash-2.3.0/bs4Dash.css b/inst/bs4Dash-2.3.3/bs4Dash.css similarity index 100% rename from inst/bs4Dash-2.3.0/bs4Dash.css rename to inst/bs4Dash-2.3.3/bs4Dash.css diff --git a/inst/bs4Dash-2.3.0/bs4Dash.js b/inst/bs4Dash-2.3.3/bs4Dash.js similarity index 99% rename from inst/bs4Dash-2.3.0/bs4Dash.js rename to inst/bs4Dash-2.3.3/bs4Dash.js index 1cfcc85b..251493ac 100644 --- a/inst/bs4Dash-2.3.0/bs4Dash.js +++ b/inst/bs4Dash-2.3.3/bs4Dash.js @@ -932,12 +932,14 @@ $(function () { break; default: console.warn(`${config.status} does not belong to allowed statuses!`) } + + closeButton = ''; if (config.closable) { closeButton = '' } - titleTag = `
    ` + titleTag = `
    ${config.title}
    ` contentTag = config.content; alertTag = `
    ')),t.collapsible=!0):($(a).find('[data-card-widget = "collapse"]').remove(),t.collapsible=!1)),e.options.hasOwnProperty("closable")&&e.options.closable!==t.closable&&(e.options.closable?0===$(a).find('[data-card-widget = "remove"]').length&&(0===$(a).find('[data-card-widget = "maximize"]').length?$(a).find(".card-tools.float-right").append($('')):$('').insertBefore($(a).find('[data-card-widget = "maximize"]')),t.closable=!0):($(a).find('[data-card-widget = "remove"]').remove(),t.closable=!1)),e.options.hasOwnProperty("maximizable")&&e.options.maximizable!==t.maximizable&&(e.options.maximizable?0===$(a).find('[data-card-widget = "maximize"]').length&&($(a).find(".card-tools.float-right").append($('')),t.maximizable=!0):($(a).find('[data-card-widget = "maximize"]').remove(),t.maximizable=!1)),e.options.hasOwnProperty("solidHeader")&&!n&&!i)if(e.options.solidHeader!==t.solidHeader&&$(a).hasClass("card-outline"))$(a).removeClass("card-outline"),t.solidHeader=!0;else if($(a).hasClass("card-outline")||e.options.solidHeader){if($(a).hasClass("card-outline")){o=t.status||e.options.status;e.options.background&&o?($(a).removeClass("card-outline"),t.solidHeader=!0):t.background&&o&&($(a).removeClass("card-outline"),t.solidHeader=!1)}}else{var o=t.status||e.options.status;e.options.background&&o&&(null!==e.options.background||t.background&&o)||($(a).addClass("card-outline"),t.solidHeader=!1)}if(e.options.hasOwnProperty("status")&&!n&&e.options.status!==t.status){var d,l,c;if(null===e.options.status&&null!==t.status){if(i||$(a).removeClass("card-"+t.status),$(a).hasClass("card-outline")&&!i&&$(a).addClass("card-outline"),e.options.background){var u=e.options.background;validStatusesPlus.indexOf(u)>-1?$(a).find(".btn-tool").addClass("bg-"+u):validStatuses.indexOf(u)>-1&&$(a).find(".btn-tool").addClass("btn-"+u)}}else e.options.status&&(i?(l="bg-",e.options.gradient&&(l+="gradient-"),l+=e.options.status,$(a).find(".widget-user-header").addClass(l)):(l="card-"+e.options.status,$(a).addClass(l)),t.status&&(i?(d="bg-",t.gradient&&(d+="gradient-"),d+=t.status,$(a).find(".widget-user-header").removeClass(d)):(d="card-"+t.status,$(a).removeClass(d))),$(a).hasClass("card-outline")&&!i||(validStatusesPlus.indexOf(e.options.status)>-1?$(a).find(".btn-tool").addClass("bg-"+e.options.status):validStatuses.indexOf(e.options.status)>-1&&$(a).find(".btn-tool").addClass("btn-"+e.options.status)));(t.status||t.background)&&(t.status?c=t.status:t.background&&(c=t.background),validStatusesPlus.indexOf(c)>-1?$(a).find(".btn-tool").removeClass("bg-"+c):validStatuses.indexOf(c)>-1&&$(a).find(".btn-tool").removeClass("btn-"+c)),t.status=e.options.status}if(e.options.hasOwnProperty("background")&&e.options.background!==t.background){var b="bg-";if(newBgClass=b,t.background){if(t.gradient&&(b+="gradient-"),b+=t.background,i&&!t.status&&!e.options.status){var h=$(a).find(".widget-user-header");$(h).removeClass(b)}$(a).removeClass(b)}if(e.options.background){if((t.gradient||e.options.gradient)&&(newBgClass+="gradient-"),newBgClass+=e.options.background,i&&!t.status&&!e.options.status){h=$(a).find(".widget-user-header");$(h).addClass(newBgClass)}$(a).addClass(newBgClass)}t.gradient!==e.options.gradient&&void 0!==e.options.gradient&&(t.gradient=e.options.gradient),t.background=e.options.background}e.options.hasOwnProperty("width")&&e.options.width!==t.width&&(this._updateWidth(a,t.width,e.options.width),t.width=e.options.width),e.options.hasOwnProperty("height")&&e.options.height!==t.height&&(null===e.options.height?$(a).find(".card-body").css("height",""):$(a).find(".card-body").css("height",e.options.height),t.height=e.options.height),$(a).parent().find("script[data-for='"+a.id+"']").replaceWith('\"\n );\n } else {\n if (value != \"restore\") {\n if ($(el).css(\"display\") != \"none\") {\n $(el).CardWidget(value);\n }\n } else {\n $(el).show();\n // this is needed so that the last event handler is considered\n // in the subscribe method.\n $(el).trigger(\"shown\");\n }\n }\n },\n receiveMessage: function(el, data) {\n this.setValue(el, data);\n $(el).trigger(\"change\");\n },\n\n subscribe: function(el, callback) {\n $(el).on(\"expanded.lte.cardwidget collapsed.lte.cardwidget\", function(e) {\n // set a delay so that SHiny get the input value when the collapse animation\n // is finished.\n setTimeout(function() {\n callback();\n }, 500);\n });\n\n $(el).on(\"maximized.lte.cardwidget minimized.lte.cardwidget\", function(e) {\n callback();\n });\n\n $(el).on(\"removed.lte.cardwidget\", function(e) {\n setTimeout(function() {\n callback();\n }, 500);\n });\n // we need to split removed and shown event since shown is immediate whereas close\n // takes some time\n $(el).on(\"shown.cardBinding\", function(e) {\n callback();\n });\n\n // handle change event triggered in the setValue method\n $(el).on(\"change.cardBinding\", function(event) {\n setTimeout(function() {\n callback();\n }, 500);\n });\n },\n\n unsubscribe: function(el) {\n $(el).off(\".cardBinding\");\n }\n});\n\nShiny.inputBindings.register(cardBinding);\n\n// Card sidebar input binding\nvar cardSidebarBinding = new Shiny.InputBinding();\n$.extend(cardSidebarBinding, {\n initialize: function(el) {\n // erase default to avoid seeing moving sidebars on initialization\n $(\".direct-chat-contacts, .direct-chat-messages\").css({\n transition: \"transform .0s ease-in-out\"\n });\n\n var background = $(el).attr(\"data-background\")\n ? $(el).attr(\"data-background\")\n : \"#343a40\";\n var width = $(el).attr(\"data-width\")\n ? parseInt($(el).attr(\"data-width\"))\n : 100;\n var closeTranslationRate = (100 * 100) / width;\n var contacts = $(el)\n .closest(\".direct-chat\")\n .find(\".direct-chat-contacts\");\n\n // apply width and background\n $(contacts).css({\n background: `${background}`,\n width: `${width}%`\n });\n\n // If start open, apply openTranslationRate else apply closeTranslationRate ...\n if ($(el).attr(\"data-start-open\") === \"true\") {\n var openTranslationRate = closeTranslationRate - 100;\n $(contacts).css({ transform: `translate(${openTranslationRate}%, 0)` });\n } else {\n $(contacts).css({ transform: `translate(${closeTranslationRate}%, 0)` });\n }\n\n // Restore for better transitions\n setTimeout(function() {\n $(\".direct-chat-contacts, .direct-chat-messages\").css({\n transition: \"transform .5s ease-in-out\"\n });\n }, 300);\n \n // Easyclose feature\n if ($(el).attr(\"data-easy-close\") === \"true\") {\n $(document).mouseup(function(e) {\n var container = $(\".direct-chat-contacts\");\n var openContainer = $(\".direct-chat-contacts-open\");\n // if the target of the click isn't the container nor a descendant of the container and also not if the filter symbol was clicke d\n if (!container.is(e.target) && \n container.has(e.target).length === 0 && \n $(e.target).parents('.card-tools').length !== 1) {\n openContainer\n .find(\"[data-widget='chat-pane-toggle']\")\n .click();\n }\n }); \n }\n },\n\n find: function(scope) {\n return $(scope).find('[data-widget=\"chat-pane-toggle\"]');\n },\n\n // Given the DOM element for the input, return the value\n getValue: function(el) {\n var cardWrapper = $(el).closest(\".card\");\n return $(cardWrapper).hasClass(\"direct-chat-contacts-open\");\n },\n\n // see updatebs4Card\n receiveMessage: function(el, data) {\n // In theory, adminLTE3 has a builtin function\n // we could use $(el).DirectChat('toggle');\n // However, it does not update the related input.\n // The toggled.lte.directchat event seems to be broken.\n $(el).trigger(\"click\");\n $(el).trigger(\"shown\");\n },\n\n subscribe: function(el, callback) {\n var self = this;\n $(el).on(\"click\", function(e) {\n var width = $(el).attr(\"data-width\")\n ? parseInt($(el).attr(\"data-width\"))\n : 100;\n var closeTranslationRate = (100 * 100) / width;\n var openTranslationRate = closeTranslationRate - 100;\n // set a delay so that Shiny get the input value when the collapse animation\n // is finished.\n var target = e.currentTarget;\n setTimeout(function(e = target) {\n // apply correct translation rate depending on current state\n var contacts = $(e)\n .closest(\".direct-chat\")\n .find(\".direct-chat-contacts\");\n if (self.getValue(el)) {\n $(contacts).css({\n transform: `translate(${openTranslationRate}%, 0)`\n });\n } else {\n $(contacts).css({\n transform: `translate(${closeTranslationRate}%, 0)`\n });\n }\n callback();\n }, 10);\n });\n },\n\n unsubscribe: function(el) {\n $(el).off(\".cardSidebarBinding\");\n }\n});\n\nShiny.inputBindings.register(cardSidebarBinding);\n","$(function() {\n \n // required to show a toast when the controlbar is pinned \n // for the first time. Show once since it may be annoying ...\n var showToast = true;\n const controlbarToast = () => {\n if (showToast) {\n $(document).Toasts('create', {\n title: 'Controlbar is pinned',\n close: false,\n autohide: true,\n delay: 2000\n });\n showToast = false; \n }\n };\n\n // This prevent box content from going outside their container \n // when the control-bar is on push mode\n $(\"#controlbar-toggle\").on(\"click\",\n function() {\n if ($(\"body\").hasClass(\"control-sidebar-push-slide\")) {\n $(window).trigger(\"resize\"); \n }\n });\n \n \n // The code below hande the click out of the right control bar\n $(window).click(function(e) { \n // There is a potential conflict. This function detect any click outside\n // the controlbar and close if if it is not pinned. Yet, if we click on an action // button controlling the controlbar state (see updatebs4Controlbar), it is also outside the controlbar so the toggle event will be triggered twice. The controlbar will never close as shown in https://github.com/RinteRface/bs4Dash/issues/110. Below we make sure to leave the function as soon as a click on a button holding the class action button. This is not really a fix but a reasonable workaround.\n var isActionButton = $(e.target).hasClass(\"action-button\");\n if (isActionButton) return null;\n \n if($(\"aside.control-sidebar\").find(e.target).length === 0) {\n var pinned = $(\".control-sidebar\").attr(\"data-pin\");\n if (pinned === \"false\" || pinned === undefined) {\n $(\"body\").removeClass(\"control-sidebar-slide-open\"); \n // don't forget to refresh the input binding\n $(\"#controlbar-toggle\").trigger('collapsed.lte.controlsidebar');\n }\n } \n });\n \n // handle the pin button: toggle data-pin state\n $(\"#controlbarPin\").on('click', function() {\n var $pinIcon = $(this).children();\n $pinIcon.toggleClass(\"fa-rotate-90 fa-lg\");\n \n $(\".control-sidebar\").attr(\"data-pin\",\n $(\".control-sidebar\").attr(\"data-pin\") == \"false\" ? \"true\" : \"false\");\n // toggle right sidebar control depending on the datapin\n if ($(\".control-sidebar\").attr(\"data-pin\") === \"true\") {\n $pinIcon.css(\"color\", \"#007bff\");\n $(\"#controlbar-toggle\").addClass(\"disabled\");\n controlbarToast();\n } else {\n $(\"#controlbar-toggle\").removeClass(\"disabled\");\n $pinIcon.css(\"color\", \"\");\n }\n });\n\n\nvar init = true;\n\n // Input binding\n var controlbarBinding = new Shiny.InputBinding();\n \n $.extend(controlbarBinding, {\n \n find: function(scope) {\n return $(scope).find(\".control-sidebar\");\n },\n \n // Given the DOM element for the input, return the value\n getValue: function(el) {\n // Handles the pin \n var controlbarOpen = $(\"body\").hasClass(\"control-sidebar-slide-open\");\n var pinned = $(el).attr(\"data-pin\") === \"true\";\n if (controlbarOpen && pinned && init) {\n $(\"#controlbar-toggle\").addClass(\"disabled\");\n $(\"#controlbarPin\")\n .children()\n .css(\"color\", \"#007bff\");\n controlbarToast();\n init = false;\n }\n \n // this handles the case where the controlbar is not collapsed at start\n var controlbarCollapsed = $(el).attr('data-collapsed');\n if (controlbarCollapsed === \"false\") {\n $(\"#controlbar-toggle\").ControlSidebar('toggle');\n $(el).attr('data-collapsed', \"true\");\n return true;\n } else {\n return $(\"body\").hasClass(\"control-sidebar-slide-open\");\n }\n },\n // see updatebs4Controlbar\n receiveMessage: function(el, data) {\n $(\"#controlbar-toggle\").ControlSidebar('toggle');\n },\n \n subscribe: function(el, callback) {\n $(\"#controlbar-toggle\").on(\"collapsed.lte.controlsidebar expanded.lte.controlsidebar\", function(e) {\n $(el).trigger('shown');\n // add a delay so that Shiny get the input value \n // after the AdminLTE3 animation is finished!\n setTimeout(\n function() {\n callback();\n }, 10);\n });\n },\n \n unsubscribe: function(el) {\n $(el).off(\".controlbarBinding\");\n }\n });\n \n Shiny.inputBindings.register(controlbarBinding, \"bs4Dash.controlbarBinding\");\n \n // handle controlbar overlay\n var controlbarOverlay = $('.control-sidebar').attr('data-overlay');\n if (controlbarOverlay === \"false\") {\n $('body').addClass('control-sidebar-push-slide');\n }\n\n});","$(function () {\n // handle tooltip from the server side\n Shiny.addCustomMessageHandler('create-tooltip', function (message) {\n var tooltipTarget;\n if (message.id) {\n tooltipTarget = '#' + message.id;\n } else {\n if (message.selector) {\n tooltipTarget = message.selector;\n }\n }\n $(tooltipTarget)\n .addClass('has-tooltip')\n .tooltip(message.options);\n console.log(`'Tooltip created for ${tooltipTarget}'`);\n });\n\n Shiny.addCustomMessageHandler('remove-tooltip', function (message) {\n var tooltipTarget = '#' + message;\n\n // only destroys if popover exists\n if ($(tooltipTarget).hasClass('has-tooltip')) {\n $(tooltipTarget)\n .removeClass('has-tooltip')\n .tooltip('dispose');\n console.log(`'Tooltip destroyed for ${tooltipTarget}'`);\n }\n });\n\n // handle popover from the server side\n Shiny.addCustomMessageHandler('create-popover', function (message) {\n var popoverTarget;\n if (message.id) {\n popoverTarget = '#' + message.id;\n } else {\n if (message.selector) {\n popoverTarget = message.selector;\n }\n }\n // indicate target has popover. This is for removePopover to know\n // whether the popover exists\n $(popoverTarget)\n .addClass('has-popover')\n .popover(message.options);\n console.log(`'Popover created for ${popoverTarget}'`);\n });\n\n\n Shiny.addCustomMessageHandler('remove-popover', function (message) {\n var popoverTarget = '#' + message;\n\n // only destroys if popover exists\n if ($(popoverTarget).hasClass('has-popover')) {\n $(popoverTarget)\n .removeClass('has-popover')\n .popover('dispose');\n console.log(`'Popover destroyed for ${popoverTarget}'`);\n }\n });\n\n\n // handle builtin toasts\n Shiny.addCustomMessageHandler('toast', function (message) {\n $(document).Toasts('create', message);\n });\n\n // Create an alert\n Shiny.addCustomMessageHandler('create-alert', function (message) {\n // setup target\n var alertTarget;\n if (message.id) {\n alertTarget = `#${message.id}`;\n } else {\n if (message.selector) {\n alertTarget = message.selector;\n }\n }\n\n // build the tag from options\n var config = message.options, alertCl, alertTag, iconType, closeButton, titleTag, contentTag;\n alertCl = 'alert alert-dismissible';\n if (config.status !== undefined) {\n alertCl = `${alertCl} alert-${config.status}`;\n }\n if (config.elevation !== undefined) {\n alertCl = `${alertCl} elevation-${config.elevation}`;\n }\n\n switch (config.status) {\n case 'primary': iconType = 'info';\n break;\n case 'danger': iconType = 'ban';\n break;\n case 'info': iconType = 'info';\n break;\n case 'warning': iconType = 'warning';\n break;\n case 'success': iconType = 'check';\n break;\n default: console.warn(`${config.status} does not belong to allowed statuses!`)\n }\n\n closeButton = '';\n\n if (config.closable) {\n closeButton = ''\n }\n\n titleTag = `
    ${config.title}
    `\n contentTag = config.content;\n\n alertTag = `
    \n ${closeButton}${titleTag}${contentTag}\n
    `\n if (config.width !== undefined) {\n alertTag = `
    ${alertTag}
    `\n }\n\n // add it to the DOM if no existing alert is found in the anchor\n if ($(`#${message.id}-alert`).length === 0) {\n $(alertTarget).append(alertTag);\n Shiny.setInputValue(message.id, true, { priority: 'event' });\n\n // add events only after element is inserted\n\n // callback -> give ability to perform more actions on the Shiny side\n // once the alert is closed\n $(`#${message.id}-alert`).on('closed.bs.alert', function () {\n Shiny.setInputValue(message.id, false, { priority: 'event' });\n });\n // Clicking on close button does not trigger any event.\n // Trigger the closed.bs.alert event.\n $('[data-dismiss=\"alert\"]').on('click', function () {\n var alertId = $(this).parent.attr('id');\n $(`#${alertId}.`).trigger('closed.bs.alert');\n });\n\n } else {\n console.warn(`${alertTarget} already has an alert!`);\n }\n });\n\n\n Shiny.addCustomMessageHandler('close-alert', function (message) {\n // only closes if element exists\n if ($(`#${message}-alert`).length > 0) {\n $(`#${message}-alert`).alert('close');\n } else {\n console.warn('Nothing to delete!');\n }\n });\n});","// When document is ready, if there is a sidebar menu with no activated tabs,\n// activate the one specified by `data-start-selected`, or if that's not\n// present, the first one.\nvar ensureActivatedTab = function() {\n // get the selected tabs\n var $tablinks = $(\".sidebar-menu a[data-toggle='tab']\");\n\n // If there are no tabs, $startTab.length will be 0.\n var $startTab = $tablinks.filter(\"[data-start-selected='1']\");\n if ($startTab.length === 0) {\n // If no tab starts selected, use the first one, if present\n $startTab = $tablinks.first();\n }\n\n // If there's a `data-start-selected` attribute and we can find a tab with\n // that name, activate it.\n if ($startTab.length !== 0) {\n // This is just in case the user renders the tabs in a renderUI that does not\n // print immediately in the DOM. We need a bit of a delay before telling which\n // tab to show ...\n if ($(\".sidebar-menu\").hasClass(\"bs4Dash-menu-output\")) {\n setTimeout(function() {\n // we need to initialize any treeview elements that were not inserted\n // in the DOM when adminlte was first initialized!\n adminlte.Treeview._jQueryInterface.call($('[data-widget=\"treeview\"]'), 'init');\n\n $startTab.tab(\"show\");\n }, 10);\n } else {\n $startTab.tab(\"show\");\n }\n \n\n // This is indirectly setting the value of the Shiny input by setting\n // an attribute on the html element it is bound to. We cannot use the\n // inputBinding's setValue() method here because this is called too\n // early (before Shiny has fully initialized)\n $(\".sidebarMenuSelectedTabItem\").attr(\n \"data-value\",\n $startTab.attr(\"data-value\")\n );\n }\n};\n\n// This function handles a special case in the AdminLTE sidebar: when there\n// is a sidebar-menu with items, and one of those items has sub-items, and\n// they are used for tab navigation. Normally, if one of the items is\n// selected and then a sub-item is clicked, both the item and sub-item will\n// retain the \"active\" class, so they will both be highlighted. This happens\n// because they're not designed to be used together for tab panels. This\n// code ensures that only one item will have the \"active\" class.\nvar deactivateOtherTabs = function() {\n // Find all tab links under sidebar-menu even if they don't have a\n // tabName (which is why the second selector is necessary)\n var $tablinks = $(\n \".sidebar-menu a[data-toggle='tab'],\" + \".sidebar-menu li.has-treeview > a\"\n );\n\n // If any other items are active, deactivate them\n $tablinks.not($(this)).removeClass(\"active\");\n\n // also manually activate the parent link when the selected item\n // is part of a treeview. For some reason, this is not done by AdminLTE3...\n if ($(this).hasClass(\"treeview-link\")) {\n $(this)\n .parents(\".has-treeview\")\n .children()\n .eq(0)\n .addClass(\"active\");\n }\n\n // Trigger event for the tabItemInputBinding\n var $obj = $(\".sidebarMenuSelectedTabItem\");\n var inputBinding = $obj.data(\"shiny-input-binding\");\n if (typeof inputBinding !== \"undefined\") {\n inputBinding.setValue($obj, $(this).attr(\"data-value\"));\n $obj.trigger(\"change\");\n }\n};\n\n$(function() {\n // Whenever the sidebar finishes a transition (which it does every time it\n // changes from collapsed to expanded and vice versa), trigger resize,\n // so that all outputs are resized.\n $(\".main-sidebar\").on(\n \"webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend\",\n function() {\n $(window).trigger(\"resize\");\n }\n );\n\n $(document).on(\n \"shown.bs.tab\",\n '.sidebar-menu a[data-toggle=\"tab\"]',\n deactivateOtherTabs\n );\n\n ensureActivatedTab();\n\n // Whenever we expand a menuItem (to be expandable, it must have children),\n // update the value for the expandedItem's input binding (this is the\n // tabName of the fist subMenuItem inside the menuItem that is currently\n // expanded)\n $(document).on(\"click\", \".has-treeview\", function() {\n var $menu = $(this);\n // If this menuItem was already open, then clicking on it again,\n // should trigger the \"hidden\" event, so Shiny doesn't worry about\n // it while it's hidden (and vice versa).\n if ($menu.hasClass(\"menu-open\")) $menu.trigger(\"collapsed.lte.treeview\");\n else if ($menu.hasClass(\".has-treeview\"))\n $menu.trigger(\"expanded.lte.treeview\");\n\n // need to set timeout to account for the slideUp/slideDown animation\n var $obj = $(\".sidebar.shiny-bound-input\");\n setTimeout(function() {\n $obj.trigger(\"change\");\n }, 600);\n });\n\n //---------------------------------------------------------------------\n // tabItemInputBinding\n // ------------------------------------------------------------------\n // Based on Shiny.tabItemInputBinding, but customized for tabItems in\n // bs4Dash, which have a slightly different structure.\n var tabItemInputBinding = new Shiny.InputBinding();\n $.extend(tabItemInputBinding, {\n find: function(scope) {\n return $(scope).find(\".sidebarMenuSelectedTabItem\");\n },\n getValue: function(el) {\n var value = $(el).attr(\"data-value\");\n if (value === \"null\") return null;\n return value;\n },\n setValue: function(el, value) {\n var self = this;\n var anchors = $(el)\n .parent(\".sidebar-menu\")\n .find(\"li:not(.treeview)\")\n .children(\"a\");\n anchors.each(function() {\n // eslint-disable-line consistent-return\n if (self._getTabName($(this)) === value) {\n $(this).tab(\"show\");\n // this make sure that treeview items are open when we\n // use the updatebs4TabItems function on the server side\n if ($(this).hasClass(\"treeview-link\")) {\n if (\n !$(this)\n .parents(\".has-treeview\")\n .hasClass(\"menu-open\")\n ) {\n $(this)\n .parents(\".has-treeview\")\n .children()\n .eq(0)\n .trigger(\"click\");\n }\n }\n $(el).attr(\"data-value\", self._getTabName($(this)));\n return false;\n }\n });\n },\n receiveMessage: function(el, data) {\n if (data.hasOwnProperty(\"value\")) this.setValue(el, data.value);\n },\n subscribe: function(el, callback) {\n // This event is triggered by deactivateOtherTabs, which is triggered by\n // shown. The deactivation of other tabs must occur before Shiny gets the\n // input value.\n $(el).on(\"change.tabItemInputBinding\", function() {\n callback();\n });\n },\n unsubscribe: function(el) {\n $(el).off(\".tabItemInputBinding\");\n },\n _getTabName: function(anchor) {\n return anchor.attr(\"data-value\");\n }\n });\n\n Shiny.inputBindings.register(tabItemInputBinding, \"bs4Dash.tabItemInput\");\n\n //---------------------------------------------------------------------\n // sidebarInputBinding\n // ------------------------------------------------------------------\n // similar to controlbarInputBinding\n var sidebarBinding = new Shiny.InputBinding();\n\n $.extend(sidebarBinding, {\n find: function(scope) {\n return $(scope).find(\".main-sidebar\");\n },\n\n // Given the DOM element for the input, return the value\n getValue: function(el) {\n // Warning: we can't look for sidebar-open since this\n // class is only generated on mobile devices\n return !$(\"body\").hasClass(\"sidebar-collapse\");\n },\n\n // see updatebs4Controlbar\n receiveMessage: function(el, data) {\n $(\"[data-widget='pushmenu']\").PushMenu(\"toggle\");\n },\n\n subscribe: function(el, callback) {\n $(\"[data-widget='pushmenu']\").on(\n \"collapsed.lte.pushmenu.sidebarBinding shown.lte.pushmenu.sidebarBinding\",\n function(e) {\n callback();\n }\n );\n },\n\n unsubscribe: function(el) {\n $(el).off(\".sidebarBinding\");\n }\n });\n\n Shiny.inputBindings.register(sidebarBinding, \"bs4Dash.sidebarInput\");\n\n // sidebarmenuExpandedInputBinding\n // ------------------------------------------------------------------\n // This keeps tracks of what menuItem (if any) is expanded\n var sidebarmenuExpandedInputBinding = new Shiny.InputBinding();\n $.extend(sidebarmenuExpandedInputBinding, {\n find: function(scope) {\n // This will also have id=\"sidebarItemExpanded\"\n return $(scope).find(\".sidebar\");\n },\n getValue: function(el) {\n var $open = $(el)\n .find(\"li\")\n .filter(\".menu-open\")\n .find(\"ul\");\n if ($open.length === 1) return $open.attr(\"data-expanded\");\n else return null;\n },\n setValue: function(el, value) {\n // does not work (nothing is printed)\n var $menuItem = $(el).find(\"[data-expanded='\" + value + \"']\");\n // This will trigger actions defined by AdminLTE, as well as actions\n // defined in sidebar.js.\n $menuItem.prev().trigger(\"click\");\n },\n subscribe: function(el, callback) {\n $(el).on(\"change.sidebarmenuExpandedInputBinding\", function() {\n callback();\n });\n },\n unsubscribe: function(el) {\n $(el).off(\".sidebarmenuExpandedInputBinding\");\n }\n });\n Shiny.inputBindings.register(\n sidebarmenuExpandedInputBinding,\n \"bs4Dash.sidebarmenuExpandedInputBinding\"\n );\n\n // handle fixed sidebar\n if ($(\".main-sidebar\").attr(\"data-fixed\") === \"true\") {\n $(\"body\").addClass(\"layout-fixed\");\n //$('body').Layout('fixLayoutHeight');\n }\n});\n","$(function () {\n\n $navbar = $('.main-header.navbar');\n\n // Modify the shiny tabsetpanel binding to follow BS4 rules\n $(document).on('shiny:connected', function (event) {\n Shiny.unbindAll();\n $.extend(Shiny\n .inputBindings\n .bindingNames['shiny.bootstrapTabInput']\n .binding, {\n // do whathever you want to edit existing methods\n getValue: function (el) {\n var anchor = $(el).find('li:not(.dropdown)').children('a.active');\n if (anchor.length === 1)\n return this._getTabName(anchor);\n\n return null;\n }\n });\n Shiny.bindAll();\n });\n\n // footer has fixed layout?\n if ($(\".main-footer\").attr(\"data-fixed\") === \"true\") {\n $(\"body\").addClass(\"layout-footer-fixed\");\n }\n\n // add dropdown-menu-right class to correctly open the dropdown to all \n // navbar rightUi elements\n var navbarRight = $('.navbar-right').find('.dropdown-menu');\n $(navbarRight).each(function () {\n if (!$(this).hasClass('dropdown-menu-right')) {\n $(this).addClass('dropdown-menu-right');\n }\n });\n\n\n // data toggle collapse icon update\n $('.user-block [data-toggle=\"collapse\"]').on('click', function () {\n if ($(this).children('i').hasClass('fa-plus')) {\n $(this).children('i').attr('class', 'fa fa-minus');\n } else {\n $(this).children('i').attr('class', 'fa fa-plus');\n }\n });\n\n // fullscreen toggle\n if ($('body').attr('data-fullscreen') == 1) {\n var fullScreenToggle = `
  • \n \n \n \n
  • `;\n $(fullScreenToggle).insertBefore($('[data-widget=\"control-sidebar\"]').parent());\n }\n\n // slide to top button\n if ($('body').attr('data-scrollToTop') == 1) {\n var $slideToTop = $('
    ');\n\n $slideToTop.html('');\n\n $slideToTop.css({\n position: 'fixed',\n bottom: '20px',\n right: '25px',\n width: '40px',\n height: '40px',\n color: '#eee',\n 'font-size': '',\n 'line-height': '40px',\n 'text-align': 'center',\n 'background-color': '#222d32',\n cursor: 'pointer',\n 'border-radius': '5px',\n 'z-index': '99999',\n opacity: '.7',\n 'display': 'none'\n });\n\n $slideToTop.on('mouseenter', function () {\n $(this).css('opacity', '1');\n });\n\n $slideToTop.on('mouseout', function () {\n $(this).css('opacity', '.7');\n });\n\n $('.wrapper').append($slideToTop);\n\n $(window).scroll(function () {\n if ($(window).scrollTop() >= 150) {\n if (!$($slideToTop).is(':visible')) {\n $($slideToTop).fadeIn(500);\n }\n } else {\n $($slideToTop).fadeOut(500);\n }\n });\n\n $($slideToTop).click(function () {\n $('html, body').animate({\n scrollTop: 0\n }, 500);\n });\n }\n \n // nav item click also triggers scroll to top\n $('.main-sidebar .nav-item').on('click', function () {\n $('html, body').animate({\n scrollTop: 0\n }, 0);\n }); \n\n\n // tooltip/popover toggle\n if ($('body').attr('data-help') == 2 || \n $('body').attr('data-help') == 1) {\n var $help_switch_checkbox = $('', {\n type: 'checkbox',\n id: 'help_switch',\n class: 'custom-control-input'\n }).on('click', function () {\n if ($(this).is(':checked')) {\n $('[data-toggle=\"tooltip\"]').tooltip('enable');\n $('[data-toggle=\"popover\"]').popover({\n trigger: 'hover'\n });\n $('[data-toggle=\"popover\"]').popover('enable');\n } else {\n $('[data-toggle=\"tooltip\"]').tooltip('disable');\n $('[data-toggle=\"popover\"]').popover('disable');\n }\n });\n\n var $help_switch_container = $('
    ', { class: 'custom-control custom-switch mx-2 mt-2' }).append($help_switch_checkbox).append(``);\n \n // insert before $('#controlbar-toggle') whenever possible ...\n if ($('.nav-item #controlbar-toggle')) {\n $help_switch_container.insertBefore($('#controlbar-toggle').parent());\n } else {\n $navbar.append($help_switch_container);\n }\n\n // trigger first click, if necessary\n $(document).on('shiny:connected', function() {\n if ($('body').attr('data-help') == 2 || $('body').attr('data-help') == 1) {\n $help_switch_checkbox.click(); \n // Click again if option is set to FALSE\n if ($('body').attr('data-help') == 1) {\n $help_switch_checkbox.click(); \n }\n }\n }); \n }\n\n // dark mode input\n $(document).one('shiny:connected', function () {\n if ($('body').hasClass('dark-mode')) {\n Shiny.setInputValue('dark_mode', true, { priority: 'event' });\n } else {\n Shiny.setInputValue('dark_mode', false, { priority: 'event' });\n }\n });\n\n\n // Navbar colors\n getNavbarColor = function () {\n for (let color of navbar_all_colors) {\n if ($('.main-header').attr('class').search(color) > -1) {\n return color;\n }\n }\n };\n\n var navbar_dark_skins = [\n 'navbar-primary',\n 'navbar-secondary',\n 'navbar-info',\n 'navbar-success',\n 'navbar-danger',\n 'navbar-indigo',\n 'navbar-purple',\n 'navbar-pink',\n 'navbar-maroon',\n 'navbar-fuchsia',\n 'navbar-navy',\n 'navbar-lightblue',\n 'navbar-lime',\n 'navbar-teal',\n 'navbar-olive',\n 'navbar-gray-dark',\n 'navbar-gray'\n ];\n\n var navbar_light_skins = [\n 'navbar-warning',\n 'navbar-white',\n 'navbar-orange'\n ];\n\n var navbar_all_colors = navbar_dark_skins.concat(navbar_light_skins);\n \n /**\n * Update color theme to navbar tag\n *\n * @param String color Color to apply.\n * @returns void\n */\n updateNavbarTheme = function (color) {\n var $main_header = $('.main-header');\n $main_header.removeClass('navbar-dark').removeClass('navbar-light');\n navbar_all_colors.forEach(function (color) {\n $main_header.removeClass(color);\n });\n\n if (navbar_dark_skins.indexOf(color) > -1) {\n $main_header.addClass('navbar-dark');\n } else {\n $main_header.addClass('navbar-light');\n }\n\n $main_header.addClass(color);\n };\n \n /**\n * Update icon color style based on navbar color.\n *\n * @param String color Current navbar color.\n * @returns void\n */\n updateNavbarIconColor = function(color) {\n var iconThemeColor = navbar_dark_skins.indexOf(color) > -1 ? \"white\" : \"rgba(0,0,0,.5)\";\n $(\".dark-theme-icon\").css(\"color\", iconThemeColor);\n $('[for=\"help_switch\"] i').css(\"color\", iconThemeColor);\n };\n \n // automatic global theme switcher\n if ($('body').attr('data-dark') == 2 || \n $('body').attr('data-dark') == 1) {\n var $dark_mode_checkbox = $('', {\n type: 'checkbox',\n id: 'theme_switch',\n class: 'custom-control-input'\n }).on('click', function () {\n\n // get any selected navbar skin in the navbar themer\n var newNavbarColor;\n // If there is not themer, we keep the navbar current color.\n // Otherwise, we replace it by the new color.\n if ($('.navbar-themer-chip').length > 0) {\n $('.navbar-themer-chip').filter(function () {\n if ($(this).css('border-style') === 'solid') {\n newNavbarColor = 'navbar-' +\n $(this)\n .attr('class')\n .split('elevation-2')[0]\n .trim()\n .replace('bg-', '');\n }\n });\n } else {\n newNavbarColor = getNavbarColor();\n }\n\n if ($(this).is(':checked')) {\n $('body').addClass('dark-mode');\n\n // use updateNavbarTheme to correctly setup the skin as depending\n // on the required color. If no color is chosen, we use gray-dark for dark mode\n if (newNavbarColor === undefined || newNavbarColor === 'navbar-white') {\n newNavbarColor = \"navbar-gray-dark\";\n }\n updateNavbarTheme(newNavbarColor);\n\n // sidebar update \n if ($('.main-sidebar').length > 0) {\n $('.main-sidebar').attr('class', $('.main-sidebar')\n .attr('class')\n .replace('light', 'dark'));\n $('#sidebar-skin').prop(\"checked\", true);\n\n $('.sidebar-themer-icon')\n .removeClass('fa-sun')\n .addClass('fa-moon');\n }\n\n // controlbar update\n if ($('.control-sidebar').length > 0) {\n $('.control-sidebar').attr('class', $('.control-sidebar')\n .attr('class')\n .replace('light', 'dark'));\n $('#controlbar-skin').prop(\"checked\", true);\n\n $('.controlbar-themer-icon')\n .removeClass('fa-sun')\n .addClass('fa-moon');\n }\n\n\n $('.dark-theme-icon')\n .removeClass('fa-sun')\n .addClass('fa-moon');\n\n // refresh shiny input value \n Shiny.setInputValue('dark_mode', true, { priority: 'event' });\n\n } else {\n $('body').removeClass('dark-mode');\n\n // use updateNavbarTheme to correctly setup the skin as depending\n // on the required color. If no color is chosen, we use white for light mode\n if (newNavbarColor === undefined || newNavbarColor === 'navbar-gray-dark') {\n newNavbarColor = \"navbar-white\";\n }\n updateNavbarTheme(newNavbarColor);\n\n // sidebar update\n if ($('.main-sidebar').length > 0) {\n $('.main-sidebar').attr('class', $('.main-sidebar')\n .attr('class')\n .replace('dark', 'light'));\n $('#sidebar-skin').prop(\"checked\", false);\n\n $('.sidebar-themer-icon')\n .removeClass('fa-moon')\n .addClass('fa-sun');\n }\n\n // controlbar update\n if ($('.control-sidebar').length > 0) {\n $('.control-sidebar').attr('class', $('.control-sidebar')\n .attr('class')\n .replace('dark', 'light'));\n $('#controlbar-skin').prop(\"checked\", false);\n\n $('.controlbar-themer-icon')\n .removeClass('fa-moon')\n .addClass('fa-sun');\n }\n\n $('.dark-theme-icon')\n .removeClass('fa-moon')\n .addClass('fa-sun');\n\n // refresh shiny input value \n Shiny.setInputValue('dark_mode', false, { priority: 'event' });\n }\n \n // update navbar icon colors\n updateNavbarIconColor(newNavbarColor);\n });\n\n var $dark_mode_icon = $('body').hasClass('dark-mode') ? '' : '';\n var $dark_mode_container = $('
    ', { class: 'custom-control custom-switch mx-2 mt-2' }).append($dark_mode_checkbox).append(``);\n \n // insert before $('#controlbar-toggle') whenever possible ...\n if ($('.nav-item #controlbar-toggle')) {\n $dark_mode_container.insertBefore($('#controlbar-toggle').parent());\n } else {\n $navbar.append($dark_mode_container);\n }\n \n \n // Trigger dark mode\n if ($('body').attr('data-dark') == 2) {\n $(document).on('shiny:connected', function() {\n $dark_mode_checkbox.click();\n }); \n }\n }\n \n // apply correct navbar class depending on selected color\n if (getNavbarColor() !== undefined) {\n updateNavbarTheme(getNavbarColor());\n updateNavbarIconColor(getNavbarColor());\n }\n \n\n // Themer chips\n\n // Better style on hover\n $('.themer-chip').hover(function () {\n $(this).css({ opacity: 1 }).removeClass('elevation-2').addClass('elevation-4');\n }, function () {\n $(this).css({ opacity: 0.8 }).removeClass('elevation-4').addClass('elevation-2');\n });\n\n // \n $('.navbar-themer-chip').on('click', function () {\n $(this).css({ 'border-color': 'yellow', 'border-style': 'solid' });\n $('.navbar-themer-chip').not(this).css({ 'border-color': '', 'border-style': '' });\n });\n\n $('.accents-themer-chip').on('click', function () {\n $(this).css({ 'border-color': 'yellow', 'border-style': 'solid' });\n $('.accents-themer-chip').not(this).css({ 'border-color': '', 'border-style': '' });\n });\n\n $('.sidebar-themer-chip').on('click', function () {\n $(this).css({ 'border-color': 'yellow', 'border-style': 'solid' });\n $('.sidebar-themer-chip').not(this).css({ 'border-color': '', 'border-style': '' });\n });\n\n\n // Sidebar themer\n\n // detect global sidebar theme and select or not the toggle\n if ($('.main-sidebar').length > 0) {\n if ($('.main-sidebar').attr('class').match('dark')) {\n $('#sidebar-skin').prop(\"checked\", true);\n }\n }\n\n // clicking on dark/light switch changes:\n // - icon style\n // - sidebar class \n $('#sidebar-skin').on('click', function () {\n var sidebarCl;\n if ($(this).is(':checked')) {\n sidebarCl = $('.main-sidebar')\n .attr('class')\n .replace('light', 'dark');\n $('.main-sidebar').attr('class', sidebarCl);\n\n $('.sidebar-themer-icon')\n .removeClass('fa-sun')\n .addClass('fa-moon');\n } else {\n sidebarCl = $('.main-sidebar')\n .attr('class')\n .replace('dark', 'light');\n $('.main-sidebar').attr('class', sidebarCl);\n\n $('.sidebar-themer-icon')\n .removeClass('fa-moon')\n .addClass('fa-sun');\n }\n });\n\n var sidebar_colors = [\n 'bg-primary',\n 'bg-secondary',\n 'bg-info',\n 'bg-success',\n 'bg-danger',\n 'bg-indigo',\n 'bg-purple',\n 'bg-pink',\n 'bg-maroon',\n 'bg-fuchsia',\n 'bg-navy',\n 'bg-lightblue',\n 'bg-lime',\n 'bg-teal',\n 'bg-olive',\n 'bg-gray-dark',\n 'bg-gray',\n 'bg-light',\n 'bg-warning',\n 'bg-white',\n 'bg-orange'\n ];\n\n\n var sidebar_skins = [\n 'sidebar-dark-primary',\n 'sidebar-dark-secondary',\n 'sidebar-dark-info',\n 'sidebar-dark-success',\n 'sidebar-dark-danger',\n 'sidebar-dark-indigo',\n 'sidebar-dark-purple',\n 'sidebar-dark-pink',\n 'sidebar-dark-maroon',\n 'sidebar-dark-fuchsia',\n 'sidebar-dark-navy',\n 'sidebar-dark-lightblue',\n 'sidebar-dark-lime',\n 'sidebar-dark-teal',\n 'sidebar-dark-olive',\n 'sidebar-dark-gray-dark',\n 'sidebar-dark-gray',\n 'sidebar-dark-light',\n 'sidebar-dark-warning',\n 'sidebar-dark-white',\n 'sidebar-dark-orange',\n 'sidebar-light-primary',\n 'sidebar-light-secondary',\n 'sidebar-light-info',\n 'sidebar-light-success',\n 'sidebar-light-danger',\n 'sidebar-light-indigo',\n 'sidebar-light-purple',\n 'sidebar-light-pink',\n 'sidebar-light-maroon',\n 'sidebar-light-fuchsia',\n 'sidebar-light-navy',\n 'sidebar-light-lightblue',\n 'sidebar-light-lime',\n 'sidebar-light-teal',\n 'sidebar-light-olive',\n 'sidebar-light-gray-dark',\n 'sidebar-light-gray',\n 'sidebar-light-light',\n 'sidebar-light-warning',\n 'sidebar-light-white',\n 'sidebar-light-orange'\n ];\n\n\n updateSidebarTheme = function (color) {\n var sidebarCl;\n if ($('#sidebar-skin').is(':checked')) {\n sidebarCl = 'sidebar-dark-';\n } else {\n sidebarCl = 'sidebar-light-';\n }\n\n var sidebar_class = sidebarCl + color.replace('bg-', '');\n var $sidebar = $('.main-sidebar');\n sidebar_skins.forEach(function (skin) {\n $sidebar.removeClass(skin);\n });\n\n $sidebar.addClass(sidebar_class);\n };\n\n\n // Accents themer\n var accent_colors = [\n 'accent-primary',\n 'accent-secondary',\n 'accent-info',\n 'accent-success',\n 'accent-danger',\n 'accent-indigo',\n 'accent-purple',\n 'accent-pink',\n 'accent-maroon',\n 'accent-fuchsia',\n 'accent-navy',\n 'accent-lightblue',\n 'accent-lime',\n 'accent-teal',\n 'accent-olive',\n 'accent-gray-dark',\n 'accent-gray',\n 'accent-light',\n 'accent-warning',\n 'accent-white',\n 'accent-orange'\n ];\n\n\n updateAccentsTheme = function (color) {\n var accent_class = color;\n var $body = $('body');\n accent_colors.forEach(function (skin) {\n $body.removeClass(skin);\n });\n\n $body.addClass(accent_class);\n };\n\n\n // Controlbar themer\n\n // detect global controlbar theme and select or not the toggle\n if ($('.control-sidebar').length > 0) {\n if ($('.control-sidebar').attr('class').match('dark')) {\n $('#controlbar-skin').prop(\"checked\", true);\n }\n }\n\n // clicking on dark/light switch changes:\n // - icon style\n // - sidebar class \n $('#controlbar-skin').on('click', function () {\n var controlbarCl;\n if ($(this).is(':checked')) {\n controlbarCl = $('.control-sidebar')\n .attr('class')\n .replace('light', 'dark');\n $('.control-sidebar').attr('class', controlbarCl);\n\n $('.controlbar-themer-icon')\n .removeClass('fa-sun')\n .addClass('fa-moon');\n } else {\n controlbarCl = $('.control-sidebar')\n .attr('class')\n .replace('dark', 'light');\n $('.control-sidebar').attr('class', controlbarCl);\n\n $('.controlbar-themer-icon')\n .removeClass('fa-moon')\n .addClass('fa-sun');\n }\n });\n\n});","$(function () {\n // hide the right sidebar toggle \n // if no right sidebar is specified\n noControlbar = ($(\".control-sidebar\").length === 0);\n if (noControlbar) {\n $(\"#controlbar-toggle\").hide();\n }\n\n // hide the right sidebar toggle if the controlbar is disable\n disableControlbar = ($(\".control-sidebar\").attr(\"data-show\"));\n if (!disableControlbar) {\n $(\"#controlbar-toggle\").hide();\n }\n\n // controlbar slide\n controlbarSlide = ($(\".control-sidebar\").attr(\"data-slide\"));\n if (controlbarSlide) {\n $(\"#controlbar-toggle\").attr('data-controlsidebar-slide', controlbarSlide);\n }\n\n // when the sidebar is disabled, hide the sidebar toggle\n disableSidebar = ($(\".main-sidebar\").length === 0);\n if (disableSidebar) {\n $(\".nav-item > a[data-widget='pushmenu']\").css(\"visibility\", \"hidden\");\n }\n\n // handle fixed navbar\n if ($(\".navbar\").attr(\"data-fixed\") === \"true\") {\n $(\"body\").addClass(\"layout-navbar-fixed\");\n }\n\n});","var menuOutputBinding = new Shiny.OutputBinding();\n$.extend(menuOutputBinding, {\n find: function (scope) {\n return $(scope).find('.bs4Dash-menu-output');\n },\n onValueError: function (el, err) {\n Shiny.unbindAll(el);\n this.renderError(el, err);\n },\n renderValue: function (el, data) {\n Shiny.unbindAll(el);\n\n var html;\n var dependencies = [];\n if (data === null) {\n return;\n } else if (typeof (data) === 'string') {\n html = data;\n } else if (typeof (data) === 'object') {\n html = data.html;\n dependencies = data.deps;\n }\n\n var $html = $($.parseHTML(html));\n\n // Convert the inner contents to HTML, and pass to renderHtml\n Shiny.renderHtml($html.html(), el, dependencies);\n\n // Extract class of wrapper, and add them to the wrapper element\n el.className = 'bs4Dash-menu-output shiny-bound-output ' +\n $html.attr('class');\n \n // need this to activate adminLTE3 plugin for treeview \n $(el)\n .attr(\"data-widget\", \"treeview\")\n .attr(\"role\", \"menu\")\n .attr(\"data-accordion\", \"true\");\n\n Shiny.initializeInputs(el);\n Shiny.bindAll(el);\n if ($(el).hasClass(\"sidebar-menu\")) ensureActivatedTab(); // eslint-disable-line\n }\n});\nShiny.outputBindings.register(menuOutputBinding, \"bs4Dash.menuOutputBinding\");","$( document ).ready(function() {\n \n function findActivePage() {\n return $('ul.pagination').find('li.active');\n };\n \n function toggleNavigationItems(activeItem) {\n // hide pagination previous if we are at the first item\n if ($(activeItem).prev().find('a').hasClass('pagination-previous')) {\n $('.pagination-previous').parent('li').css('display', 'none');\n } else {\n $('.pagination-previous').parent('li').css('display', 'block');\n }\n \n // hide pagination next if we are at the last item\n if ($(activeItem).next().find('a').hasClass('pagination-next')) {\n $('.pagination-next').parent('li').css('display', 'none');\n } else {\n $('.pagination-next').parent('li').css('display', 'block');\n }\n }\n \n // Must run at start\n toggleNavigationItems(findActivePage());\n \n // Toggle active item button state\n $('ul.pagination a:not(.disabled, .pagination-previous, .pagination-next)')\n .on('click', function() {\n var activeItem = findActivePage();\n $(activeItem).removeClass('active');\n $(this).parent().addClass('active');\n activeItem = $(this).parent(); // store new active item\n // Also run dynamically\n toggleNavigationItems(activeItem);\n });\n \n // Previous click\n $('ul.pagination .pagination-previous').on('click', function() {\n var activeItem = findActivePage();\n \n var previousSibling = $(activeItem).prev();\n // jump back when we find a disabled sibling\n while ($(previousSibling).hasClass('disabled')) {\n previousSibling = $(previousSibling).prev();\n }\n // Only if active item has a previous sibling that is not \n // the pagination-previous itself.\n if ($(previousSibling).find('a').hasClass('pagination-previous') == false) {\n $(activeItem).removeClass('active');\n $(previousSibling).find('a').click(); \n }\n });\n \n // Next click\n $('ul.pagination .pagination-next').on('click', function() {\n var activeItem = findActivePage();\n \n var nextSibling = $(activeItem).next();\n // jump back when we find a disabled sibling\n while ($(nextSibling).hasClass('disabled')) {\n nextSibling = $(nextSibling).next();\n }\n // Only if active item has a previous sibling that is not \n // the pagination-previous itself.\n if ($(nextSibling).find('a').hasClass('pagination-next') == false) {\n $(activeItem).removeClass('active');\n $(nextSibling).find('a').click(); \n }\n });\n \n var paginationBinding = new Shiny.InputBinding();\n $.extend(paginationBinding, {\n \n initialize: function(el) {\n \n },\n \n find: function (scope) {\n return $(scope).find('ul.pagination');\n },\n \n // Given the DOM element for the input, return the value\n getValue: function (el) {\n return $(el).find('li.active a').attr('data-value');\n },\n \n setValue: function(el, value) {\n \n },\n // internal\n _disableTab: function(el, value) {\n $(el)\n .find('a[data-value=\"' + value + '\"]')\n .parent()\n .removeClass('active')\n .addClass('disabled')\n .attr('tabindex', '-1')\n },\n // see updatePagination\n receiveMessage: function (el, data) {\n // Activate new element\n if (data.hasOwnProperty('selected')) {\n // Disable active element\n $(el)\n .find('.active')\n .removeClass('active');\n // Activate new element\n var selectedItem = $(el)\n .find('a[data-value=\"' + data.selected + '\"]');\n // If the element was disabled before\n if ($(selectedItem).parent('li').hasClass('disabled')) {\n $(selectedItem)\n .parent('li')\n .removeClass('disabled')\n .removeAttr('tabindex');\n }\n $(selectedItem).click();\n }\n \n // Disable elements\n if (data.hasOwnProperty('disabled')) {\n // loop over all elements\n if (typeof data.disabled == 'string') {\n this._disableTab(el, data.disabled)\n } else {\n for (i of data.disabled) {\n // disable element \n this._disableTab(el, i)\n }\n }\n \n // Activate next or previous not disabled li\n var hasActiveItem = $(el).find('li.active').length\n if (hasActiveItem == 0) {\n // Activate first element found (maybe discussed ...)\n var newActive = $(el)\n .find('a:not(.pagination-previous, .pagination-next)')\n .parent('li:not(.disabled)');\n if (newActive.length > 0) {\n $(newActive[0]).find('a').click();\n }\n }\n }\n \n // Trigger the callback to update the input value \n // on the server side.\n $(el).trigger('change');\n },\n \n subscribe: function (el, callback) {\n $(el).find('a').on('click', function() {\n callback();\n });\n \n // Necessry for updatePagination\n $(el).on('change', function() {\n callback();\n })\n },\n \n unsubscribe: function (el) {\n $(el).off('.paginationBinding');\n }\n });\n \n Shiny.inputBindings.register(paginationBinding);\n});","var bootstrapTabInputBinding = new Shiny.InputBinding();\n$.extend(bootstrapTabInputBinding, {\n find: function(scope) {\n return $(scope).find('ul.nav.shiny-tab-input');\n },\n getValue: function(el) {\n // for Bootstrap 4, li element does not hold the active class anymore!\n // We need to look at a.active.\n var anchor = $(el).find('li:not(.dropdown)').children('a.active');\n if (anchor.length === 1)\n return this._getTabName(anchor);\n\n return null;\n },\n setValue: function(el, value) {\n let self = this;\n let success = false;\n if (value) {\n let anchors = $(el).find('li:not(.dropdown)').children('a');\n anchors.each(function() {\n if (self._getTabName($(this)) === value) {\n $(this).tab('show');\n success = true;\n return false; // Break out of each()\n }\n return true;\n });\n }\n if (!success) {\n // This is to handle the case where nothing is selected, e.g. the last tab\n // was removed using removeTab.\n $(el).trigger(\"change\");\n }\n },\n getState: function(el) {\n return { value: this.getValue(el) };\n },\n receiveMessage: function(el, data) {\n if (data.hasOwnProperty('value'))\n this.setValue(el, data.value);\n },\n subscribe: function(el, callback) {\n $(el).on('change shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding', function(event) {\n callback();\n });\n },\n unsubscribe: function(el) {\n $(el).off('.bootstrapTabInputBinding');\n },\n _getTabName: function(anchor) {\n return anchor.attr('data-value') || anchor.text();\n }\n});\n\nShiny.inputBindings.register(bootstrapTabInputBinding, 'shiny.bootstrapTabInput');","// This code creates acustom handler for userMessages\nShiny.addCustomMessageHandler(\"user-messages\", function(message) {\n var id = message.id, action = message.action, content = message.body, index = message.index;\n \n // message text\n // We use Shiny.renderHtml to handle the case where the user pass input/outputs in the updated content that require a new dependency not available in the \n // page at startup. \n if (content.hasOwnProperty(\"text\")) {\n var text;\n if (content.text.html === undefined) {\n text = content.text;\n } else {\n text = Shiny.renderHtml(content.text.html, $([]), content.text.dependencies).html;\n } \n }\n \n // unbind all\n Shiny.unbindAll();\n \n if (action === \"remove\") {\n $(\"#\" + id).find(\".direct-chat-msg\").eq(index - 1).remove();\n } else if (action === \"add\") {\n var author = content.author, date = content.date, image = content.image, type = content.type;\n \n // build the new message \n var authorWrapper, dateWrapper;\n if (type === \"sent\") {\n authorWrapper = '' + author + '';\n dateWrapper = '' + date + '';\n } else {\n authorWrapper = '' + author + '';\n dateWrapper = '' + date + '';\n }\n\n var newMessage = `
    ${authorWrapper}${dateWrapper}
    ${text}
    `;\n \n // build wrapper\n var newMessageWrapper;\n if (type === \"sent\") {\n newMessageWrapper = '
    ' + newMessage + '
    ';\n } else {\n newMessageWrapper = '
    ' + newMessage + '
    ';\n }\n \n // append message\n $(\"#\" + id).find(\".direct-chat-messages\").append(newMessageWrapper);\n } else if (action === \"update\") {\n \n // today's date\n var d = new Date();\n var month = d.getMonth() + 1;\n var day = d.getDate();\n var today = d.getFullYear() + '/' +\n ((''+month).length<2 ? '0' : '') + month + '/' +\n ((''+day).length<2 ? '0' : '') + day;\n \n // we assume only text may be updated. Does not make sense to modify author/date\n \n $(\"#\" + id)\n .find(\".direct-chat-text\")\n .eq(index - 1)\n .replaceWith('
    (modified: ' + today +')
    ' + text + '
    ');\n }\n \n // Calls .initialize() for all of the input objects in all input bindings,\n // in the given scope (document)\n Shiny.initializeInputs();\n Shiny.bindAll(); // bind all inputs/outputs\n});"]} diff --git a/man/dashboardControlbar.Rd b/man/dashboardControlbar.Rd index 7dcf52ed..c91a0b4a 100644 --- a/man/dashboardControlbar.Rd +++ b/man/dashboardControlbar.Rd @@ -16,7 +16,7 @@ bs4DashControlbar( width = 250, collapsed = TRUE, overlay = TRUE, - skin = "dark", + skin = NULL, pinned = NULL ) @@ -47,7 +47,7 @@ dashboardControlbar( width = 250, collapsed = TRUE, overlay = TRUE, - skin = "dark", + skin = NULL, pinned = NULL ) } @@ -65,7 +65,8 @@ in pixels, or a string that specifies the width in CSS units. 250 px by default. \item{overlay}{Whether the sidebar covers the content when expanded. Default to TRUE.} -\item{skin}{Controlbar skin. "dark" or "light".} +\item{skin}{Controlbar skin. "dark" or "light". Matches the \link{dashboardPage} dark parameter +value.} \item{pinned}{Whether to block the controlbar state (TRUE or FALSE). Default to NULL.} diff --git a/man/dashboardSidebar.Rd b/man/dashboardSidebar.Rd index 95bf14ba..9db3a285 100644 --- a/man/dashboardSidebar.Rd +++ b/man/dashboardSidebar.Rd @@ -23,7 +23,7 @@ bs4DashSidebar( ..., disable = FALSE, width = NULL, - skin = "dark", + skin = NULL, status = "primary", elevation = 4, collapsed = FALSE, @@ -85,7 +85,7 @@ dashboardSidebar( ..., disable = FALSE, width = NULL, - skin = "dark", + skin = NULL, status = "primary", elevation = 4, collapsed = FALSE, @@ -152,7 +152,8 @@ updateTabItems( specifies the width in pixels, or a string that specifies the width in CSS units.} -\item{skin}{Sidebar skin. "dark" or "light".} +\item{skin}{Sidebar skin. "dark" or "light". Matches the \link{dashboardPage} dark parameter +value.} \item{status}{Sidebar status. Valid statuses are defined as follows: \itemize{ diff --git a/man/table.Rd b/man/table.Rd index 32e12e27..8c19c0c0 100644 --- a/man/table.Rd +++ b/man/table.Rd @@ -26,9 +26,6 @@ bs4TableItem(..., dataCell = FALSE) \item{...}{Any HTML element.} \item{dataCell}{Whether the cell should be contain data or text. by default.} - -\item{headTitles}{Table header names. Must have the same length as the number of -\link{bs4TableItem} in \link{bs4TableItems}. Set "" to have an empty title field.} } \description{ Build an Bootstrap 4 table container diff --git a/srcjs/bs4Dash-2.3.0/accordions-binding.js b/srcjs/bs4Dash-2.4.0.9000/accordions-binding.js similarity index 100% rename from srcjs/bs4Dash-2.3.0/accordions-binding.js rename to srcjs/bs4Dash-2.4.0.9000/accordions-binding.js diff --git a/srcjs/bs4Dash-2.3.0/cards.js b/srcjs/bs4Dash-2.4.0.9000/cards.js similarity index 100% rename from srcjs/bs4Dash-2.3.0/cards.js rename to srcjs/bs4Dash-2.4.0.9000/cards.js diff --git a/srcjs/bs4Dash-2.3.0/controlbar.js b/srcjs/bs4Dash-2.4.0.9000/controlbar.js similarity index 100% rename from srcjs/bs4Dash-2.3.0/controlbar.js rename to srcjs/bs4Dash-2.4.0.9000/controlbar.js diff --git a/srcjs/bs4Dash-2.3.0/feedbacks.js b/srcjs/bs4Dash-2.4.0.9000/feedbacks.js similarity index 97% rename from srcjs/bs4Dash-2.3.0/feedbacks.js rename to srcjs/bs4Dash-2.4.0.9000/feedbacks.js index 921b8a2d..5d72084a 100644 --- a/srcjs/bs4Dash-2.3.0/feedbacks.js +++ b/srcjs/bs4Dash-2.4.0.9000/feedbacks.js @@ -100,11 +100,13 @@ $(function () { default: console.warn(`${config.status} does not belong to allowed statuses!`) } + closeButton = ''; + if (config.closable) { closeButton = '' } - titleTag = `
    ` + titleTag = `
    ${config.title}
    ` contentTag = config.content; alertTag = `