diff --git a/NAMESPACE b/NAMESPACE
index ba08cf5..f35a95e 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -4,10 +4,12 @@ export(add_busy_bar)
export(add_busy_gif)
export(add_busy_spinner)
export(add_loading_state)
+export(block)
export(busy_start_up)
export(config_notify)
export(config_report)
export(hide_spinner)
+export(html_dependency_block)
export(html_dependency_busy)
export(html_dependency_epic)
export(html_dependency_freezeframe)
@@ -46,6 +48,7 @@ export(show_spinner)
export(spin_epic)
export(spin_kit)
export(stop_gif)
+export(unblock)
export(update_busy_bar)
export(update_modal_progress)
export(update_modal_spinner)
diff --git a/NEWS.md b/NEWS.md
index dd62f5a..9505874 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,8 @@
+# shinybusy 0.3.2
+
+* New functions `block()` and `unblock()` to block/unblock elements (like outputs) during a lonng calculation.
+
+
# shinybusy 0.3.1
* Use correct HTML dependencies.
diff --git a/R/block.R b/R/block.R
new file mode 100644
index 0000000..33d3513
--- /dev/null
+++ b/R/block.R
@@ -0,0 +1,62 @@
+
+#' Block / unblock an UI element
+#'
+#' @param id Id of the element to block, for exemple an `outputId`.
+#' @param text Text displayed below the blocking indicator.
+#' @param type Type of blocking indicator.
+#' @param ... Other configuration option, see [online documentation](https://notiflix.github.io/documentation#DocsBlock).
+#' @param selector Custom CSS selector, if used `id` is ignored.
+#' @param session Default Shiny session.
+#'
+#' @return No value.
+#' @export
+#'
+#' @name block
+#'
+#' @example examples/block.R
+block <- function(id,
+ text = "Loading",
+ type = c("standard", "hourglass", "circle", "arrows", "dots", "pulse"),
+ ...,
+ selector = NULL,
+ session = shiny::getDefaultReactiveDomain()) {
+ type <- match.arg(type)
+ insertUI(
+ selector = "html",
+ ui = tagList(html_dependency_block()),
+ immediate = TRUE,
+ where = "afterBegin",
+ session = session
+ )
+ if (is.null(selector))
+ selector <- paste0("#", session$ns(id))
+ session$sendCustomMessage(
+ type = "shinybusy-block-output",
+ message = list(
+ selector = selector,
+ type = type,
+ text = text,
+ config = list(...)
+ )
+ )
+}
+
+#' @param timeout Unblock after a delay.
+#' @export
+#'
+#' @rdname block
+unblock <- function(id,
+ selector = NULL,
+ timeout = 0,
+ session = shiny::getDefaultReactiveDomain()) {
+ if (is.null(selector))
+ selector <- paste0("#", session$ns(id))
+ session$sendCustomMessage(
+ type = "shinybusy-unblock-output",
+ message = list(
+ selector = selector,
+ timeout = timeout
+ )
+ )
+}
+
diff --git a/R/dependencies.R b/R/dependencies.R
index 12e3258..0300291 100644
--- a/R/dependencies.R
+++ b/R/dependencies.R
@@ -150,3 +150,16 @@ html_dependency_report <- function() {
script = "report.js"
)
}
+
+#' @export
+#' @rdname html-dependencies
+html_dependency_block <- function() {
+ htmlDependency(
+ name = "shinybusy-block",
+ version = packageVersion("shinybusy"),
+ src = list(file = "packer"),
+ package = "shinybusy",
+ script = "block.js",
+ head = ""
+ )
+}
diff --git a/examples/block.R b/examples/block.R
new file mode 100644
index 0000000..ff5aa5d
--- /dev/null
+++ b/examples/block.R
@@ -0,0 +1,62 @@
+library(shinybusy)
+library(shiny)
+
+ui <- fluidPage(
+
+ tags$h3("Block Output"),
+
+
+ fluidRow(
+ column(
+ width = 6,
+ plotOutput(outputId = "plot1"),
+ actionButton("block_manually", "Block / unblock")
+ ),
+ column(
+ width = 6,
+ plotOutput(outputId = "plot2"),
+ actionButton("block_reac", "Block when calculating in reactive()")
+ )
+ )
+
+)
+
+server <- function(input, output, session) {
+
+ output$plot1 <- renderPlot({
+ barplot(table(floor(runif(100) * 6)))
+ })
+
+ observeEvent(input$block_manually, {
+ if (input$block_manually %% 2 == 1) {
+ block(id = "plot1", type = "pulse", svgColor = "#5ea4d8")
+ } else {
+ unblock(id = "plot1")
+ }
+ })
+
+ data_r <- reactive({
+ input$block_reac
+ block(
+ id = "plot2",
+ type = "circle",
+ text = "Calculating, please wait...",
+ messageColor = "#FFF",
+ svgColor = "#FFF",
+ backgroundColor = "#5ea4d8"
+ )
+ Sys.sleep(3)
+ data <- data.frame(x = rnorm(50), y = rnorm(50))
+ unblock(id = "plot2", timeout = 300)
+ return(data)
+ })
+
+ output$plot2 <- renderPlot({
+ plot(data_r())
+ })
+
+}
+
+if (interactive())
+ shinyApp(ui, server)
+
diff --git a/inst/packer/block.js b/inst/packer/block.js
new file mode 100644
index 0000000..8c7b71f
--- /dev/null
+++ b/inst/packer/block.js
@@ -0,0 +1 @@
+(()=>{var e={666:function(e,t,i){var a,o;o=void 0!==i.g?i.g:"undefined"!=typeof window?window:this,a=function(){return function(e){"use strict";if(void 0===e&&void 0===e.document)return!1;var t,i="\n\nVisit documentation page to learn more: https://notiflix.github.io/documentation",a="Standard",o="Hourglass",n="Circle",r="Arrows",s="Dots",l="Pulse",c={ID:"NotiflixBlockWrap",querySelectorLimit:200,className:"notiflix-block",position:"absolute",zindex:1e3,backgroundColor:"rgba(255,255,255,0.9)",rtl:!1,fontFamily:"Quicksand",cssAnimation:!0,cssAnimationDuration:300,svgSize:"45px",svgColor:"#383838",messageFontSize:"14px",messageMaxLength:34,messageColor:"#383838"},m=function(e){return console.error("%c Notiflix Error ","padding:2px;border-radius:20px;color:#fff;background:#ff5549","\n"+e+i)},f=function(t){return t||(t="head"),null!==e.document[t]||(m('\nNotiflix needs to be appended to the "<'+t+'>" element, but you called it before the "<'+t+'>" element has been created.'),!1)},u=function(){var e={},t=!1,i=0;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(t=arguments[0],i++);for(var a=function(i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(t&&"[object Object]"===Object.prototype.toString.call(i[a])?e[a]=u(e[a],i[a]):e[a]=i[a])};i'},p=function(e,t){return e||(e="60px"),t||(t="#32c682"),''},b=function(e,t){return e||(e="60px"),t||(t="#32c682"),''},h=function(e,t){return e||(e="60px"),t||(t="#32c682"),''},k=function(e,t){return e||(e="60px"),t||(t="#32c682"),''},y=function(){return'[id^=NotiflixBlockWrap]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;box-sizing:border-box;position:absolute;z-index:1000;font-family:"Quicksand",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;background:rgba(255,255,255,.9);text-align:center;animation-duration:.4s;width:100%;height:100%;left:0;top:0;border-radius:inherit;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}[id^=NotiflixBlockWrap] *{-webkit-box-sizing:border-box;box-sizing:border-box}[id^=NotiflixBlockWrap]>span[class*="-icon"]{display:block;width:45px;height:45px;position:relative;margin:0 auto}[id^=NotiflixBlockWrap]>span[class*="-icon"] svg{width:inherit;height:inherit}[id^=NotiflixBlockWrap]>span[class*="-message"]{position:relative;display:block;width:100%;margin:10px auto 0;padding:0 10px;font-family:inherit!important;font-weight:normal;font-size:14px;line-height:1.4}[id^=NotiflixBlockWrap].nx-with-animation{-webkit-animation:block-animation-fade .3s ease-in-out 0s normal;animation:block-animation-fade .3s ease-in-out 0s normal}@-webkit-keyframes block-animation-fade{0%{opacity:0}100%{opacity:1}}@keyframes block-animation-fade{0%{opacity:0}100%{opacity:1}}[id^=NotiflixBlockWrap].nx-with-animation.nx-remove{opacity:0;-webkit-animation:block-animation-fade-remove .3s ease-in-out 0s normal;animation:block-animation-fade-remove .3s ease-in-out 0s normal}@-webkit-keyframes block-animation-fade-remove{0%{opacity:1}100%{opacity:0}}@keyframes block-animation-fade-remove{0%{opacity:1}100%{opacity:0}}'},w=0,v=function(a,y,v,N,S,C){var B;if(Array.isArray(v)){if(v.length<1)return m("Array of HTMLElements should contains at least one HTMLElement."),!1;B=v}else if(Object.prototype.isPrototypeOf.call(NodeList.prototype,v)){if(v.length<1)return m("NodeListOf should contains at least one HTMLElement."),!1;B=Array.prototype.slice.call(v)}else{if("string"!=typeof v||(v||"").length<1||1===(v||"").length&&("#"===(v||"")[0]||"."===(v||"")[0]))return m("The selector parameter must be a string and matches a specified CSS selector(s)."),!1;var z=e.document.querySelectorAll(v);if(z.length<1)return m('You called the "Notiflix.Block..." function with "'+v+'" selector, but there is no such element(s) in the document.'),!1;B=z}t||x.Block.init({});var L=u(!0,t,{});if("object"==typeof N&&!Array.isArray(N)||"object"==typeof S&&!Array.isArray(S)){var T={};"object"==typeof N?T=N:"object"==typeof S&&(T=S),t=u(!0,t,T)}var M="";"string"==typeof N&&N.length>0&&(M=N),t.cssAnimation||(t.cssAnimationDuration=0);var A=c.className;"string"==typeof t.className&&(A=t.className.trim());var H,j,X="number"==typeof t.querySelectorLimit?t.querySelectorLimit:200,I=(B||[]).length>=X?X:B.length,D="nx-block-temporary-position";if(a)for(var E=["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","html","head","title","script","style","iframe"],O=0;O-1)break;var W=F.querySelectorAll("[id^="+c.ID+"]");if(W.length<1){var q="";y&&(q=y===o?(H=t.svgSize,j=t.svgColor,H||(H="60px"),j||(j="#32c682"),''):y===n?p(t.svgSize,t.svgColor):y===r?b(t.svgSize,t.svgColor):y===s?h(t.svgSize,t.svgColor):y===l?k(t.svgSize,t.svgColor):g(t.svgSize,t.svgColor));var P=''+q+"",V="";M.length>0&&(M=M.length>t.messageMaxLength?d(M).substring(0,t.messageMaxLength)+"...":d(M),V=''+M+""),w++;var R=e.document.createElement("div");R.id=c.ID+"-"+w,R.className=A+(t.cssAnimation?" nx-with-animation":""),R.style.position=t.position,R.style.zIndex=t.zindex,R.style.background=t.backgroundColor,R.style.animationDuration=t.cssAnimationDuration+"ms",R.style.fontFamily='"'+t.fontFamily+'", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif',R.style.display="flex",R.style.flexWrap="wrap",R.style.flexDirection="column",R.style.alignItems="center",R.style.justifyContent="center",t.rtl&&(R.setAttribute("dir","rtl"),R.classList.add("nx-rtl-on")),R.innerHTML=P+V;var Q=e.getComputedStyle(F).getPropertyValue("position"),U="string"==typeof Q?Q.toLocaleLowerCase("en"):"relative",Y=Math.round(1.25*parseInt(t.svgSize))+40,G="";Y>(F.offsetHeight||0)&&(G="min-height:"+Y+"px;");var J;J=F.getAttribute("id")?"#"+F.getAttribute("id"):F.classList[0]?"."+F.classList[0]:(F.tagName||"").toLocaleLowerCase("en");var K="",Z=["absolute","relative","fixed","sticky"].indexOf(U)<=-1;if(Z||G.length>0){if(!f("head"))return!1;Z&&(K="position:relative!important;");var $='",_=e.document.createRange();_.selectNode(e.document.head);var ee=_.createContextualFragment($);e.document.head.appendChild(ee),F.classList.add(D)}F.appendChild(R)}}}else var te=function(i){var a=setTimeout((function(){null!==i.parentNode&&i.parentNode.removeChild(i);var t=i.getAttribute("id"),o=e.document.getElementById("Style-"+t);o&&null!==o.parentNode&&o.parentNode.removeChild(o),clearTimeout(a)}),t.cssAnimationDuration)},ie=function(e){if(e&&e.length>0)for(var t=0;t" or "NodeListOf" does not have a "Block" element to remove.')},ae=function(e){var i=setTimeout((function(){e.classList.remove(D),clearTimeout(i)}),t.cssAnimationDuration+300)},oe=setTimeout((function(){for(var e=0;e{"use strict";Shiny;var e=i(666);Shiny.addCustomMessageHandler("shinybusy-block-output",(function(t){t.hasOwnProperty("text")?e.Block[t.type](t.selector,t.text,t.config):e.Block[t.type](t.selector,t.config)})),Shiny.addCustomMessageHandler("shinybusy-unblock-output",(function(t){e.Block.remove(t.selector,t.timeout)}))})()})();
\ No newline at end of file
diff --git a/man/block.Rd b/man/block.Rd
new file mode 100644
index 0000000..dfcd306
--- /dev/null
+++ b/man/block.Rd
@@ -0,0 +1,108 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/block.R
+\name{block}
+\alias{block}
+\alias{unblock}
+\title{Block / unblock an UI element}
+\usage{
+block(
+ id,
+ text = "Loading",
+ type = c("standard", "hourglass", "circle", "arrows", "dots", "pulse"),
+ ...,
+ selector = NULL,
+ session = shiny::getDefaultReactiveDomain()
+)
+
+unblock(
+ id,
+ selector = NULL,
+ timeout = 0,
+ session = shiny::getDefaultReactiveDomain()
+)
+}
+\arguments{
+\item{id}{Id of the element to block, for exemple an \code{outputId}.}
+
+\item{text}{Text displayed below the blocking indicator.}
+
+\item{type}{Type of blocking indicator.}
+
+\item{...}{Other configuration option, see \href{https://notiflix.github.io/documentation#DocsBlock}{online documentation}.}
+
+\item{selector}{Custom CSS selector, if used \code{id} is ignored.}
+
+\item{session}{Default Shiny session.}
+
+\item{timeout}{Unblock after a delay.}
+}
+\value{
+No value.
+}
+\description{
+Block / unblock an UI element
+}
+\examples{
+library(shinybusy)
+library(shiny)
+
+ui <- fluidPage(
+
+ tags$h3("Block Output"),
+
+
+ fluidRow(
+ column(
+ width = 6,
+ plotOutput(outputId = "plot1"),
+ actionButton("block_manually", "Block / unblock")
+ ),
+ column(
+ width = 6,
+ plotOutput(outputId = "plot2"),
+ actionButton("block_reac", "Block when calculating in reactive()")
+ )
+ )
+
+)
+
+server <- function(input, output, session) {
+
+ output$plot1 <- renderPlot({
+ barplot(table(floor(runif(100) * 6)))
+ })
+
+ observeEvent(input$block_manually, {
+ if (input$block_manually \%\% 2 == 1) {
+ block(id = "plot1", type = "pulse", svgColor = "#5ea4d8")
+ } else {
+ unblock(id = "plot1")
+ }
+ })
+
+ data_r <- reactive({
+ input$block_reac
+ block(
+ id = "plot2",
+ type = "circle",
+ text = "Calculating, please wait...",
+ messageColor = "#FFF",
+ svgColor = "#FFF",
+ backgroundColor = "#5ea4d8"
+ )
+ Sys.sleep(3)
+ data <- data.frame(x = rnorm(50), y = rnorm(50))
+ unblock(id = "plot2", timeout = 300)
+ return(data)
+ })
+
+ output$plot2 <- renderPlot({
+ plot(data_r())
+ })
+
+}
+
+if (interactive())
+ shinyApp(ui, server)
+
+}
diff --git a/man/html-dependencies.Rd b/man/html-dependencies.Rd
index ea49871..175a4fb 100644
--- a/man/html-dependencies.Rd
+++ b/man/html-dependencies.Rd
@@ -13,6 +13,7 @@
\alias{html_dependency_busy}
\alias{html_dependency_notify}
\alias{html_dependency_report}
+\alias{html_dependency_block}
\title{HTML dependencies used by shinybusy}
\usage{
html_dependency_spinkit()
@@ -36,6 +37,8 @@ html_dependency_busy()
html_dependency_notify()
html_dependency_report()
+
+html_dependency_block()
}
\value{
an \code{\link[htmltools:htmlDependency]{htmltools::htmlDependency()}}.
diff --git a/srcjs/config/entry_points.json b/srcjs/config/entry_points.json
index e801c2b..a1d301e 100644
--- a/srcjs/config/entry_points.json
+++ b/srcjs/config/entry_points.json
@@ -3,5 +3,6 @@
"loading": "./srcjs/exts/loading.js",
"busy": "./srcjs/exts/busy.js",
"notify": "./srcjs/exts/notify.js",
- "report": "./srcjs/exts/report.js"
+ "report": "./srcjs/exts/report.js",
+ "block": "./srcjs/exts/block.js"
}
diff --git a/srcjs/exts/block.js b/srcjs/exts/block.js
new file mode 100644
index 0000000..e327fe0
--- /dev/null
+++ b/srcjs/exts/block.js
@@ -0,0 +1,13 @@
+import "shiny";
+import { Block } from 'notiflix/build/notiflix-block-aio';
+
+Shiny.addCustomMessageHandler("shinybusy-block-output", function(data) {
+ if (data.hasOwnProperty("text")) {
+ Block[data.type](data.selector, data.text, data.config);
+ } else {
+ Block[data.type](data.selector,data.config);
+ }
+});
+Shiny.addCustomMessageHandler("shinybusy-unblock-output", function(data) {
+ Block.remove(data.selector, data.timeout);
+});