@@ -180,6 +180,7 @@ Dash <- R6::R6Class(
180
180
181
181
dash_index <- routes_pathname_prefix
182
182
route $ add_handler(" get" , dash_index , function (request , response , keys , ... ) {
183
+
183
184
response $ body <- private $ .index
184
185
response $ status <- 200L
185
186
response $ type <- ' html'
@@ -188,6 +189,7 @@ Dash <- R6::R6Class(
188
189
189
190
dash_layout <- paste0(routes_pathname_prefix , " _dash-layout" )
190
191
route $ add_handler(" get" , dash_layout , function (request , response , keys , ... ) {
192
+
191
193
lay <- private $ layout_render()
192
194
response $ body <- to_JSON(lay , pretty = TRUE )
193
195
response $ status <- 200L
@@ -196,6 +198,7 @@ Dash <- R6::R6Class(
196
198
})
197
199
198
200
dash_deps <- paste0(routes_pathname_prefix , " _dash-dependencies" )
201
+
199
202
route $ add_handler(" get" , dash_deps , function (request , response , keys , ... ) {
200
203
201
204
# dash-renderer wants an empty array when no dependencies exist (see python/01.py)
@@ -206,19 +209,13 @@ Dash <- R6::R6Class(
206
209
return (FALSE )
207
210
}
208
211
209
- # client wants the mapping formatted this way -- https://github.com/plotly/dash/blob/d2ebc837/dash/dash.py#L367-L378
210
- outputs <- strsplit(names(private $ callback_map ), " \\ ." )
211
- payload <- Map(function (x , y ) {
212
- # IMPORTANT: if state/events don't exist, dash-renderer wants them
213
- # to be an empty array (i.e., null/missing won't work)
212
+ payload <- Map(function (callback_signature ) {
214
213
list (
215
- output = list (id = y [[1 ]], property = y [[2 ]]),
216
- inputs = setNames(callback_inputs(x ), NULL ),
217
- state = setNames(callback_states(x ), NULL ),
218
- # Chris mentioned that events might/should be deprecated
219
- events = setNames(callback_events(x ), NULL )
214
+ output = callback_signature $ output ,
215
+ inputs = callback_signature $ inputs ,
216
+ state = callback_signature $ state
220
217
)
221
- }, private $ callback_map , outputs )
218
+ }, private $ callback_map )
222
219
223
220
response $ body <- to_JSON(setNames(payload , NULL ))
224
221
response $ status <- 200L
@@ -228,7 +225,6 @@ Dash <- R6::R6Class(
228
225
229
226
dash_update <- paste0(routes_pathname_prefix , " _dash-update-component" )
230
227
route $ add_handler(" post" , dash_update , function (request , response , keys , ... ) {
231
-
232
228
request <- request_parse_json(request )
233
229
234
230
if (! " output" %in% names(request $ body )) {
@@ -240,41 +236,14 @@ Dash <- R6::R6Class(
240
236
241
237
# get the callback associated with this particular output
242
238
thisOutput <- with(request $ body $ output , paste(id , property , sep = " ." ))
243
- callback <- private $ callback_map [[thisOutput ]]
239
+ callback <- private $ callback_map [[thisOutput ]][[ ' func ' ]]
244
240
if (! length(callback )) stop_report(" Couldn't find output component." )
245
241
if (! is.function(callback )) {
246
242
stop(sprintf(" Couldn't find a callback function associated with '%s'" , thisOutput ))
247
243
}
248
244
249
- callback_args <- formals(callback )
250
- if (length(callback_args )) {
251
- get_key <- function (x ) paste0(x [[" id" ]], " ." , x [[" property" ]])
252
- input_keys <- vapply(request $ body $ inputs , get_key , character (1 ))
253
- state_keys <- vapply(request $ body $ states , get_key , character (1 ))
254
-
255
- get_value <- function (x ) getFromNamespace(" simplify" , " jsonlite" )(x [[" value" ]])
256
- input_values <- lapply(request $ body $ inputs , get_value )
257
- state_values <- lapply(request $ body $ state , get_value )
258
-
259
- client_values <- c(
260
- setNames(input_values , input_keys ),
261
- setNames(state_values , state_keys )
262
- )
263
-
264
- get_dependency_key <- function (arg ) {
265
- val <- tryNULL(eval(arg ))
266
- if (is.dependency(val )) val [[" key" ]] else NA
267
- }
268
-
269
- callback_arg_keys <- sapply(callback_args , get_dependency_key )
270
-
271
- # note that a modifyList() strategy throws away NULL args, which is WRONG
272
- for (i in names(client_values )) {
273
- callback_args [[match(i , callback_arg_keys )]] <- client_values [[i ]]
274
- }
275
- }
276
-
277
- output_value <- do.call(callback , args = as.list(callback_args ))
245
+ callback_args <- lapply(c(request $ body $ inputs , request $ body $ state ), `[[` , 3 )
246
+ output_value <- do.call(callback , callback_args )
278
247
279
248
# have to format the response body like this
280
249
# https://github.com/plotly/dash/blob/064c811d/dash/dash.py#L562-L584
@@ -295,6 +264,7 @@ Dash <- R6::R6Class(
295
264
# https://plotly.slack.com/archives/D07PDTRK6/p1507657249000714?thread_ts=1505157408.000123&cid=D07PDTRK6
296
265
dash_suite <- paste0(routes_pathname_prefix , " _dash-component-suites" )
297
266
route $ add_handler(" get" , dash_suite , function (request , response , keys , ... ) {
267
+
298
268
response $ status <- 500L
299
269
response $ body <- " Not yet implemented"
300
270
TRUE
@@ -320,6 +290,8 @@ Dash <- R6::R6Class(
320
290
},
321
291
layout_set = function (... ) {
322
292
private $ layout <- if (is.function(..1 )) ..1 else list (... )
293
+ # render the layout, and then return the rendered layout without printing
294
+ invisible (private $ layout_render())
323
295
},
324
296
325
297
# ------------------------------------------------------------------------
@@ -366,55 +338,21 @@ Dash <- R6::R6Class(
366
338
# ------------------------------------------------------------------------
367
339
# callback registration
368
340
# ------------------------------------------------------------------------
369
- callback = function (func = NULL , output = NULL ) {
370
-
371
- # argument type checking
372
- assertthat :: assert_that(is.function(func ))
373
- assertthat :: assert_that(is.output(output ))
374
-
375
- # TODO: cache layouts so we don't have to do this for every callback...
376
- layout <- private $ layout_render()
377
- if (identical(layout , welcome_page())) {
378
- stop(" The layout must be set before defining any callbacks" , call. = FALSE )
379
- }
380
-
381
- # -----------------------------------------------------------------------
382
- # verify that output/input/state IDs provided exists in the layout
383
- # -----------------------------------------------------------------------
384
- callbackInputs <- callback_inputs(func )
385
- callbackStates <- callback_states(func )
386
-
387
- callback_ids <- unlist(c(
388
- output $ id ,
389
- sapply(callbackInputs , " [[" , " id" ),
390
- sapply(callbackStates , " [[" , " id" )
391
- ))
392
- illegal_ids <- setdiff(callback_ids , private $ layout_ids )
393
- if (length(illegal_ids ) && ! private $ suppress_callback_exceptions ) {
394
- warning(
395
- sprintf(
396
- " The following id(s) do not match any in the layout: '%s'" ,
397
- paste(illegal_ids , collapse = " ', '" )
398
- ),
399
- call. = FALSE
341
+ callback = function (output , params , func ) {
342
+ assert_valid_callbacks(output , params , func )
343
+
344
+ inputs <- params [vapply(params , function (x ) ' input' %in% attr(x , " class" ), FUN.VALUE = logical (1 ))]
345
+ state <- params [vapply(params , function (x ) ' state' %in% attr(x , " class" ), FUN.VALUE = logical (1 ))]
346
+
347
+ # register the callback_map
348
+ private $ callback_map [[paste(output $ id , output $ property , sep = ' .' )]] <- list (
349
+ output = output ,
350
+ inputs = inputs ,
351
+ state = state ,
352
+ func = func
400
353
)
401
- }
402
-
403
- # ----------------------------------------------------------------------
404
- # verify that properties attached to output/inputs/state value are valid
405
- # ----------------------------------------------------------------------
406
- if (! private $ suppress_callback_exceptions ) {
407
- validate_dependency(layout , output )
408
- lapply(callbackInputs , function (i ) validate_dependency(layout , i ))
409
- lapply(callbackStates , function (s ) validate_dependency(layout , s ))
410
- }
411
-
412
- # store the callback mapping/function so we may access it later
413
- # https://github.com/plotly/dash/blob/d2ebc837/dash/dash.py#L530-L546
414
- private $ callback_map [[output [[" key" ]]]] <- func
415
354
},
416
355
417
-
418
356
# ------------------------------------------------------------------------
419
357
# convenient fiery wrappers
420
358
# ------------------------------------------------------------------------
@@ -443,7 +381,7 @@ Dash <- R6::R6Class(
443
381
dependencies = list (),
444
382
dependencies_user = list (),
445
383
dependencies_internal = list (),
446
-
384
+
447
385
# layout stuff
448
386
layout = welcome_page(),
449
387
layout_ids = NULL ,
@@ -505,27 +443,27 @@ Dash <- R6::R6Class(
505
443
# construct function name based on package name
506
444
fn_name <- paste0(" ." , pkg , " _js_metadata" )
507
445
fn_summary <- getAnywhere(fn_name )
508
-
446
+
509
447
# ensure that the object refers to a function,
510
448
# and we are able to locate it somewhere
511
449
if (length(fn_summary $ where ) == 0 ) return (NULL )
512
-
450
+
513
451
if (mode(fn_summary $ obj [[1 ]]) == " function" ) {
514
452
# function is available
515
453
dep_list <- do.call(fn_summary $ obj [[1 ]], list ())
516
454
517
455
return (dep_list )
518
456
} else {
519
- return (NULL )
457
+ return (NULL )
520
458
}
521
459
})
522
-
460
+
523
461
deps_layout <- unlist(deps_layout , recursive = FALSE )
524
462
525
463
# add on HTML dependencies we've identified by crawling the layout
526
464
private $ dependencies <- c(private $ dependencies , deps_layout )
527
465
528
- # DashR's own dependencies
466
+ # DashR's own dependencies
529
467
private $ dependencies_internal <- dashR ::: .dashR_js_metadata()
530
468
531
469
# return the computed layout
@@ -572,6 +510,7 @@ Dash <- R6::R6Class(
572
510
# akin to https://github.com/plotly/dash-renderer/blob/master/dash_renderer/__init__.py
573
511
react_version_enabled = function () {
574
512
version <- private $ dependencies_internal $ react $ version
513
+ return (version )
575
514
},
576
515
react_deps = function () {
577
516
deps <- private $ dependencies_internal
@@ -580,11 +519,13 @@ Dash <- R6::R6Class(
580
519
react_versions = function () {
581
520
vapply(private $ react_deps(), " [[" , character (1 ), " version" )
582
521
},
583
-
522
+
584
523
# akin to https://github.com/plotly/dash/blob/d2ebc837/dash/dash.py#L338
585
524
# note discussion here https://github.com/plotly/dash/blob/d2ebc837/dash/dash.py#L279-L284
586
525
.index = NULL ,
587
526
index = function () {
527
+
528
+
588
529
# collect and resolve dependencies
589
530
depsAll <- compact(c(
590
531
private $ react_deps()[private $ react_versions() %in% private $ react_version_enabled()],
@@ -643,9 +584,6 @@ Dash <- R6::R6Class(
643
584
)
644
585
)
645
586
646
-
647
-
648
-
649
587
# verify that properties attached to output/inputs/state value are valid
650
588
# @param layout
651
589
# @param component a component (should be a dependency)
@@ -669,16 +607,16 @@ validate_dependency <- function(layout, dependency) {
669
607
TRUE
670
608
}
671
609
672
- assert_valid_wildcards <- function (... )
610
+ assert_valid_wildcards <- function (... )
673
611
{
674
612
args <- list (... )
675
613
validation_results <- lapply(args , function (x ) {
676
614
grepl(c(' ^data-[a-zA-Z0-9]{1,}$|^aria-[a-zA-Z0-9]{1,}$' ), x )
677
615
}
678
616
)
679
617
if (FALSE %in% validation_results ) {
680
- stop(sprintf(" The following wildcards are not currently valid in DashR: '%s'" ,
681
- paste((args )[grepl(FALSE , unlist(validation_results ))],
618
+ stop(sprintf(" The following wildcards are not currently valid in DashR: '%s'" ,
619
+ paste((args )[grepl(FALSE , unlist(validation_results ))],
682
620
collapse = " , " )), call. = FALSE )
683
621
} else {
684
622
return (args )
0 commit comments