diff --git a/DESCRIPTION b/DESCRIPTION index 7dbcc7ea..99ea6ac4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -19,11 +19,13 @@ Imports: htmltools (>= 0.5.1.1), jsonlite (>= 0.9.16), fresh, + grDevices, waiter (>= 0.2.3), httpuv (>= 1.5.2), lifecycle, bslib (>= 0.2.4), - httr + httr, + UU (>= 1.5.1) Suggests: knitr, rmarkdown, @@ -35,6 +37,7 @@ Encoding: UTF-8 RoxygenNote: 7.2.1 VignetteBuilder: knitr Collate: + 'aaa_reimports.R' 'feedbacks.R' 'useful-items.R' 'tabs.R' @@ -56,3 +59,5 @@ Collate: 'skinSelector.R' 'utils.R' RdMacros: lifecycle +Remotes: + yogat3ch/UU diff --git a/NAMESPACE b/NAMESPACE index 7e7da764..f994e1fd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,12 @@ # Generated by roxygen2: do not edit by hand +export("%nin%") +export("%|%") +export("%|0|%") +export("%|legit|%") +export("%|try|%") +export("%|zchar|%") +export("%||%") export(accordion) export(accordionItem) export(actionButton) @@ -20,6 +27,7 @@ export(boxProfileItem) export(boxSidebar) export(bs4Accordion) export(bs4AccordionItem) +export(bs4Alert) export(bs4Badge) export(bs4Callout) export(bs4Card) @@ -82,6 +90,7 @@ export(cardSidebar) export(carousel) export(carouselItem) export(closeAlert) +export(col2css) export(column) export(controlbarItem) export(controlbarMenu) @@ -179,7 +188,15 @@ export(userPost) export(userPostMedia) export(userPostTagItem) export(userPostTagItems) +export(validateColors) export(valueBox) export(valueBoxOutput) +importFrom(UU,`%nin%`) +importFrom(UU,`%|0|%`) +importFrom(UU,`%|legit|%`) +importFrom(UU,`%|try|%`) +importFrom(UU,`%|zchar|%`) importFrom(jsonlite,toJSON) importFrom(lifecycle,deprecated) +importFrom(rlang,`%|%`) +importFrom(rlang,`%||%`) diff --git a/R/aaa_reimports.R b/R/aaa_reimports.R new file mode 100644 index 00000000..43dbb0b8 --- /dev/null +++ b/R/aaa_reimports.R @@ -0,0 +1,28 @@ +#' @title Re-imports +#' @name Re-imports +#' @description Useful functions from other packages +#' @importFrom rlang `%||%` `%|%` +#' @importFrom UU `%|0|%` `%|try|%` `%|zchar|%` `%|legit|%` `%nin%` +NULL + + +#' @export +rlang::`%||%` + +#' @export +rlang::`%|%` + +#' @export +UU::`%|try|%` + +#' @export +UU::`%|0|%` + +#' @export +UU::`%|zchar|%` + +#' @export +UU::`%|legit|%` + +#' @export +UU::`%nin%` diff --git a/R/cards.R b/R/cards.R index 878b4548..7d8f0f9e 100644 --- a/R/cards.R +++ b/R/cards.R @@ -69,6 +69,7 @@ #' @param closable If TRUE, display a button in the upper right that allows the user to close the box. #' @param maximizable If TRUE, the card can be displayed in full screen mode. #' @param icon Header icon. Displayed before title. Expect \code{\link[shiny]{icon}}. +#' @param tip_icon Tip icon. Tooltip Icon displayed after title. Expect \code{\link[tippy]{tippy}}. #' @param gradient Whether to allow gradient effect for the background color. Default to FALSE. #' @param boxToolSize Size of the toolbox: choose among "xs", "sm", "md", "lg". #' @param elevation Card elevation. @@ -135,11 +136,29 @@ #' @author David Granjon, \email{dgranjon@@ymail.com} #' #' @export -bs4Card <- function(..., title = NULL, footer = NULL, status = NULL, - solidHeader = FALSE, background = NULL, width = 6, height = NULL, - collapsible = TRUE, collapsed = FALSE, closable = FALSE, maximizable = FALSE, icon = NULL, - gradient = FALSE, boxToolSize = "sm", elevation = NULL, headerBorder = TRUE, label = NULL, dropdownMenu = NULL, - sidebar = NULL, id = NULL) { +bs4Card <- function(..., + title = NULL, + footer = NULL, + status = NULL, + solidHeader = FALSE, + background = NULL, + width = 6, + height = NULL, + collapsible = TRUE, + collapsed = FALSE, + closable = FALSE, + maximizable = FALSE, + icon = NULL, + tip_icon = NULL, + gradient = FALSE, + boxToolSize = "sm", + elevation = NULL, + headerBorder = TRUE, + label = NULL, + dropdownMenu = NULL, + sidebar = NULL, + id = NULL) { + if (is.null(status)) solidHeader <- TRUE @@ -242,7 +261,7 @@ bs4Card <- function(..., title = NULL, footer = NULL, status = NULL, headerTag <- shiny::tags$div( class = if (headerBorder) "card-header" else "card-header border-0", - shiny::tags$h3(class = "card-title", icon, title) + shiny::tags$h3(class = "card-title", tip_icon, icon, title) ) headerTag <- shiny::tagAppendChild(headerTag, cardToolTag) @@ -250,7 +269,6 @@ bs4Card <- function(..., title = NULL, footer = NULL, status = NULL, # body bodyTag <- shiny::tags$div( class = "card-body", - style = style, ..., sidebar[[2]] ) @@ -263,7 +281,7 @@ bs4Card <- function(..., title = NULL, footer = NULL, status = NULL, ) } - cardTag <- shiny::tags$div(class = cardCl, id = id) + cardTag <- shiny::tags$div(class = cardCl, id = id, style = style) cardTag <- shiny::tagAppendChildren(cardTag, headerTag, bodyTag, footerTag) # wrapper @@ -280,6 +298,54 @@ bs4Card <- function(..., title = NULL, footer = NULL, status = NULL, json_verbatim = TRUE ) ) + # , if (maximizable) + # shiny::tags$script( + # type = "text/javascript", + # paste0(" + # $(document).ready(function() { + # var ids = $('div",ifelse(is.null(id), "", paste0("#",id))," div.card-body div').map(function(){ + # return $(this).attr('id'); + # }).get(); + # function resizeBoxContent(trigger, target) { + # var target = '#' + target + # $(trigger).on('click', function() { + # setTimeout(function() { + # var isMaximized = $('html').hasClass('maximized-card'); + # if (isMaximized) { + # $(target).css('height', '100%'); + # $(target).css('width', 'auto'); + # } else { + # $(target).css('height', '400px'); + # $(target).css('width', 'auto'); + # } + # console.log('resizing '+ target) + # }, 300); + # $(target).trigger('resize'); + # }); + # }; + # setTimeout(function() { + # ids.map(function(x){ + # resizeBoxContent('div.card button[data-card-widget=\"maximize\"]', x); + # }) + # + # }, 500); + # console.log(ids); + # });"), + # paste0(" + # $(document).ready(function() { + # $('[data-card-widget=\"maximize\"]').on('click', function() { + # setTimeout(function() { + # var isMaximized = $('html').hasClass('maximized-card'); + # if (isMaximized) { + # window.location.reload() + # } + # }, 300); + # $('",ifelse(is.null(id), "", paste0(id)),"').resize(); + # }); + # } + # ") + # ) + ) } @@ -936,7 +1002,6 @@ bs4InfoBox <- function(title, value = NULL, subtitle = NULL, icon = shiny::icon( elevation = NULL, iconElevation = NULL, tabName = NULL) { # check conditions - tagAssert(icon, "i") if (!is.null(color)) validateStatusPlus(color) if (is.null(color) && (fill || gradient)) { @@ -987,12 +1052,16 @@ bs4InfoBox <- function(title, value = NULL, subtitle = NULL, icon = shiny::icon( } if (!is.null(iconElevation)) infoBoxIconCl <- paste0(infoBoxIconCl, " elevation-", iconElevation) - iconTag <- shiny::tags$span( - class = infoBoxIconCl, - id = if (!is.null(tabName)) paste0("icon-", tabName), - # icon - icon - ) + if (!is.null(icon)) { + tagAssert(icon, "i") + iconTag <- shiny::tags$span( + class = infoBoxIconCl, + id = if (!is.null(tabName)) paste0("icon-", tabName), + # icon + icon + ) + } else + iconTag <- icon contentTag <- shiny::tags$div( diff --git a/R/dashboardHeader.R b/R/dashboardHeader.R index dba27901..08863b99 100644 --- a/R/dashboardHeader.R +++ b/R/dashboardHeader.R @@ -59,23 +59,24 @@ bs4DashNavbar <- function(..., title = NULL, titleWidth = NULL, disable = FALSE, # by the end user. if (skin == "dark" && is.null(status)) status <- "gray-dark" + .types <- c("li", "a", "button", "div", "shiny.tag", "shiny.tag.list") if (!is.null(leftUi)) { if (inherits(leftUi, "shiny.tag.list")) { lapply(leftUi, function(item) { - tagAssert(item, type = "li", class = "dropdown") + tagAssert(item, type = .types) }) } else { - tagAssert(leftUi, type = "li", class = "dropdown") + tagAssert(leftUi, type = .types) } } if (!is.null(rightUi)) { if (inherits(rightUi, "shiny.tag.list")) { lapply(rightUi, function(item) { - tagAssert(item, type = "li", class = "dropdown") + tagAssert(item, type = .types) }) } else { - tagAssert(rightUi, type = "li", class = "dropdown") + tagAssert(rightUi, type = .types) } } diff --git a/R/dashboardSidebar.R b/R/dashboardSidebar.R index 1151d476..714bed2f 100644 --- a/R/dashboardSidebar.R +++ b/R/dashboardSidebar.R @@ -367,7 +367,7 @@ bs4SidebarMenuItem <- function(text, ..., icon = NULL, badgeLabel = NULL, badgeC subItems <- c(list(...), .list) if (!is.null(icon)) { - tagAssert(icon, type = "i") + tagAssert(icon, type = c("i", "img")) icon$attribs$class <- paste0(icon$attribs$class, " nav-icon") } @@ -412,7 +412,7 @@ bs4SidebarMenuItem <- function(text, ..., icon = NULL, badgeLabel = NULL, badgeC # needed by leftSidebar.js `data-start-selected` = if (isTRUE(selected)) 1 else NULL, icon, - shiny::tags$p(text, badgeTag) + shiny::tags$span(text, badgeTag) ) ) ) @@ -454,7 +454,7 @@ bs4SidebarMenuItem <- function(text, ..., icon = NULL, badgeLabel = NULL, badgeC class = "nav-link", `data-start-selected` = if (isTRUE(selected)) 1 else NULL, icon, - shiny::tags$p( + shiny::tags$span( text, shiny::tags$i(class = "right fas fa-angle-left") ) @@ -517,7 +517,7 @@ bs4SidebarMenuSubItem <- function(text, tabName = NULL, href = NULL, # below this is needed by leftSidebar.js `data-start-selected` = if (isTRUE(selected)) 1 else NULL, icon, - shiny::tags$p(text) + shiny::tags$span(text) ) ) } diff --git a/R/useful-items.R b/R/useful-items.R index 334feeb8..49ff6254 100644 --- a/R/useful-items.R +++ b/R/useful-items.R @@ -57,7 +57,16 @@ bs4Badge <- function(..., color, position = c("left", "right"), ) } +#' @title Bootstrap 4 Alert box +#' +#' @param style \code{(character)} Inline style parameters to add +#' @inherit bs4Card params +#' @export +bs4Alert <- function(..., status = "primary", style = NULL, id = NULL, width = 6) { + bs4Dash:::validateStatus(status) + shiny::tags$div(class = paste0("alert alert-",status), role = "alert", ..., style = paste0("margin: 6px 5px 6px 15px;", sapply(\(x) {ifelse(grepl(";$", x), x, paste0(x, ";"))}), id = id)) +} #' Bootstrap 4 accordion container @@ -120,10 +129,10 @@ bs4Badge <- function(..., color, position = c("left", "right"), #' } #' #' @export -bs4Accordion <- function(..., id, width = 12) { +bs4Accordion <- function(..., id, width = 12, collapse_all = TRUE) { items <- list(...) - + if (collapse_all) { # patch that enables a proper accordion behavior # we add the data-parent non standard attribute to each # item. Each accordion must have a unique id. @@ -132,6 +141,12 @@ bs4Accordion <- function(..., id, width = 12) { items[[i]]$children[[1]]$children[[1]]$children[[1]]$attribs$`data-target` <<- paste0("#collapse_", id, "_", i) items[[i]]$children[[2]]$attribs[["id"]] <<- paste0("collapse_", id, "_", i) }) + } else { + lapply(seq_along(items), FUN = function(i) { + items[[i]]$children[[1]]$children[[1]]$children[[1]]$attribs$`data-target` <<- paste0("#collapse_", id, "_", i) + items[[i]]$children[[2]]$attribs[["id"]] <<- paste0("collapse_", id, "_", i) + }) + } shiny::tags$div( class = if (!is.null(width)) paste0("col-sm-", width), @@ -467,14 +482,19 @@ bs4CarouselItem <- function(..., caption = NULL, active = FALSE) { #' #' @param size Progress bar size. NULL, "sm", "xs" or "xxs". #' @param label Progress label. NULL by default. -#' +#' @param id HTML ID. NULL by default +#' @param style \code{chr} CSS Styles applied to each bar +#' @param values_cumulative \code{multiProgressBar} only. Whether \code{value} is comprised of cumulative \code{TRUE} values or actual \code{FALSE} values. See details for examples. #' @md #' @details For `multiProgressBar()`, `value` can be a vector which #' corresponds to the progress for each segment within the progress bar. #' If supplied, `striped`, `animated`, `status`, and `label` must be the #' same length as `value` or length 1, in which case vector recycling is -#' used. -#' +#' used. The `values_cumulative` argument is described below: +#' \itemize{ +#' \item{cumulative}{ values = c(10, 20, 30) is interpreted as a 10% width bar, a 20% width bar, and a 30% width bar for a total of 60%} +#' \item{actual}{ values = c(10, 20, 30) is interpreted as a 10% width bar, a 10% width bar, and a 10% width bar for a total of 30%. These values are scaled by \code{min} & \code{max} arguments.} +#' } #' @examples #' if(interactive()){ #' library(shiny) @@ -549,42 +569,55 @@ bs4CarouselItem <- function(..., caption = NULL, active = FALSE) { #' @export bs4ProgressBar <- function (value, min = 0, max = 100, vertical = FALSE, striped = FALSE, animated = FALSE, status = "primary", size = NULL, - label = NULL) { + label = NULL, + style = NULL, + id = NULL) { - if (!is.null(status)) validateStatusPlus(status) + if (!is.null(status)) validateColors(status) stopifnot(value >= min) stopifnot(value <= max) # wrapper class - progressCl <- if (isTRUE(vertical)) "progress vertical" else "progress mb-3" + progressCl <- if (isTRUE(vertical)) "progress vertical" else "progress" if (!is.null(size)) progressCl <- paste0(progressCl, " progress-", size) # bar class barCl <- "progress-bar" - if (!is.null(status)) barCl <- paste0(barCl, " bg-", status) + style <- NULL + if (!is.null(status) && status %in% validStatusesPlus) barCl <- paste0(barCl, " bg-", status) + else if (status %in% validColorsPlus || is_hex_color(status)) + style <- paste0(style, "background-color: ", col2css(status), ";") if (striped) barCl <- paste0(barCl, " progress-bar-striped") if (animated) barCl <- paste0(barCl, " progress-bar-animated") # wrapper barTag <- shiny::tags$div( + id = id, class = barCl, role = "progressbar", `aria-valuenow` = value, `aria-valuemin` = min, `aria-valuemax` = max, - style = if (vertical) { - paste0("height: ", paste0(value, "%")) - } - else { - paste0("width: ", paste0(value, "%")) - }, + style = paste0(style, ifelse(vertical, "height: ", "width: "), ((value - min) / (max - min) * 100), "%"), if(!is.null(label)) label ) - progressTag <- shiny::tags$div(class = progressCl) + progressTag <- shiny::tags$div(id = id, style = style, class = progressCl) progressTag <- shiny::tagAppendChild(progressTag, barTag) progressTag } +# Scale values to a percentage/decimal by min & max +val2pct <- function(val, min = 0, max = 100, include_min = TRUE, include_max = FALSE, as_percent = TRUE) { + .val <- sort(val) + if (include_min) + .val <- c(min, .val) + if (include_max) + .val <- c(.val, max) + out <- diff((.val - min) / (max - min)) + if (as_percent) + out <- out * 100 + out +} #' @rdname progress #' @export @@ -598,22 +631,36 @@ bs4MultiProgressBar <- animated = FALSE, status = "primary", size = NULL, - label = NULL + label = NULL, + id = NULL, + values_cumulative = TRUE, + style = NULL ) { status <- verify_compatible_lengths(value, status) striped <- verify_compatible_lengths(value, striped) animated <- verify_compatible_lengths(value, animated) if (!is.null(label)) label <- verify_compatible_lengths(value, label) - if (!is.null(status)) lapply(status, function(x) validateStatusPlus(x)) + if (!is.null(status)) lapply(status, function(x) validateColors(x)) stopifnot(all(value >= min)) stopifnot(all(value <= max)) - stopifnot(sum(value) <= max) + if (!values_cumulative) { + .value <- val2pct(value, min = min, max = max) + stopifnot(sum(.value) <= 100) + } else { + .value <- value + stopifnot(sum(value) <= max) + } + bar_segment <- function(value, striped, animated, status, label) { # bar class barCl <- "progress-bar" - if (!is.null(status)) barCl <- paste0(barCl, " bg-", status) + if (!is.null(status) && status %in% validStatusesPlus) barCl <- paste0(barCl, " bg-", status) + else if (status %in% validColorsPlus || is_hex_color(status)) + style <- paste0(style, "background-color: ", col2css(status), ";") + + if (striped) barCl <- paste0(barCl, " progress-bar-striped") if (animated) barCl <- paste0(barCl, " progress-bar-animated") @@ -623,22 +670,25 @@ bs4MultiProgressBar <- `aria-valuenow` = value, `aria-valuemin` = min, `aria-valuemax` = max, - style = if (vertical) { - paste0("height: ", paste0(value, "%")) - } - else { - paste0("width: ", paste0(value, "%")) - }, + style = paste0(style, ifelse(vertical, "height: ", "width: "), value, "%;", ifelse(vertical && values_cumulative, "position:relative;", "")), if(!is.null(label)) label ) } + if (vertical && values_cumulative) { + # the vertical bar has the divs rendered in order so it appears top down. This reverses the order so it appears bottom up as is more intuitive. + .value <- rev(.value) + status <- rev(status) + striped <- rev(striped) + animated <- rev(animated) + label <- rev(label) + } barSegs <- list() # progress bar segments for (i in seq_along(value)) { barSegs[[i]] <- bar_segment( - value[[i]], + .value[[i]], striped[[i]], animated[[i]], status[[i]], @@ -647,9 +697,9 @@ bs4MultiProgressBar <- } # wrapper class - progressCl <- if (isTRUE(vertical)) "progress vertical" else "progress mb-3" + progressCl <- if (isTRUE(vertical)) "progress vertical" else "progress" if (!is.null(size)) progressCl <- paste0(progressCl, " progress-", size) - progressTag <- shiny::tags$div(class = progressCl) + progressTag <- shiny::tags$div(id = id, style = style, class = progressCl) progressTag <- shiny::tagAppendChild(progressTag, barSegs) progressTag } diff --git a/R/utils.R b/R/utils.R index 464f7af2..c81de609 100644 --- a/R/utils.R +++ b/R/utils.R @@ -8,11 +8,6 @@ #' contents. #' @keywords internal tagAssert <- function(tag, type = NULL, class = NULL, allowUI = TRUE) { - if (!inherits(tag, "shiny.tag")) { - print(tag) - stop("Expected an object with class 'shiny.tag'.") - } - # Skip dynamic output elements if (allowUI && (hasCssClass(tag, "shiny-html-output") || @@ -20,8 +15,8 @@ tagAssert <- function(tag, type = NULL, class = NULL, allowUI = TRUE) { return() } - if (!is.null(type) && tag$name != type) { - stop("Expected tag to be of type ", type) + if (!is.null(type) && !(tag$name %||% "" %in% type || inherits(tag, type))) { + stop("Expected tag to be of type(s) ", paste0(type, collapse = ", ")) } if (!is.null(class)) { @@ -212,6 +207,52 @@ validateStatusPlus <- function(status) { #' @keywords internal validStatusesPlus <- c(validStatuses, validNuances, validColors) +#' All valid colors +#' @usage NULL +#' @format NULL +#' +#' @keywords internal +validColorsPlus <- c(grDevices::colours(), validStatusesPlus) + +# is string a hex formatted color +is_hex_color <- function(color) { + grepl("^\\#[a-fA-F0-9]{3,6}", color) +} +# is string an rgba formatted color +is_rgba_color <- function(color) { + grepl("^rgba\\(", color) +} + +#' Validate all colors & Bootstrap statuses +#' +#' @param color \code{chr} string with status name or color name +#' +#' @return \code{lgl} +#' @export +#' @seealso grDevices::color +validateColors <- function(color) { + if (color %in% validColorsPlus || is_hex_color(color) || is_rgba_color(color)) + TRUE + else + stop("Invalid color: ", color, ". Valid colors are hex or rgba formatted or one of the following: ", + paste(validColorsPlus, collapse = ", "), ".") +} + + +#' Create a CSS rgba declaration for a color name +#' +#' @param color \code{chr} Color name, see \link[grDevices]{colors} +#' @param alpha \code{num} alpha value +#' +#' @return \code{chr} css `rgba()` formatted color +#' @export + +col2css <- function(color, alpha = NULL) { + if (!is_hex_color(color) && !is_rgba_color(color)) + paste0("rgba(", paste0(c(grDevices::col2rgb(color), alpha), collapse = ", "), ")") + else + color +} diff --git a/inst/bs4Dash-2.0.2/bs4Dash-old.min.js b/inst/bs4Dash-2.0.2/bs4Dash-old.min.js new file mode 100644 index 00000000..9f0aca32 --- /dev/null +++ b/inst/bs4Dash-2.0.2/bs4Dash-old.min.js @@ -0,0 +1,2 @@ +var accordionBinding=new Shiny.InputBinding;$.extend(accordionBinding,{find:function(a){return $(a).find(".accordion")},getValue:function(a){var t=$(a).find(".active").index()+1;if(0!==t)return t},setValue:function(a,t){$(a).find(".active").removeClass("active"),$(a).children().eq(t-1).addClass("active"),$(a).children().eq(t-1).find('[data-toggle="collapse"]').click(),$(a).trigger("change")},receiveMessage:function(a,t){this.setValue(a,t)},subscribe:function(a,t){$(a).on("change",(function(a){t()})),$(a).find('[data-toggle="collapse"]').on("click",(function(e){$(this).closest(".card").hasClass("active")||$(a).find(".active").removeClass("active"),$(this).closest(".card").addClass("active"),t()}))},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")},getValue:function(a){var t=$(a).parent().find("script[data-for='"+a.id+"']");t=JSON.parse(t.html());var e,i=$(a).hasClass("collapsed-card"),n=$(a).css("display"),s=$(a).hasClass("maximized-card");return e="none"!==n,s?$(a).find("[data-card-widget = 'collapse']").hide():$(a).find("[data-card-widget = 'collapse']").show(),{collapsible:t.collapsible,collapsed:i,closable:t.closable,visible:e,maximizable:t.maximizable,maximized:s,status:t.status,solidHeader:t.solidHeader,background:t.background,width:t.width,height:t.height}},_updateWidth:function(a,t,e){$(a).parent().toggleClass("col-sm-"+t),$(a).parent().addClass("col-sm-"+e),$(a).trigger("resize")},setValue:function(a,t){var e=$(a).parent().find("script[data-for='"+a.id+"']");if(e=JSON.parse(e.html()),"update"===t.action){var i=$(a).hasClass("user-card"),n=$(a).hasClass("social-card");if(t.options.hasOwnProperty("title")&&t.options.title!==e.title){var s;s="string"!=typeof t.options.title?$.parseHTML(t.options.title[0]):$.parseHTML(t.options.title);var r=$(a).find(".card-tools");n?$(a).find(".user-block").replaceWith($(s)):i?("string"==typeof t.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!==t.options.status&&(t.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))),e.title=t.options.title}if(t.options.hasOwnProperty("collapsible")&&t.options.collapsible!==e.collapsible&&(t.options.collapsible?0===$(a).find('[data-card-widget = "collapse"]').length&&($(a).find(".card-tools.float-right").prepend($('')),e.collapsible=!0):($(a).find('[data-card-widget = "collapse"]').remove(),e.collapsible=!1)),t.options.hasOwnProperty("closable")&&t.options.closable!==e.closable&&(t.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"]')),e.closable=!0):($(a).find('[data-card-widget = "remove"]').remove(),e.closable=!1)),t.options.hasOwnProperty("maximizable")&&t.options.maximizable!==e.maximizable&&(t.options.maximizable?0===$(a).find('[data-card-widget = "maximize"]').length&&($(a).find(".card-tools.float-right").append($('')),e.maximizable=!0):($(a).find('[data-card-widget = "maximize"]').remove(),e.maximizable=!1)),t.options.hasOwnProperty("solidHeader")&&!n&&!i)if(t.options.solidHeader!==e.solidHeader&&$(a).hasClass("card-outline"))$(a).removeClass("card-outline"),e.solidHeader=!0;else if($(a).hasClass("card-outline")||t.options.solidHeader){if($(a).hasClass("card-outline")){o=e.status||t.options.status;t.options.background&&o?($(a).removeClass("card-outline"),e.solidHeader=!0):e.background&&o&&($(a).removeClass("card-outline"),e.solidHeader=!1)}}else{var o=e.status||t.options.status;t.options.background&&o&&(null!==t.options.background||e.background&&o)||($(a).addClass("card-outline"),e.solidHeader=!1)}if(t.options.hasOwnProperty("status")&&!n&&t.options.status!==e.status){var d,l,c;if(null===t.options.status&&null!==e.status){if(i||$(a).removeClass("card-"+e.status),$(a).hasClass("card-outline")&&!i&&$(a).addClass("card-outline"),t.options.background){var u=t.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 t.options.status&&(i?(l="bg-",t.options.gradient&&(l+="gradient-"),l+=t.options.status,$(a).find(".widget-user-header").addClass(l)):(l="card-"+t.options.status,$(a).addClass(l)),e.status&&(i?(d="bg-",e.gradient&&(d+="gradient-"),d+=e.status,$(a).find(".widget-user-header").removeClass(d)):(d="card-"+e.status,$(a).removeClass(d))),$(a).hasClass("card-outline")&&!i||(validStatusesPlus.indexOf(t.options.status)>-1?$(a).find(".btn-tool").addClass("bg-"+t.options.status):validStatuses.indexOf(t.options.status)>-1&&$(a).find(".btn-tool").addClass("btn-"+t.options.status)));(e.status||e.background)&&(e.status?c=e.status:e.background&&(c=e.background),validStatusesPlus.indexOf(c)>-1?$(a).find(".btn-tool").removeClass("bg-"+c):validStatuses.indexOf(c)>-1&&$(a).find(".btn-tool").removeClass("btn-"+c)),e.status=t.options.status}if(t.options.hasOwnProperty("background")&&t.options.background!==e.background){var b="bg-";if(newBgClass=b,e.background){if(e.gradient&&(b+="gradient-"),b+=e.background,i&&!e.status&&!t.options.status){var h=$(a).find(".widget-user-header");$(h).removeClass(b)}$(a).removeClass(b)}if(t.options.background){if((e.gradient||t.options.gradient)&&(newBgClass+="gradient-"),newBgClass+=t.options.background,i&&!e.status&&!t.options.status){h=$(a).find(".widget-user-header");$(h).addClass(newBgClass)}$(a).addClass(newBgClass)}e.gradient!==t.options.gradient&&void 0!==t.options.gradient&&(e.gradient=t.options.gradient),e.background=t.options.background}t.options.hasOwnProperty("width")&&t.options.width!==e.width&&(this._updateWidth(a,e.width,t.options.width),e.width=t.options.width),t.options.hasOwnProperty("height")&&t.options.height!==e.height&&(null===t.options.height?$(a).find(".card-body").css("height",""):$(a).find(".card-body").css("height",t.options.height),e.height=t.options.height),$(a).parent().find("script[data-for='"+a.id+"']").replaceWith('