Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added dismissalert to remove arbitrary number of messages #30

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
.RData
.Ruserdata
inst/doc
.DS_Store
4 changes: 3 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ Authors@R: c(
person("Dean", "Attali", email = "daattali@gmail.com",
role = c("aut", "cre"), comment = "R interface"),
person("Tristan", "Edwards", role = c("aut"),
comment = "sweetalert library")
comment = "sweetalert library"),
person("Zhengjia", "Wang", email = "dipterix.wang@gmail.com",
role = c("ctb"))
)
Description: Easily create pretty popup messages (modals) in 'Shiny'. A modal can
contain text, images, OK/Cancel buttons, an input to get a response from the
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand

export(closeAlert)
export(runExample)
export(shinyalert)
export(useShinyalert)
34 changes: 33 additions & 1 deletion R/shinyalert.R
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
#' close automatically. Use \code{0} to not close the modal automatically
#' (default). If the modal closes automatically, no value is returned from the
#' modal. See the 'Modal return value' section below.
#' @param immediate Whether to close previously opened alerts and display
#' current message immediately; default is \code{FALSE}.
#' @param animation If \code{FALSE}, the modal's animation will be disabled.
#' Possible values: \code{FALSE}, \code{TRUE}, \code{"slide-from-top"},
#' \code{"slide-from-bottom"}, \code{"pop"} (the default animation when
Expand Down Expand Up @@ -197,6 +199,7 @@ shinyalert <- function(
confirmButtonCol = "#AEDEF4",
cancelButtonText = "Cancel",
timer = 0,
immediate = FALSE,
animation = TRUE,
imageUrl = NULL,
imageWidth = 100,
Expand Down Expand Up @@ -238,6 +241,11 @@ shinyalert <- function(

session <- getSession()

if( immediate ){
# Close all previously opened alerts and display this one at once
closeAlert()
}

# If an R callback function is provided, create an observer for it
if (!is.null(callbackR)) {
cbid <- paste0("__shinyalert-", gsub("-", "", uuid::UUIDgenerate()))
Expand All @@ -250,6 +258,15 @@ shinyalert <- function(
}
}, once = TRUE)
params[['callbackR']] <- NULL
} else {
# Still create `cbid` used by closeAlert to explicitly close this alert
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the if statement I added a comment that the serialization isn't happening to prevent a performance issue #19

Did you notice that and test if this change is going to re-introduce that issue?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't recommend params <- as.list(environment()) at the very beginning. Chances are too small for two alerts sharing the same cbid with runif postfix. If you get rid of the enclosing environment, digest::digest will be quite fast as everything else should be basic types and contain no environment. Alternatively you could use utils::capture.output({print(environment())}) to distinguish environments because the runtime environment of R functions will unlikely to be named, meaning its memory address will be printed, resulting in something like "<environment: 0x7f802d60a1b0>". Serializing string is much faster than serializing an environment.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, if you do want to keep params <- as.list(environment()), you can create random cbid with no digests for alerts without callbacks. The returned cbid can be used to close that specific alert or writers can use that cbid to generate their own callbacks outside of shinyalert.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line params <- as.list(environment()) is not for serialization or creating a unique ID, it was simply a way to get a list of all the function arguments values - default values for arguments that weren't provided and the user-supplied values for the arguments that were provided. Do you know of an alternative way to get all the arguments of the current function as a list?

But anyway, you're right that the digest isn't actually required , it was only to add some randomness. Instead of using the params hash plus a random integer, it might be better to just use the uuid package

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that way you are right, but your function arguments contains no function except callbackR, which will be removed before serialization. Therefore serialization should be fast. Also personally I think using runif is good enough to create unique IDs unless people set seed before calling the function. In that case, you might want to consider utils::capture.output({print(environment())}) I proposed to ensure different IDs created.

paramsSerialize <- params
paramsSerialize[['callbackR']] <- NULL

cbid <- sprintf("shinyalert-%s-%s",
digest::digest(paramsSerialize),
as.integer(stats::runif(1, 0, 1e9)))
params[['cbid']] <- session$ns(cbid)
}


Expand All @@ -273,5 +290,20 @@ shinyalert <- function(
params[["inputId"]] <- session$ns(params[["inputId"]])
session$sendCustomMessage(type = "shinyalert.show", message = params)

invisible(NULL)
invisible(params[["cbid"]])
}


#' Dismiss one or more popup messages
#' @param n Number of popup messages to dismiss. If set to \code{NULL}, then
#' all modals will be dismissed; default is \code{NULL}.
#' @param cbid ID created by \code{\link{shinyalert}}. Usually \code{cbid} is
#' used to close a specific alert. Note that if \code{cbid} is specified,
#' then \code{n} will be ignored.
#' @export
closeAlert <- function(n = NULL, cbid = NULL){

session <- getSession()
session$sendCustomMessage(type = "shinyalert.close",
message = list(count = n, cbid = cbid))
}
2 changes: 1 addition & 1 deletion inst/www/shared/swalservice/swalservice.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ SwalService.prototype = {
var pending = {
args: arguments,
id: this.nextId++
}
};
return this._swalWithId(pending);
},

Expand Down
70 changes: 60 additions & 10 deletions inst/www/srcjs/shinyalert.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
var swalService = new SwalService({showPendingMessage: false});
shinyalert = {};
shinyalert.num = 0; // Used to make the timer work
shinyalert.instances = []; // Used to make the timer work

Shiny.addCustomMessageHandler('shinyalert.show', function(params) {
shinyalert.num++;

var callbackJS = function(value) {};
if (params['callbackJS'] != null) {
Expand All @@ -13,15 +12,27 @@ Shiny.addCustomMessageHandler('shinyalert.show', function(params) {
}

var callbackR = function(value) {};
if (params['cbid'] != null) {
var cbid = params['cbid'];
delete params['cbid'];
// Always create cbid
var cbid = params['cbid'];
delete params['cbid'];
if(typeof cbid === 'string'){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is cbid not alwasy going to be a string?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove the if statement if you always include cbid in R code.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't before, but my understanding is that your PR ensures that cbid is ALWAYS going to be in the javascript parameters, in which case this check is redundant. Is that right?

callbackR = function(value) {
Shiny.onInputChange(cbid, value);
}
}

var callback = function(value) {
// Remove instance immediately
var ii = 0;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a better variable name

for(ii in shinyalert.instances){
swalService.close( shinyalert.instances[ii].swal_id );
if( shinyalert.instances[ii].cbid === cbid ){
shinyalert.instances.splice( ii, 1 );
}
break;
}


if ('compareVersion' in Shiny &&
Shiny.compareVersion(Shiny.version, ">=", "1.1.0") ) {
Shiny.setInputValue(params['inputId'], value, {priority: "event"});
Expand All @@ -33,15 +44,54 @@ Shiny.addCustomMessageHandler('shinyalert.show', function(params) {
delete params['inputId'];
}

if (params['timer'] != 0) {
var timer = params['timer'];
delete params['timer'];

var swal_id = swalService.swal(params, callback);
shinyalert.instances.push({
swal_id : swal_id,
cbid : cbid
});

// Changed timer != 0 to timer > 0
if (timer > 0) {
setTimeout(function(x) {
if (x == shinyalert.num) {
swalService.close();
var ii = 0;
for(ii in shinyalert.instances){
swalService.close( x );
if( shinyalert.instances[ii].swal_id === x ){
shinyalert.instances.splice( ii, 1 );
}
break;
}
}, params['timer'], shinyalert.num);
}, timer, swal_id);
}
delete params['timer'];

Shiny.unbindAll($(".sweet-alert"));
swalService.swal(params, callback);
});

Shiny.addCustomMessageHandler('shinyalert.close', function(params) {
var n = (params && params.count) || shinyalert.instances.length,
cbid = params.cbid;

var i, item;
if( typeof cbid === 'string' ){
// close specific alert
for( i = 0; i < shinyalert.instances.length; i++ ){
item = shinyalert.instances[i];
if( item.cbid === cbid ){
swalService.close( item.swal_id );
shinyalert.instances.splice( i, 1 );
break;
}
}
} else {
// dismiss n alerts
item = shinyalert.instances.splice(0, n);
for( i = 0; i < item.length; i++ ){
swalService.close( item[i].swal_id );
}
}

});
19 changes: 19 additions & 0 deletions man/closeAlert.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions man/shinyalert.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions vignettes/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
shinyalert.R
shinyalert.html