@@ -138,7 +138,7 @@ render_dependencies <- function(dependencies, local = TRUE, prefix=NULL) {
138
138
139
139
# According to Dash convention, label react and react-dom as originating
140
140
# in dash_renderer package, even though all three are currently served
141
- # u p from the DashR package
141
+ # up from the DashR package
142
142
if (dep $ name %in% c(" react" , " react-dom" , " prop-types" )) {
143
143
dep $ name <- " dash-renderer"
144
144
}
@@ -352,14 +352,38 @@ clean_dependencies <- function(deps) {
352
352
return (deps_with_file )
353
353
}
354
354
355
+ insertIntoCallbackMap <- function (map , inputs , output , state , func ) {
356
+ map [[createCallbackId(output )]] <- list (inputs = inputs ,
357
+ output = output ,
358
+ state = state ,
359
+ func = func
360
+ )
361
+ if (length(map ) > = 2 ) {
362
+ ids <- lapply(names(map ), function (x ) dash ::: getIdProps(x )$ ids )
363
+ props <- lapply(names(map ), function (x ) dash ::: getIdProps(x )$ props )
364
+
365
+ outputs_as_list <- mapply(paste , ids , props , sep = " ." , SIMPLIFY = FALSE )
366
+
367
+ if (length(Reduce(intersect , outputs_as_list ))) {
368
+ stop(sprintf(" One or more outputs are duplicated across callbacks. Please ensure that all ID and property combinations are unique." ), call. = FALSE )
369
+ }
370
+ }
371
+ return (map )
372
+ }
373
+
355
374
assert_valid_callbacks <- function (output , params , func ) {
356
375
inputs <- params [vapply(params , function (x ) ' input' %in% attr(x , " class" ), FUN.VALUE = logical (1 ))]
357
376
state <- params [vapply(params , function (x ) ' state' %in% attr(x , " class" ), FUN.VALUE = logical (1 ))]
358
-
377
+
359
378
invalid_params <- vapply(params , function (x ) {
360
379
! any(c(' input' , ' state' ) %in% attr(x , " class" ))
361
380
}, FUN.VALUE = logical (1 ))
362
381
382
+ # Verify that no outputs are duplicated
383
+ if (length(output ) != length(unique(output ))) {
384
+ stop(sprintf(" One or more callback outputs have been duplicated; please confirm that all outputs are unique." ), call. = FALSE )
385
+ }
386
+
363
387
# Verify that params contains no elements that are not either members of 'input' or 'state' classes
364
388
if (any(invalid_params )) {
365
389
stop(sprintf(" Callback parameters must be inputs or states. Please verify formatting of callback parameters." ), call. = FALSE )
@@ -371,10 +395,22 @@ assert_valid_callbacks <- function(output, params, func) {
371
395
}
372
396
373
397
# Assert that the component ID as passed is a string.
374
- if (! (is.character(output $ id ) & ! grepl(" ^\\ s*$" , output $ id ) & ! grepl(" \\ ." , output $ id ))) {
375
- stop(sprintf(" Callback IDs must be (non-empty) character strings that do not contain one or more dots/periods. Please verify that the component ID is valid." ), call. = FALSE )
398
+ # This function inspects the output object to see if its ID
399
+ # is a valid string.
400
+ validateOutput <- function (string ) {
401
+ return ((is.character(string [[" id" ]]) & ! grepl(" ^\\ s*$" , string [[" id" ]]) & ! grepl(" \\ ." , string [[" id" ]])))
376
402
}
377
403
404
+ # Check if the callback uses multiple outputs
405
+ if (any(sapply(output , is.list ))) {
406
+ invalid_callback_ID <- (! all(vapply(output , validateOutput , logical (1 ))))
407
+ } else {
408
+ invalid_callback_ID <- (! validateOutput(output ))
409
+ }
410
+ if (invalid_callback_ID ) {
411
+ stop(sprintf(" Callback IDs must be (non-empty) character strings that do not contain one or more dots/periods. Please verify that the component ID is valid." ), call. = FALSE )
412
+ }
413
+
378
414
# Assert that user_function is a valid function
379
415
if (! (is.function(func ))) {
380
416
stop(sprintf(" The callback method's 'func' parameter requires a function as its argument. Please verify that 'func' is a valid, executable R function." ), call. = FALSE )
@@ -397,7 +433,22 @@ assert_valid_callbacks <- function(output, params, func) {
397
433
398
434
# Check that outputs are not inputs
399
435
# https://github.com/plotly/dash/issues/323
400
- inputs_vs_outputs <- lapply(inputs , function (x ) identical(x , output ))
436
+
437
+ # helper function to permit same mapply syntax regardless
438
+ # of whether output is defined using output function or not
439
+ listWrap <- function (x ){
440
+ if (! any(sapply(x , is.list ))) {
441
+ return (list (x ))
442
+ } else {
443
+ x
444
+ }
445
+ }
446
+
447
+ # determine whether any input matches the output, or outputs, if
448
+ # multiple callback scenario
449
+ inputs_vs_outputs <- mapply(function (inputObject , outputObject ) {
450
+ identical(outputObject [[" id" ]], inputObject [[" id" ]]) & identical(outputObject [[" property" ]], inputObject [[" property" ]])
451
+ }, inputs , listWrap(output ))
401
452
402
453
if (TRUE %in% inputs_vs_outputs ) {
403
454
stop(sprintf(" Circular input and output arguments were found. Please verify that callback outputs are not also input arguments." ), call. = FALSE )
@@ -828,3 +879,25 @@ getDashMetadata <- function(pkgname) {
828
879
metadataFn <- as.vector(fnList [grepl(" ^\\ .dash.+_js_metadata$" , fnList )])
829
880
return (metadataFn )
830
881
}
882
+
883
+ createCallbackId <- function (output ) {
884
+ # check if callback uses single output
885
+ if (! any(sapply(output , is.list ))) {
886
+ id <- paste0(output , collapse = " ." )
887
+ } else {
888
+ # multi-output callback, concatenate
889
+ ids <- vapply(output , function (x ) {
890
+ paste(x , collapse = " ." )
891
+ }, character (1 ))
892
+ id <- paste0(" .." , paste0(ids , collapse = " ..." ), " .." )
893
+ }
894
+ return (id )
895
+ }
896
+
897
+ getIdProps <- function (output ) {
898
+ output_ids <- strsplit(substr(output , 3 , nchar(output )- 2 ), ' ...' , fixed = TRUE )
899
+ idprops <- lapply(output_ids , strsplit , ' .' , fixed = TRUE )
900
+ ids <- vapply(unlist(idprops , recursive = FALSE ), ' [' , character (1 ), 1 )
901
+ props <- vapply(unlist(idprops , recursive = FALSE ), ' [' , character (1 ), 2 )
902
+ return (list (ids = ids , props = props ))
903
+ }
0 commit comments